// Copyright 2014 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_request.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/auto_reset.h"
#include "base/byte_size.h"
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/safe_ref.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/no_destructor.h"
#include "base/not_fatal_until.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "base/state_transitions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/trace_event.h"
#include "base/types/optional_util.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/agent_cluster_key.h"
#include "content/browser/back_forward_cache/back_forward_cache_impl.h"
#include "content/browser/bad_message.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/client_hints/client_hints.h"
#include "content/browser/connection_allowlist_utils.h"
#include "content/browser/declarative_performance_observer/declarative_performance_observer.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/network_service_devtools_observer.h"
#include "content/browser/download/download_manager_impl.h"
#include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "content/browser/loader/browser_initiated_resource_request.h"
#include "content/browser/loader/cached_navigation_url_loader.h"
#include "content/browser/loader/navigation_early_hints_manager.h"
#include "content/browser/loader/navigation_fast_fetch_manager.h"
#include "content/browser/loader/navigation_url_loader.h"
#include "content/browser/loader/object_navigation_fallback_body_loader.h"
#include "content/browser/loader/url_loader_factory_utils.h"
#include "content/browser/navigation_or_document_handle.h"
#include "content/browser/network/cross_origin_embedder_policy_reporter.h"
#include "content/browser/network_service_instance_impl.h"
#include "content/browser/origin_agent_cluster_isolation_state.h"
#include "content/browser/origin_trials/origin_trials_utils.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/preload_activation_report_manager.h"
#include "content/browser/preloading/preload_activation_report_utils.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/preloading/prerender/prerender_navigation_utils.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/concurrent_navigations_commit_deferring_condition.h"
#include "content/browser/renderer_host/cookie_utils.h"
#include "content/browser/renderer_host/debug_urls.h"
#include "content/browser/renderer_host/frame_navigation_entry.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/local_network_access_util.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request_info.h"
#include "content/browser/renderer_host/navigation_state_keep_alive.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/navigator_delegate.h"
#include "content/browser/renderer_host/page_delegate.h"
#include "content/browser/renderer_host/render_frame_host_csp_context.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/scoped_view_transition_resources.h"
#include "content/browser/renderer_host/subframe_history_navigation_throttle.h"
#include "content/browser/scoped_active_url.h"
#include "content/browser/security/coop/cross_origin_opener_policy_reporter.h"
#include "content/browser/security/cpsp/child_process_security_policy_impl.h"
#include "content/browser/service_worker/service_worker_client.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_main_resource_handle.h"
#include "content/browser/site_info.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_params_helper.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
#include "content/common/content_constants_internal.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/debug_utils.h"
#include "content/common/features.h"
#include "content/common/navigation_params_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/client_hints_controller_delegate.h"
#include "content/public/browser/commit_deferring_condition.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/cookie_access_details.h"
#include "content/public/browser/direct_sockets_delegate.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/isolated_context_util.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_ui_data.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "content/public/browser/peak_gpu_memory_tracker_factory.h"
#include "content/public/browser/reduce_accept_language_controller_delegate.h"
#include "content/public/browser/reduce_accept_language_utils.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/runtime_feature_state/runtime_feature_state_document_data.h"
#include "content/public/browser/security_principal.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/tracing_support.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/origin_util.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/features.h"
#include "net/base/filename_util.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/url_util.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/filter/source_stream_type.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/storage_access_api/status.h"
#include "net/url_request/redirect_info.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/connection_allowlist.h"
#include "services/network/public/cpp/connection_allowlist_parser.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/content_decoding_interceptor.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/cpp/cross_origin_embedder_policy.h"
#include "services/network/public/cpp/cross_origin_opener_policy.h"
#include "services/network/public/cpp/cross_origin_resource_policy.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/header_util.h"
#include "services/network/public/cpp/integrity_policy.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/permissions_policy/fenced_frame_permissions_policies.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_features.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/cpp/supports_loading_mode/supports_loading_mode_parser.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/connection_change_observer_client.mojom.h"
#include "services/network/public/mojom/device_bound_sessions.mojom.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/link_header.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "services/network/public/mojom/supports_loading_mode.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/public/mojom/web_client_hints_types.mojom.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom.h"
#include "storage/browser/blob/blob_url_registry.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/blink/public/common/blob/blob_utils.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h"
#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/common/navigation/navigation_params.h"
#include "third_party/blink/public/common/navigation/navigation_params_mojom_traits.h"
#include "third_party/blink/public/common/navigation/navigation_policy.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
#include "third_party/blink/public/common/permissions_policy/document_policy.h"
#include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_context.h"
#include "third_party/blink/public/common/security/address_space_feature.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
#include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
#include "third_party/blink/public/mojom/navigation/prefetched_signed_exchange_info.mojom.h"
#include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
#include "third_party/blink/public/mojom/storage_key/ancestor_chain_bit.mojom.h"
#include "third_party/blink/public/mojom/timing/resource_timing.mojom-forward.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
#include "third_party/blink/public/platform/resource_request_blocked_reason.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "ui/compositor/compositor_lock.h"
#include "url/origin.h"
#include "url/url_constants.h"

#if BUILDFLAG(IS_ANDROID)
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
#include "ui/android/window_android.h"
#include "ui/android/window_android_compositor.h"
#endif

namespace content {

namespace {

// Kill-switch for the fix to copy item_sequence_number and
// document_sequence_number from a prerender navigation entry during prerender
// activation (https://crbug.com/442618062).
BASE_FEATURE(kPrerenderFixSequenceNumbers, base::FEATURE_ENABLED_BY_DEFAULT);

// Default timeout for the READY_TO_COMMIT -> COMMIT transition. Chosen
// initially based on the Navigation.ReadyToCommitUntilCommit UMA, and then
// refined based on feedback based on CrashExitCodes.Renderer/RESULT_CODE_HUNG.
constexpr base::TimeDelta kDefaultCommitTimeout = base::Seconds(30);

// Timeout for the READY_TO_COMMIT -> COMMIT transition.
// Overrideable via SetCommitTimeoutForTesting.
base::TimeDelta g_commit_timeout = kDefaultCommitTimeout;

#if BUILDFLAG(IS_ANDROID)
// Timeout for locking the compositor at the beginning of navigation.
constexpr base::TimeDelta kCompositorLockTimeout = base::Milliseconds(150);
#endif

// Flag to control whether redirect URLs are being sanitized before sending
// them to the renderer process as part of the navigation.
// See https://crbug.com/40095391.
BASE_FEATURE(kSanitizeRedirectUrlsDuringNavigation,
             base::FEATURE_ENABLED_BY_DEFAULT);

// Killswitch to replace the current NavigationEntry for same-URL navigations
// recovering from an error document. See https://crbug.com/396645696.
BASE_FEATURE(kReplaceEntryWhenRecoveringFromError,
             base::FEATURE_ENABLED_BY_DEFAULT);

const base::FeatureParam<bool> kDeferSpeculativeRFHWaitUntilFinalResponse{
    &features::kDeferSpeculativeRFHCreation, "wait_until_final_response",
    false};

// Denotes the type of user agent string value sent in the User-Agent request
// header.
//
// Corresponds to the "UserAgentStringType" histogram enumeration type in
// tools/metrics/histograms/enums.xml.
//
// PLEASE DO NOT REORDER, REMOVE, OR CHANGE THE MEANING OF THESE VALUES.
enum class UserAgentStringType {
  kFullVersion,
  kReducedVersion,
  kOverriden,
  kMaxValue = kOverriden
};

// Returns the net load flags to use based on the navigation type.
// TODO(clamy): Remove the blink code that sets the caching flags.
void UpdateLoadFlagsWithCacheFlags(int* load_flags,
                                   blink::mojom::NavigationType navigation_type,
                                   bool is_post) {
  switch (navigation_type) {
    case blink::mojom::NavigationType::RELOAD:
      *load_flags |= net::LOAD_VALIDATE_CACHE;
      break;
    case blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE:
      *load_flags |= net::LOAD_BYPASS_CACHE;
      break;
    case blink::mojom::NavigationType::RESTORE:
      *load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
      break;
    case blink::mojom::NavigationType::RESTORE_WITH_POST:
      *load_flags |=
          net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
      break;
    case blink::mojom::NavigationType::SAME_DOCUMENT:
    case blink::mojom::NavigationType::DIFFERENT_DOCUMENT:
      if (is_post)
        *load_flags |= net::LOAD_VALIDATE_CACHE;
      break;
    case blink::mojom::NavigationType::HISTORY_SAME_DOCUMENT:
    case blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT:
      if (is_post) {
        *load_flags |=
            net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
      } else {
        *load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
      }
      break;
  }
}

// This should match blink::ResourceRequest::needsHTTPOrigin.
bool NeedsHTTPOrigin(const std::string& method) {
  // Blink version of this function checks if the Origin header might have
  // already been added to the headers.  This check is not replicated below
  // because:
  // 1. We want to overwrite the old (renderer-provided) header value
  //    with a new, trustworthy (browser-provided) value.
  // 2. The rest of the function matches the Blink version, so there should
  //    be no discrepancies in the Origin value used.

  // Don't send an Origin header for GET or HEAD to avoid privacy issues.
  // For example, if an intranet page has a hyperlink to an external web
  // site, we don't want to include the Origin of the request because it
  // will leak the internal host name. Similar privacy concerns have lead
  // to the widespread suppression of the Referer header at the network
  // layer.
  if (method == "GET" || method == "HEAD")
    return false;

  // For non-GET and non-HEAD methods, always send an Origin header so the
  // server knows we support this feature.
  return true;
}

// Computes the value that should be set for the User-Agent header, if
// `user_agent_override` is non-empty, `user_agent_override` is returned as the
// header value.
std::string ComputeUserAgentValue(const std::string& user_agent_override) {
  if (!user_agent_override.empty()) {
    base::UmaHistogramEnumeration("Navigation.UserAgentStringType",
                                  UserAgentStringType::kOverriden);
    return user_agent_override;
  }

  base::UmaHistogramEnumeration(
      "Navigation.UserAgentStringType",
      base::FeatureList::IsEnabled(
          blink::features::kReduceUserAgentMinorVersion)
          ? UserAgentStringType::kReducedVersion
          : UserAgentStringType::kFullVersion);

  return GetContentClient()->browser()->GetUserAgent();
}

void AddAdditionalRequestHeaders(
    net::HttpRequestHeaders* headers,
    const GURL& url,
    blink::mojom::NavigationType navigation_type,
    ui::PageTransition transition,
    BrowserContext* browser_context,
    const std::string& method,
    const std::string& user_agent_override,
    const std::optional<url::Origin>& initiator_origin,
    blink::mojom::Referrer* referrer,
    FrameTreeNode* frame_tree_node,
    bool is_browser_initiated) {
  if (!url.SchemeIsHTTPOrHTTPS())
    return;

  bool is_reload = NavigationTypeUtils::IsReload(navigation_type);
  bool is_history = NavigationTypeUtils::IsHistory(navigation_type);
  RenderViewHostImpl* render_view_host =
      frame_tree_node->current_frame_host()->render_view_host();
  const blink::RendererPreferences& render_prefs =
      render_view_host->GetDelegate()->GetRendererPrefs(render_view_host);
  UpdateAdditionalHeadersForBrowserInitiatedRequest(
      headers, browser_context, is_reload, render_prefs,
      /*is_for_worker_script*=*/false);

  // Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational
  // requests, as described in
  // https://w3c.github.io/webappsec/specs/upgrade/#feature-detect
  headers->SetHeaderIfMissing("Upgrade-Insecure-Requests", "1");

  headers->SetHeaderIfMissing(
      net::HttpRequestHeaders::kUserAgent,
      ComputeUserAgentValue(user_agent_override));

  if (!render_prefs.enable_referrers) {
    *referrer =
        blink::mojom::Referrer(GURL(), network::mojom::ReferrerPolicy::kNever);
  }

  // Next, set the HTTP Origin if needed.
  std::optional<std::string> existing_origin =
      headers->GetHeader(net::HttpRequestHeaders::kOrigin);
  bool needs_http_origin = NeedsHTTPOrigin(method);
  if (needs_http_origin) {
    url::Origin origin_header_value = initiator_origin.value_or(url::Origin());
    origin_header_value = Referrer::SanitizeOriginForRequest(
        url, origin_header_value, referrer->policy);
    std::string serialized_origin = origin_header_value.Serialize();
    headers->SetHeader(net::HttpRequestHeaders::kOrigin, serialized_origin);
  } else if (!is_browser_initiated && !is_history) {
    headers->RemoveHeader(net::HttpRequestHeaders::kOrigin);
  }
  if (existing_origin && !is_browser_initiated && !is_history &&
      base::FeatureList::IsEnabled(features::kKillOnUnexpectedOriginHeader)) {
    // A well-behaved renderer will never provide an Origin header on a
    // navigation request, and will instead rely on the browser process to set
    // it. History navigations are excluded from this enforcement because it is
    // valid behavior for a renderer-initiated history navigation to reach this
    // point with an Origin header populated from an existing navigation entry.
    SCOPED_CRASH_KEY_STRING64("Bug487795397", "renderer_provided_origin",
                              existing_origin.value());
    SCOPED_CRASH_KEY_STRING64(
        "Bug487795397", "current_header_origin",
        headers->GetHeader(net::HttpRequestHeaders::kOrigin).value_or(""));
    SCOPED_CRASH_KEY_BOOL("Bug487795397", "needs_origin_header",
                          needs_http_origin);
    bad_message::ReceivedBadMessage(render_view_host->GetProcess(),
                                    bad_message::NR_BAD_ORIGIN_HEADER);
  }

  if (base::FeatureList::IsEnabled(features::kDocumentPolicyNegotiation)) {
    const blink::DocumentPolicyFeatureState& required_policy =
        frame_tree_node->effective_frame_policy().required_document_policy;
    if (!required_policy.empty()) {
      std::optional<std::string> policy_header =
          blink::DocumentPolicy::Serialize(required_policy);
      CHECK(policy_header);
      headers->SetHeader("Sec-Required-Document-Policy", policy_header.value());
    }
  }

  // Add the "Sec-Purpose: prefetch;prerender" header to prerender navigations
  // including subframe navigations.
  if (frame_tree_node->frame_tree().is_prerendering()) {
    PrerenderHost::GetFromFrameTreeNode(*frame_tree_node)
        .AddAdditionalRequestHeaders(*headers, *frame_tree_node);
  }
}

bool ShouldPropagateUserActivation(const url::Origin& previous_origin,
                                   const url::Origin& new_origin) {
  if ((previous_origin.scheme() != "http" &&
       previous_origin.scheme() != "https") ||
      (new_origin.scheme() != "http" && new_origin.scheme() != "https")) {
    return false;
  }

  if (previous_origin.host() == new_origin.host())
    return true;

  std::string previous_domain =
      net::registry_controlled_domains::GetDomainAndRegistry(
          previous_origin,
          net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
  std::string new_domain =
      net::registry_controlled_domains::GetDomainAndRegistry(
          new_origin,
          net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
  return !previous_domain.empty() && previous_domain == new_domain;
}

// LOG_NAVIGATION_TIMING_HISTOGRAM logs |value| for "Navigation.<histogram>" UMA
// as well as supplementary UMAs (depending on |transition| and |priority|)
// for BackForward/Reload/NewNavigation variants.
//
// kMaxTime and kBuckets constants are consistent with
// UmaHistogramMediumTimes, but a custom kMinTime is used for high
// fidelity near the low end of measured values.
//
// TODO(csharrison,nasko): This macro is incorrect for subframe navigations,
// which will only have subframe-specific transition types. This means that all
// subframes currently are tagged as NewNavigations.
#define LOG_NAVIGATION_TIMING_HISTOGRAM(histogram, transition, priority,      \
                                        duration)                             \
  do {                                                                        \
    const base::TimeDelta kMinTime = base::Milliseconds(1);                   \
    const base::TimeDelta kMaxTime = base::Minutes(3);                        \
    const int kBuckets = 50;                                                  \
    UMA_HISTOGRAM_CUSTOM_TIMES("Navigation." histogram, duration, kMinTime,   \
                               kMaxTime, kBuckets);                           \
    if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) {                      \
      UMA_HISTOGRAM_CUSTOM_TIMES("Navigation." histogram ".BackForward",      \
                                 duration, kMinTime, kMaxTime, kBuckets);     \
    } else if (ui::PageTransitionCoreTypeIs(transition,                       \
                                            ui::PAGE_TRANSITION_RELOAD)) {    \
      UMA_HISTOGRAM_CUSTOM_TIMES("Navigation." histogram ".Reload", duration, \
                                 kMinTime, kMaxTime, kBuckets);               \
    } else if (ui::PageTransitionIsNewNavigation(transition)) {               \
      UMA_HISTOGRAM_CUSTOM_TIMES("Navigation." histogram ".NewNavigation",    \
                                 duration, kMinTime, kMaxTime, kBuckets);     \
    } else {                                                                  \
      NOTREACHED() << "Invalid page transition: " << transition;              \
    }                                                                         \
    if (priority.has_value()) {                                               \
      if (priority.value() == base::Process::Priority::kBestEffort) {         \
        UMA_HISTOGRAM_CUSTOM_TIMES("Navigation." histogram                    \
                                   ".BackgroundProcessPriority",              \
                                   duration, kMinTime, kMaxTime, kBuckets);   \
      } else {                                                                \
        UMA_HISTOGRAM_CUSTOM_TIMES("Navigation." histogram                    \
                                   ".ForegroundProcessPriority",              \
                                   duration, kMinTime, kMaxTime, kBuckets);   \
      }                                                                       \
    }                                                                         \
  } while (0)

void RecordStartToCommitMetrics(base::TimeTicks navigation_start_time,
                                ui::PageTransition transition,
                                const base::TimeTicks& ready_to_commit_time,
                                std::optional<base::Process::Priority> priority,
                                bool is_same_process,
                                bool is_main_frame) {
  base::TimeTicks now = base::TimeTicks::Now();
  base::TimeDelta delta = now - navigation_start_time;
  LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit", transition, priority, delta);
  if (is_main_frame) {
    LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.MainFrame", transition,
                                    priority, delta);
  } else {
    LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.Subframe", transition,
                                    priority, delta);
  }
  if (is_same_process) {
    LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.SameProcess", transition,
                                    priority, delta);
    if (is_main_frame) {
      LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.SameProcess.MainFrame",
                                      transition, priority, delta);
    } else {
      LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.SameProcess.Subframe",
                                      transition, priority, delta);
    }
  } else {
    LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.CrossProcess", transition,
                                    priority, delta);
    if (is_main_frame) {
      LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.CrossProcess.MainFrame",
                                      transition, priority, delta);
    } else {
      LOG_NAVIGATION_TIMING_HISTOGRAM("StartToCommit.CrossProcess.Subframe",
                                      transition, priority, delta);
    }
  }
  if (!ready_to_commit_time.is_null()) {
    LOG_NAVIGATION_TIMING_HISTOGRAM("ReadyToCommitUntilCommit2", transition,
                                    priority, now - ready_to_commit_time);
  }
}

void RecordReadyToCommitMetrics(
    RenderFrameHostImpl* old_rfh,
    RenderFrameHostImpl* new_rfh,
    const blink::mojom::CommonNavigationParams& common_params,
    base::TimeTicks ready_to_commit_time,
    NavigationRequest::OriginAgentClusterEndResult
        origin_agent_cluster_end_result,
    bool did_receive_early_hints_before_cross_origin_redirect) {
  bool is_main_frame = !new_rfh->GetParent();
  bool is_same_process = old_rfh->GetProcess()->GetDeprecatedID() ==
                         new_rfh->GetProcess()->GetDeprecatedID();

  // Navigation.IsSameBrowsingInstance
  if (is_main_frame) {
    bool is_same_browsing_instance =
        old_rfh->GetSiteInstance()->IsRelatedSiteInstance(
            new_rfh->GetSiteInstance());

    UMA_HISTOGRAM_BOOLEAN("Navigation.IsSameBrowsingInstance",
                          is_same_browsing_instance);
  }

  // Navigation.IsSameSiteInstance
  {
    bool is_same_site_instance =
        old_rfh->GetSiteInstance() == new_rfh->GetSiteInstance();
    UMA_HISTOGRAM_BOOLEAN("Navigation.IsSameSiteInstance",
                          is_same_site_instance);
    if (is_main_frame) {
      UMA_HISTOGRAM_BOOLEAN("Navigation.IsSameSiteInstance.MainFrame",
                            is_same_site_instance);
    } else {
      UMA_HISTOGRAM_BOOLEAN("Navigation.IsSameSiteInstance.Subframe",
                            is_same_site_instance);
    }
  }

  // Navigation.IsLockedProcess
  {
    ProcessLock process_lock = new_rfh->GetProcess()->GetProcessLock();
    UMA_HISTOGRAM_BOOLEAN("Navigation.IsLockedProcess",
                          process_lock.IsLockedToSite());
    if (common_params.url.SchemeIsHTTPOrHTTPS()) {
      UMA_HISTOGRAM_BOOLEAN("Navigation.IsLockedProcess.HTTPOrHTTPS",
                            process_lock.IsLockedToSite());
    }
  }

  // Navigation.RequiresDedicatedProcess
  {
    UMA_HISTOGRAM_BOOLEAN(
        "Navigation.RequiresDedicatedProcess",
        new_rfh->GetSiteInstance()->RequiresDedicatedProcess());
    if (common_params.url.SchemeIsHTTPOrHTTPS()) {
      UMA_HISTOGRAM_BOOLEAN(
          "Navigation.RequiresDedicatedProcess.HTTPOrHTTPS",
          new_rfh->GetSiteInstance()->RequiresDedicatedProcess());
    }
  }

  // TimeToReadyToCommit2
  {
    constexpr std::optional<base::Process::Priority> kPriority = std::nullopt;
    base::TimeDelta delta =
        ready_to_commit_time - common_params.navigation_start;
    ui::PageTransition transition =
        ui::PageTransitionFromInt(common_params.transition);

    LOG_NAVIGATION_TIMING_HISTOGRAM("TimeToReadyToCommit2", transition,
                                    kPriority, delta);
    if (is_main_frame) {
      LOG_NAVIGATION_TIMING_HISTOGRAM("TimeToReadyToCommit2.MainFrame",
                                      transition, kPriority, delta);
    } else {
      LOG_NAVIGATION_TIMING_HISTOGRAM("TimeToReadyToCommit2.Subframe",
                                      transition, kPriority, delta);
    }
    if (is_same_process) {
      LOG_NAVIGATION_TIMING_HISTOGRAM("TimeToReadyToCommit2.SameProcess",
                                      transition, kPriority, delta);
    } else {
      LOG_NAVIGATION_TIMING_HISTOGRAM("TimeToReadyToCommit2.CrossProcess",
                                      transition, kPriority, delta);
    }
    if (did_receive_early_hints_before_cross_origin_redirect) {
      LOG_NAVIGATION_TIMING_HISTOGRAM(
          "TimeToReadyToCommit2.CrossOriginRedirectAfterEarlyHints", transition,
          kPriority, delta);
    }
  }

  // Navigation.OriginAgentCluster
  {
    UMA_HISTOGRAM_ENUMERATION("Navigation.OriginAgentCluster.Result",
                              origin_agent_cluster_end_result);
  }

  // Guest (<webview> tag) metrics.
  {
    base::UmaHistogramBoolean(
        "Navigation.IsGuest",
        new_rfh->GetSiteInstance()->GetSecurityPrincipal().IsGuest());
    if (new_rfh->GetSiteInstance()->GetSecurityPrincipal().IsGuest()) {
      base::UmaHistogramBoolean("Navigation.Guest.IsHTTPOrHTTPS",
                                common_params.url.SchemeIsHTTPOrHTTPS());
      base::UmaHistogramBoolean("Navigation.Guest.IsMainFrame", is_main_frame);
    }
  }
}

// Convert the navigation type to the appropriate cross-document one.
//
// This is currently used when:
// 1) Restarting a same-document navigation as cross-document.
// 2) Failing a navigation and committing an error page.
blink::mojom::NavigationType ConvertToCrossDocumentType(
    blink::mojom::NavigationType type) {
  switch (type) {
    case blink::mojom::NavigationType::SAME_DOCUMENT:
      return blink::mojom::NavigationType::DIFFERENT_DOCUMENT;
    case blink::mojom::NavigationType::HISTORY_SAME_DOCUMENT:
      return blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT;
    case blink::mojom::NavigationType::RELOAD:
    case blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE:
    case blink::mojom::NavigationType::RESTORE:
    case blink::mojom::NavigationType::RESTORE_WITH_POST:
    case blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT:
    case blink::mojom::NavigationType::DIFFERENT_DOCUMENT:
      return type;
  }
}

base::debug::CrashKeyString* GetNavigationRequestUrlCrashKey() {
  static auto* crash_key = base::debug::AllocateCrashKeyString(
      "navigation_request_url", base::debug::CrashKeySize::Size256);
  return crash_key;
}

base::debug::CrashKeyString* GetNavigationRequestInitiatorCrashKey() {
  static auto* crash_key = base::debug::AllocateCrashKeyString(
      "navigation_request_initiator", base::debug::CrashKeySize::Size64);
  return crash_key;
}

base::debug::CrashKeyString* GetNavigationRequestIsSameDocumentCrashKey() {
  static auto* crash_key = base::debug::AllocateCrashKeyString(
      "navigation_request_is_same_document", base::debug::CrashKeySize::Size64);
  return crash_key;
}

// Start a new nested async event with the given name.
template <class... Args>
void EnterChildTraceEvent(perfetto::StaticString name,
                          NavigationRequest* request,
                          Args... args) {
  TRACE_EVENT_END(
      "navigation",
      request->GetNavigationTracingTrack());  // Closes the previous child event
  TRACE_EVENT_BEGIN("navigation", name, request->GetNavigationTracingTrack(),
                    args...);
}

network::mojom::RequestDestination GetDestinationFromFrameTreeNode(
    FrameTreeNode* frame_tree_node) {
  if (frame_tree_node->IsInFencedFrameTree())
    return network::mojom::RequestDestination::kFencedframe;

  if (frame_tree_node->IsMainFrame()) {
    return network::mojom::RequestDestination::kDocument;
  }

  switch (frame_tree_node->frame_owner_element_type()) {
    case blink::FrameOwnerElementType::kObject:
      return network::mojom::RequestDestination::kObject;
    case blink::FrameOwnerElementType::kEmbed:
      return network::mojom::RequestDestination::kEmbed;
    case blink::FrameOwnerElementType::kIframe:
      return network::mojom::RequestDestination::kIframe;
    case blink::FrameOwnerElementType::kFrame:
      return network::mojom::RequestDestination::kFrame;
    // Main frames are handled above.
    case blink::FrameOwnerElementType::kNone:
      NOTREACHED();
    case blink::FrameOwnerElementType::kFencedframe:
      // Fenced frames are handled above.
      NOTREACHED();
  }
}

// Returns true if the parent's COEP policy `parent_coep` should block a child
// embedded in an <iframe> loaded with `child_coep` policy. The
// `is_credentialless` parameter reflects whether the child will be loaded as a
// credentialless document.
bool CoepBlockIframe(network::mojom::CrossOriginEmbedderPolicyValue parent_coep,
                     network::mojom::CrossOriginEmbedderPolicyValue child_coep,
                     bool is_credentialless) {
  return !is_credentialless &&
         (network::CompatibleWithCrossOriginIsolated(parent_coep) &&
          !network::CompatibleWithCrossOriginIsolated(child_coep));
}

// Computes the history offset of the new document compared to the current one.
int EstimateHistoryOffset(NavigationController& controller,
                          bool should_replace_current_entry) {
  if (should_replace_current_entry)
    return 0;

  int current_index = controller.GetLastCommittedEntryIndex();
  int pending_index = controller.GetPendingEntryIndex();

  // +1 for non history navigation.
  if (current_index == -1 || pending_index == -1)
    return 1;

  return pending_index - current_index;
}

bool IsDocumentToCommitAnonymous(FrameTreeNode* frame,
                                 bool is_synchronous_about_blank_navigation) {
  // FencedFrame do not propagate the credentialless bit deeper.
  // In particular, it means their future response will have to adhere to COEP.
  if (frame->IsFencedFrameRoot())
    return false;

  RenderFrameHostImpl* current_document = frame->current_frame_host();
  RenderFrameHostImpl* parent_document = frame->parent();

  // The synchronous about:blank navigation preserves the state of the initial
  // empty document.
  // TODO(https://github.com/whatwg/html/issues/6863): Remove the synchronous
  // about:blank navigation.
  if (is_synchronous_about_blank_navigation)
    return current_document->IsCredentialless();

  // The document to commit will be credentialless if either the iframe element
  // has the 'credentialless' attribute set or the parent document is
  // credentialless.
  bool parent_is_credentialless =
      parent_document && parent_document->IsCredentialless();
  return parent_is_credentialless || frame->Credentialless();
}

// Returns the "loading" URL in the renderer. This tries to replicate
// RenderFrameImpl::GetLoadingUrl(). This might return a different URL from
// what we get when calling GetLastCommittedURL() on `rfh`, in case the
// document had changed its URL through document.open() before, or
// when calling last_document_url_in_renderer(), in case of error pages and
// loadDataWithBaseURL documents.
// This function should only be used to preserve calculations that were
// previously done in the renderer but got moved to the browser (e.g. URL
// comparisons to determine if a navigation should do a replacement or not).
const GURL& GetLastLoadingURLInRendererForNavigationReplacement(
    RenderFrameHostImpl* rfh) {
  // Handle some special cases:
  // - The "loading URL" for an error page commit is the URL that it failed to
  // load. This will be retained as long as the document stays the same.
  // - For loadDataWithBaseURL() navigations the "loading URL" will be the
  // last committed URL (the data: URL). This will also be retained as long as
  // the document stays the same.
  if (rfh->IsErrorDocument() ||
      rfh->was_loaded_from_load_data_with_base_url()) {
    return rfh->GetLastCommittedURL();
  }

  // Otherwise, return the last document URL.
  return rfh->last_document_url_in_renderer();
}

bool IsOptedInFencedFrame(const net::HttpResponseHeaders& http_headers) {
  network::mojom::SupportsLoadingModePtr result =
      network::ParseSupportsLoadingMode(http_headers);
  return !result.is_null() &&
         std::ranges::contains(result->supported_modes,
                               network::mojom::LoadingMode::kFencedFrame);
}

// If there are any "Origin-Trial" headers on the |response|, persist those
// that correspond to persistent origin trials, provided the tokens are valid.
void PersistOriginTrialsFromHeaders(
    const url::Origin& origin,
    const url::Origin& partition_origin,
    const network::mojom::URLResponseHead* response,
    BrowserContext* browser_context,
    ukm::SourceId source_id) {
  // It is not possible to serialize opaque origins, so we cannot save any
  // information for them.
  if (origin.opaque())
    return;

  // Skip About:blank, about:srcdoc and a few other URLs, because they can't
  // have any Origin-Trial header.
  if (!response || !response->headers)
    return;

  OriginTrialsControllerDelegate* origin_trials_delegate =
      browser_context->GetOriginTrialsControllerDelegate();
  if (!origin_trials_delegate)
    return;

  std::vector<std::string> tokens =
      GetOriginTrialHeaderValues(response->headers.get());
  origin_trials_delegate->PersistTrialsFromTokens(
      origin, partition_origin, tokens, base::Time::Now(), source_id);
}

ukm::SourceId GetPageUkmSourceId(FrameTreeNode* frame_tree_node) {
  CHECK(frame_tree_node);
  RenderFrameHost* render_frame_host = frame_tree_node->current_frame_host();
  CHECK(render_frame_host);
  // Our data collection policy disallows collecting UKMs while prerendering.
  // So, return kInvalidSourceId when the page is in the prerendering state.
  // See //content/browser/preloading/prerender/README.md and ask the team to
  // explore options to record data for prerendering pages.
  if (render_frame_host->IsInLifecycleState(
          RenderFrameHost::LifecycleState::kPrerendering)) {
    return ukm::kInvalidSourceId;
  }
  return render_frame_host->GetPageUkmSourceId();
}

bool IsMhtmlMimeType(const std::string& mime_type) {
  return mime_type == "multipart/related" || mime_type == "message/rfc822";
}

std::optional<base::SafeRef<RenderFrameHostImpl>>
GetRenderFrameHostForBackForwardCacheRestore(FrameTreeNode* frame_tree_node,
                                             NavigationEntryImpl* entry) {
  if (!entry || !frame_tree_node->frame_tree().is_primary()) {
    return std::nullopt;
  }

  auto restored_entry = frame_tree_node->navigator()
                            .controller()
                            .GetBackForwardCache()
                            .GetOrEvictEntry(entry->GetUniqueID());
  if (!restored_entry.has_value()) {
    // If there is no active BFCache entry, we can't use the RFH from the
    // BFCache entry for the history navigation.
    return std::nullopt;
  }

  RenderFrameHostImpl* restored_rfh =
      restored_entry.value()->render_frame_host();

  // If there is an ongoing BFCache NavigationRequest with the same entry, that
  // NavigationRequest will be cancelled, and trigger an eviction from the
  // NavigationRequest destructor (see comment there for details). So, we can't
  // restore the to-be-evicted entry anymore.
  NavigationRequest* previous_navigation_request =
      frame_tree_node->navigation_request();
  if (previous_navigation_request &&
      previous_navigation_request->IsServedFromBackForwardCache() &&
      previous_navigation_request
              ->GetRenderFrameHostRestoredFromBackForwardCache() ==
          restored_rfh) {
    // Since the BFCache entry won't be restored, we evict it here with
    // `kNavigationCancelledWhileRestoring` so that the NavigationRequest
    // won't end up with not restored with no reason (or `Unknown` will be
    // added instead).
    // TODO(crbug.com/40283427): Only evict BFCache if the
    // `BackForwardCacheCommitDeferringCondition`, which unfreezes the
    // page and disables the eviction on the renderer side, is completed.
    restored_rfh->EvictFromBackForwardCacheWithReason(
        BackForwardCacheMetrics::NotRestoredReason::
            kNavigationCancelledWhileRestoring);
    return std::nullopt;
  }

  if (!frame_tree_node->IsMainFrame()) {
    // We have a matching BFCache entry for a subframe navigation. This
    // shouldn't happen as we should've triggered deletion of BFCache
    // entries that have the same BrowsingInstance as the current document.
    // See https://crbug.com/1250111.
    CaptureTraceForNavigationDebugScenario(
        DebugScenario::kDebugBackForwardCacheEntryExistsOnSubframeHistoryNav);
    return std::nullopt;
  }

  return restored_rfh->GetSafeRef();
}

void MaybePrewarmHttpDiskCache(
    BrowserContext& browser_context,
    const GURL& url,
    const std::optional<url::Origin>& initiator_origin) {
  if (!base::FeatureList::IsEnabled(
          blink::features::kHttpDiskCachePrewarming) ||
      !blink::features::kHttpDiskCachePrewarmingTriggerOnNavigation.Get()) {
    return;
  }

  if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
    return;
  }

  GetContentClient()->browser()->MaybePrewarmHttpDiskCache(
      browser_context, initiator_origin, url);
}

// Returns true in cases where an attempted download will end up replacing the
// current document anyway, due to showing an error page.
bool IsFailedDownload(bool is_download,
                      const net::HttpResponseHeaders* headers) {
  return is_download && headers &&
         !network::IsSuccessfulStatus(headers->response_code());
}

// Returns if the given `rfh` should be evicted from BackForwardCache due to
// ongoing navigation.
bool MaybeEvictFromBackForwardCacheBySubframeNavigation(
    RenderFrameHostImpl* rfh) {
  if (rfh->GetParentOrOuterDocument() &&
      rfh->GetLifecycleState() ==
          RenderFrameHost::LifecycleState::kInBackForwardCache) {
    // Normally, ongoing subframe navigations will be deferred by
    // `BackForwardCacheSubframeNavigationThrottle` before they reach this
    // point, if the page the subframe is on gets BFCached.
    //
    // However, it's possible for subframe navigations to end up here while its
    // page is BFCached, if at the time the navigation went through
    // `BackForwardCacheSubframeNavigationThrottle::WillStartRequest()` or
    // BackForwardCacheSubframeNavigationThrottle::WillCommitWithoutUrlLoader(),
    // the page is not BFCached yet, but then the page gets BFCached in between
    // that time and when this function is called.
    //
    // Outside of tests, this should not be possible, as
    // `BackForwardCacheSubframeNavigationThrottle` are the last throttles to be
    // registered/run. However, in tests, the last throttles to run are
    // test-only throttles, which can introduce an asynchronous step, making it
    // possible for the page to enter BFCache during that time. In that case, we
    // shouldn't continue processing the navigation in the subframe and need to
    // evict the page from BFCache.
    rfh->EvictFromBackForwardCacheWithReason(
        BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating);

    // DO NOT ADD CODE after this. The previous call has destroyed the
    // NavigationRequest.
    return true;
  }
  return false;
}

net::StorageAccessApiStatus ShouldLoadWithStorageAccess(
    const blink::mojom::BeginNavigationParams& begin_params,
    const blink::mojom::CommonNavigationParams& common_params,
    const RenderFrameHostImpl* previous_document_rfh,
    bool did_encounter_cross_origin_redirect,
    const GURL response_url,
    const network::mojom::URLResponseHead* response) {
  // Storage Access API Headers:
  // https://github.com/privacycg/storage-access-headers
  //
  // A server can opt-in to provide storage access to a document by setting the
  // `Activate-Storage-Access: load` header, provided that the user has already
  // granted the relevant `storage-access` permission.
  //
  // Note: As of today, `about:blank`, `about:srcdoc`, and MHTML-iframe do not
  // have a response.
  if (response && response->load_with_storage_access) {
    // TODO(https://crbug.com/344608182): this ought to use a dedicated status,
    // since the JS API was *not* used to get storage access here.
    return net::StorageAccessApiStatus::kAccessViaAPI;
  }

  // Storage Access API: https://privacycg.github.io/storage-access/#navigation
  //
  // If a document has storage access, and initiates a same-origin navigation in
  // the same frame toward a document from the same origin, the `has storage
  // access` bit is inherited.
  if (!previous_document_rfh->document_associated_data()
           .cookie_setting_overrides()
           .Has(net::CookieSettingOverride::kStorageAccessGrantEligible)) {
    // Frame was missing the grant eligible override, so there's no access to
    // carry over.
    return net::StorageAccessApiStatus::kNone;
  }
  if (begin_params.initiator_frame_token !=
      previous_document_rfh->GetFrameToken()) {
    // Navigation was not self-initiated.
    return net::StorageAccessApiStatus::kNone;
  }
  if (!previous_document_rfh->GetLastCommittedOrigin().IsSameOriginWith(
          response_url) ||
      did_encounter_cross_origin_redirect) {
    // Navigation is not fully same-origin.
    return net::StorageAccessApiStatus::kNone;
  }
  return net::StorageAccessApiStatus::kAccessViaAPI;
}

const char* BeforeUnloadExecutionModeToString(
    NavigationHandle::BeforeUnloadExecutionMode mode) {
  switch (mode) {
    case NavigationHandle::BeforeUnloadExecutionMode::kNotBlocked:
      return "NotBlocked";
    case NavigationHandle::BeforeUnloadExecutionMode::kForLegacy:
      return "ForLegacy";
    case NavigationHandle::BeforeUnloadExecutionMode::kSync:
      return "Sync";
    case NavigationHandle::BeforeUnloadExecutionMode::kAsync:
      return "Async";
  }
}

// The default sampling rate for UKM.
constexpr double kDefaultUkmSamplingRate = 0.001;

double GetUkmSamplingRate() {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kForceNavigationTimelineUkmRecordingForTesting)) {
    return 1.0;
  }
  return kDefaultUkmSamplingRate;
}

// Builds the final `EmbedderIsolationInfo` for a navigation. `kPdf` is
// preserved as-is so the PDF renderer carve-out is never lost.
// `kUniqueInstance` mints a fresh per-instance id from `navigation_id`, which
// is unique per NavigationRequest. Otherwise, a unique-instance EII is
// inherited from the parent SiteInstance when present, so descendants of a
// unique-instance frame stay in the same isolation domain.
EmbedderIsolationInfo ResolveEmbedderIsolationInfo(
    FrameTreeNode* frame_tree_node,
    EmbedderIsolationInfo::Mode mode,
    int64_t navigation_id) {
  // Resolve kPdf and kUniqueInstance first, before the privileged mode below.
  // If content that needs one of these modes (e.g. an embedded PDF) is hosted
  // in a privileged WebContents, it must keep its own process and mitigations
  // rather than becoming privileged -- the two imply opposite things (e.g. PDF
  // requires a JIT-less process, privileged does not), and PDF's must win.
  if (mode == EmbedderIsolationInfo::Mode::kPdf) {
    return EmbedderIsolationInfo::CreateForPdf();
  }
  if (mode == EmbedderIsolationInfo::Mode::kUniqueInstance) {
    return EmbedderIsolationInfo::CreateForUniqueInstance(navigation_id);
  }

  // A privileged WebContents (see //chrome's PrivilegedWebContents) marks every
  // frame it hosts as privileged, keyed on its constant feature id. Derive this
  // from the WebContents' immutable creation-time marker (reached via the
  // FrameTree delegate) rather than the passed-in `mode`. This prevents a
  // security downgrade: a renderer-initiated navigation requests `kNone` by
  // default, so keying off `mode` would let a privileged frame silently drop
  // its privileged isolation and be placed into an ordinary renderer process
  // shared with a normal tab, exposing its elevated browser capabilities to
  // untrusted code or extensions. Same-site frames then resolve to the same
  // SiteInfo and may share a process; cross-site frames get their own
  // privileged process and never share with ordinary (kNone) content.
  if (std::optional<int64_t> feature_id =
          frame_tree_node->frame_tree()
              .delegate()
              ->GetPrivilegedContentsFeatureId()) {
    return EmbedderIsolationInfo::CreateForPrivileged(*feature_id);
  }

  // Inherit a unique-instance or privileged ancestor's info when present so
  // that a descendant frame -- including the root of an inner frame tree such
  // as a fenced frame, reached via GetParentOrOuterDocument() -- stays in its
  // outer document's isolation domain.
  if (RenderFrameHostImpl* parent =
          frame_tree_node->GetParentOrOuterDocument()) {
    const EmbedderIsolationInfo& parent_info =
        parent->GetSiteInstance()->GetSiteInfo().embedder_isolation_info();
    if (parent_info.is_unique_instance() || parent_info.is_privileged()) {
      return parent_info;
    }
  }
  return EmbedderIsolationInfo::CreateNone();
}

}  // namespace

NavigationRequest::PrerenderActivationNavigationState::
    PrerenderActivationNavigationState() = default;
NavigationRequest::PrerenderActivationNavigationState::
    ~PrerenderActivationNavigationState() = default;

// static
std::unique_ptr<NavigationRequest> NavigationRequest::CreateBrowserInitiated(
    FrameTreeNode* frame_tree_node,
    blink::mojom::CommonNavigationParamsPtr common_params,
    blink::mojom::CommitNavigationParamsPtr commit_params,
    bool was_opener_suppressed,
    const std::string& extra_headers,
    FrameNavigationEntry* frame_entry,
    NavigationEntryImpl* entry,
    bool is_form_submission,
    std::unique_ptr<NavigationUIData> navigation_ui_data,
    EmbedderIsolationInfo::Mode embedder_isolation_mode,
    bool is_embedder_initiated_fenced_frame_navigation) {
  auto request = Create(
      frame_tree_node, std::move(common_params), std::move(commit_params),
      /*browser_initiated=*/true, was_opener_suppressed,
      std::nullopt /* initiator_frame_token */,
      ChildProcessId() /* initiator_process_id */,
      nullptr /* initiator_navigation_state */,
      false /* should_ignore_initiator_policies_for_inheritance */,
      extra_headers, frame_entry, entry, is_form_submission,
      std::move(navigation_ui_data),
      /*started_with_transient_activation=*/false,
      /*started_by_ad=*/false, embedder_isolation_mode,
      is_embedder_initiated_fenced_frame_navigation,
      /*is_container_initiated=*/false, /*has_rel_opener=*/false);
  // It is only possible for a null NavigationRequest to be returned if an
  // initiator_frame_token is provided.
  CHECK(request);
  return request;
}

// static
std::unique_ptr<NavigationRequest> NavigationRequest::Create(
    FrameTreeNode* frame_tree_node,
    blink::mojom::CommonNavigationParamsPtr common_params,
    blink::mojom::CommitNavigationParamsPtr commit_params,
    bool browser_initiated,
    bool was_opener_suppressed,
    const std::optional<blink::LocalFrameToken>& initiator_frame_token,
    ChildProcessId initiator_process_id,
    scoped_refptr<InitiatorNavigationState> initiator_navigation_state,
    bool should_ignore_initiator_policies_for_inheritance,
    const std::string& extra_headers,
    FrameNavigationEntry* frame_entry,
    NavigationEntryImpl* entry,
    bool is_form_submission,
    std::unique_ptr<NavigationUIData> navigation_ui_data,
    bool started_with_transient_activation,
    bool started_by_ad,
    EmbedderIsolationInfo::Mode embedder_isolation_mode,
    bool is_embedder_initiated_fenced_frame_navigation,
    bool is_container_initiated,
    bool has_rel_opener) {
  TRACE_EVENT("navigation", "NavigationRequest::Create", "browser_initiated",
              browser_initiated);

  common_params->request_destination =
      GetDestinationFromFrameTreeNode(frame_tree_node);

  // Note: we pass std::nullopt as `initiator_state_token` and
  // `initiator_document_token` below as all initiator relevant data has already
  // been retrieved and is in the `initiator_navigation_state`.
  auto navigation_params = blink::mojom::BeginNavigationParams::New(
      initiator_frame_token, std::nullopt /* initiator_state_token */,
      std::nullopt /* initiator_document_token*/, extra_headers,
      net::LOAD_NORMAL, false /* skip_service_worker */,
      blink::mojom::RequestContextType::LOCATION,
      blink::mojom::MixedContentContextType::kBlockable, is_form_submission,
      false /* was_initiated_by_link_click */,
      blink::mojom::ForceHistoryPush::kNo, GURL() /* searchable_form_url */,
      std::string() /* searchable_form_encoding */,
      GURL() /* client_side_redirect_url */,
      std::nullopt /* devtools_initiator_info */,
      nullptr /* trust_token_params */,
      base::TimeTicks() /* renderer_before_unload_start */,
      base::TimeTicks() /* renderer_before_unload_end */,
      base::TimeTicks() /* before_unload_dialog_opened */,
      base::TimeTicks() /* before_unload_dialog_closed */,
      started_with_transient_activation, started_by_ad, is_container_initiated,
      has_rel_opener, std::nullopt /* script_tool_invocation_id */);

  // Shift-Reload forces bypassing caches and service workers.
  if (common_params->navigation_type ==
      blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE) {
    navigation_params->load_flags |= net::LOAD_BYPASS_CACHE;
    navigation_params->skip_service_worker = true;
  }

  scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory;
  if (frame_entry) {
    blob_url_loader_factory = frame_entry->blob_url_loader_factory();

    if (common_params->url.SchemeIsBlob() && !blob_url_loader_factory) {
      // If this navigation entry came from session history then the blob
      // factory would have been cleared in
      // NavigationEntryImpl::ResetForCommit(). This is avoid keeping large
      // blobs alive unnecessarily and the spec is unclear. So create a new blob
      // factory which will work if the blob happens to still be alive,
      // resolving the blob URL in the site instance it was loaded in.
      blob_url_loader_factory =
          ChromeBlobStorageContext::URLLoaderFactoryForUrl(
              frame_tree_node->navigator()
                  .controller()
                  .GetBrowserContext()
                  ->GetStoragePartition(frame_entry->site_instance()),
              common_params->url);
    }
  }

  std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest(
      frame_tree_node, std::move(common_params), std::move(navigation_params),
      std::move(commit_params), browser_initiated,
      false /* from_begin_navigation */,
      false /* is_synchronous_renderer_commit */, frame_entry, entry,
      std::move(navigation_ui_data), std::move(blob_url_loader_factory),
      mojo::NullAssociatedRemote(),
      nullptr /* prefetched_signed_exchange_cache */,
      GetRenderFrameHostForBackForwardCacheRestore(frame_tree_node, entry),
      initiator_process_id, initiator_navigation_state,
      should_ignore_initiator_policies_for_inheritance, was_opener_suppressed,
      embedder_isolation_mode, is_embedder_initiated_fenced_frame_navigation,
      mojo::NullReceiver() /* renderer_cancellation_listener */,
      mojo::NullReceiver() /* renderer_ignore_duplicate_navigation_listener */,
      mojo::NullReceiver() /* deferred_commit_resume_listener */));

  return navigation_request;
}

// static
std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated(
    FrameTreeNode* frame_tree_node,
    NavigationEntryImpl* entry,
    blink::mojom::CommonNavigationParamsPtr common_params,
    blink::mojom::BeginNavigationParamsPtr begin_params,
    int current_history_list_index,
    int current_history_list_length,
    bool override_user_agent,
    scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
    mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
    scoped_refptr<PrefetchedSignedExchangeCache>
        prefetched_signed_exchange_cache,
    mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
        renderer_cancellation_listener,
    mojo::PendingReceiver<
        mojom::NavigationRendererIgnoreDuplicateNavigationListener>
        renderer_ignore_duplicate_navigation_listener,
    mojo::PendingReceiver<blink::mojom::NavigationResumeDeferredCommitListener>
        deferred_commit_resume_listener,
    scoped_refptr<InitiatorNavigationState> initiator_navigation_state) {
  TRACE_EVENT("navigation", "NavigationRequest::CreateRendererInitiated");
  // Only normal navigations to a different document or reloads are expected.
  // - Renderer-initiated same document navigations never start in the browser.
  // - Restore-navigations are always browser-initiated.
  // - History-navigations use the browser-initiated path, even the ones that
  //   are initiated by a javascript script.
  CHECK(NavigationTypeUtils::IsReload(common_params->navigation_type) ||
        common_params->navigation_type ==
            blink::mojom::NavigationType::DIFFERENT_DOCUMENT);

  common_params->request_destination =
      GetDestinationFromFrameTreeNode(frame_tree_node);

  GURL original_url = common_params->url;
  if (base::FeatureList::IsEnabled(
          features::kSanitizeOriginalUrlDuringNavigation)) {
    // It is safe to convert GURL to an Origin and back in the code below
    // because we only want to discard the rest of the URL (e.g., path and
    // params). The actual underlying Origin is not needed, which could be
    // inherited or opaque in sandbox cases.
    original_url = common_params->url.DeprecatedGetOriginAsURL();
  }

  // TODO(clamy): See if the navigation start time should be measured in the
  // renderer and sent to the browser instead of being measured here.
  blink::mojom::CommitNavigationParamsPtr commit_params =
      blink::mojom::CommitNavigationParams::New(
          url::Origin(),
          // The correct storage key will be computed before committing the
          // navigation.
          blink::StorageKey(), override_user_agent,
          /*redirects=*/std::vector<GURL>(),
          /*redirect_params=*/
          std::vector<blink::mojom::NavigationRedirectParamsPtr>(),
          /*post_content_type=*/std::string(), original_url,
          common_params->method,
          /*can_load_local_resources=*/false,
          /*page_state=*/std::string(),
          /*nav_entry_id=*/0,
          /*subframe_unique_names=*/base::flat_map<std::string, bool>(),
          /*intended_as_new_entry=*/false,
          // Set to -1 because history-navigations do not use this path. See
          // comments above.
          /*pending_history_list_index=*/-1, current_history_list_index,
          current_history_list_length,
          /*was_discarded=*/false,
          /*is_view_source=*/false,
          /*should_clear_history_list=*/false,
          /*navigation_timing=*/blink::mojom::NavigationTiming::New(),
          blink::mojom::WasActivatedOption::kUnknown,
          /*navigation_token=*/base::UnguessableToken::Create(),
          /*prefetched_signed_exchanges=*/
          std::vector<blink::mojom::PrefetchedSignedExchangeInfoPtr>(),
#if BUILDFLAG(IS_ANDROID)
          /*data_url_as_string=*/std::string(),
#endif
          /*is_browser_initiated=*/false,
          /*has_ua_visual_transition*/ false,
          /*document_ukm_source_id=*/ukm::kInvalidSourceId,
          frame_tree_node->pending_frame_policy(),
          /*force_enabled_origin_trials=*/std::vector<std::string>(),
          blink::mojom::AgentClusterKey::NewSiteKey(GURL()),
          /*enabled_client_hints=*/
          std::vector<network::mojom::WebClientHintsType>(),
          /*is_cross_site_cross_browsing_context_group=*/false,
          /*should_have_sticky_user_activation=*/false,
          /*old_page_info=*/nullptr, /*http_response_code=*/-1,
          blink::mojom::NavigationApiHistoryEntryArrays::New(),
          /*early_hints_preloaded_resources=*/
          std::vector<network::mojom::LinkHeaderPtr>(),
          /*early_hints_preconnects=*/
          std::vector<network::mojom::LinkHeaderPtr>(),
          /*navigation_preconnects=*/
          std::vector<network::mojom::LinkHeaderPtr>(),
          // This timestamp will be populated when the commit IPC is sent.
          /*commit_sent=*/base::TimeTicks(), /*srcdoc_value=*/std::string(),
          /*should_load_data_url=*/false,
          /*ancestor_or_self_has_cspee=*/
          frame_tree_node->AncestorOrSelfHasCSPEE(),
          /*reduced_accept_language=*/std::string(),
          /*navigation_delivery_type=*/
          network::mojom::NavigationDeliveryType::kDefault,
          /*view_transition_state=*/std::nullopt,
          /*soft_navigation_heuristics_task_id=*/std::nullopt,
          /*modified_runtime_features=*/
          base::flat_map<::blink::mojom::RuntimeFeature, bool>(),
          /*fenced_frame_properties=*/std::nullopt,
          /*not_restored_reasons=*/nullptr,
          /*load_with_storage_access=*/
          net::StorageAccessApiStatus::kNone,
          /*browsing_context_group_info=*/std::nullopt,
          /*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
          /*visited_link_salt=*/std::nullopt,
          /*local_surface_id=*/std::nullopt,
          frame_tree_node->current_frame_host()->GetCachedPermissionStatuses(),
          /*should_skip_screenshot=*/false,
          /*force_new_document_sequence_number=*/false,
          /*navigation_metrics_token=*/base::UnguessableToken::Create(),
          /*commit_target_frame_token=*/std::nullopt,
          /*is_initial_webui=*/false,
          /*isolated_app_policy=*/std::nullopt,
          /*internal_scroll_to_text_fragment=*/std::nullopt,
          /*is_secure_context_root=*/false,
          blink::mojom::ScriptInjectionPolicy::kNone);
#if !BUILDFLAG(IS_ANDROID)
  CHECK(!GetContentClient()->browser()->IsInitialWebUIURL(common_params->url));
#endif

  // CreateRendererInitiated() should only be triggered when the navigation is
  // initiated by a frame in the same process.
  // TODO(crbug.com/40686861): Find a way to CHECK that the routing ID
  // is from the current RFH.
  ChildProcessId initiator_process_id =
      frame_tree_node->current_frame_host()->GetProcess()->GetID();

  // `was_opener_suppressed` can be true for renderer initiated navigations, but
  // only in cases which get routed through `CreateBrowserInitiated()` instead.
  std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest(
      frame_tree_node, std::move(common_params), std::move(begin_params),
      std::move(commit_params),
      false,    // browser_initiated
      true,     // from_begin_navigation
      false,    // is_synchronous_renderer_commit
      nullptr,  // frame_entry
      entry,
      nullptr,  // navigation_ui_data
      std::move(blob_url_loader_factory), std::move(navigation_client),
      std::move(prefetched_signed_exchange_cache),
      std::nullopt,  // rfh_restored_from_back_forward_cache
      initiator_process_id, initiator_navigation_state,
      /*should_ignore_initiator_policies_for_inheritance=*/false,
      /*was_opener_suppressed=*/false, EmbedderIsolationInfo::Mode::kNone,
      /*is_embedder_initiated_fenced_frame_navigation=*/false,
      std::move(renderer_cancellation_listener),
      std::move(renderer_ignore_duplicate_navigation_listener),
      std::move(deferred_commit_resume_listener)));

  return navigation_request;
}

// static
std::unique_ptr<NavigationRequest>
NavigationRequest::CreateForSynchronousRendererCommit(
    FrameTreeNode* frame_tree_node,
    RenderFrameHostImpl* render_frame_host,
    bool is_same_document,
    const GURL& url,
    const url::Origin& origin,
    const std::optional<url::Origin>& initiator_origin,
    const std::optional<GURL>& initiator_base_url,
    const net::IsolationInfo& isolation_info_for_subresources,
    blink::mojom::ReferrerPtr referrer,
    const ui::PageTransition& transition,
    bool should_replace_current_entry,
    const std::string& method,
    bool has_transient_activation,
    bool is_overriding_user_agent,
    const std::vector<GURL>& redirects,
    const GURL& original_url,
    std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter,
    std::unique_ptr<DocumentIsolationPolicyReporter> dip_reporter,
    int http_response_code,
    base::TimeTicks actual_navigation_start) {
  TRACE_EVENT("navigation",
              "NavigationRequest::CreateForSynchronousRendererCommit");
  // TODO(clamy): Improve the *NavigationParams and *CommitParams to avoid
  // copying so many parameters here.
  blink::mojom::CommonNavigationParamsPtr common_params =
      blink::mojom::CommonNavigationParams::New(
          url, initiator_origin, initiator_base_url, std::move(referrer),
          transition,
          is_same_document ? blink::mojom::NavigationType::SAME_DOCUMENT
                           : blink::mojom::NavigationType::DIFFERENT_DOCUMENT,
          blink::NavigationDownloadPolicy(), should_replace_current_entry,
          GURL() /* base_url_for_data_url*/, actual_navigation_start,
          base::TimeTicks::Now() /* navigation_start */, method /* method */,
          nullptr /* post_data */, network::mojom::SourceLocation::New(),
          false /* started_from_context_menu */, has_transient_activation,
          false /* has_text_fragment_token */,
          network::mojom::CSPDisposition::CHECK,
          std::vector<int>() /* initiator_origin_trial_features */,
          std::string() /* href_translate */,
          false /* is_history_navigation_in_new_child_frame */,
          base::TimeTicks() /* input_start */,
          network::mojom::RequestDestination::kEmpty);
  // Note that some params are set to default values (e.g. page_state set to
  // the default blink::PageState()) even if the DidCommit message that came
  // from the renderer contained relevant info that can be used to fill the
  // params, because setting those values don't match with the pattern used
  // by navigations that went through the browser (e.g. page_state is only
  // set in CommitNavigationParams of history navigations) or these values are
  // not used by the browser after commit.
  // It is safe to convert GURL to an Origin and back in the code below because
  // we only want to discard the rest of the URL (e.g., path and params). The
  // actual underlying Origin is not needed, which could be inherited or opaque
  // in sandbox cases.
  const GURL original_url_for_renderer =
      base::FeatureList::IsEnabled(
          features::kSanitizeOriginalUrlDuringNavigation)
          ? original_url.DeprecatedGetOriginAsURL()
          : original_url;
  blink::mojom::CommitNavigationParamsPtr commit_params =
      blink::mojom::CommitNavigationParams::New(
          url::Origin(),
          // The correct storage key is computed right after creating the
          // NavigationRequest below.
          blink::StorageKey(), is_overriding_user_agent, redirects,
          /*redirect_params=*/
          std::vector<blink::mojom::NavigationRedirectParamsPtr>(),
          /*post_content_type=*/std::string(), original_url_for_renderer,
          /*original_method=*/method,
          /*can_load_local_resources=*/false,
          /*page_state=*/std::string(),
          /*nav_entry_id=*/0,
          /*subframe_unique_names=*/base::flat_map<std::string, bool>(),
          /*intended_as_new_entry=*/false,
          /*pending_history_list_index=*/-1,
          /*current_history_list_index=*/-1,
          /*current_history_list_length=*/-1,
          /*was_discarded=*/false,
          /*is_view_source=*/false,
          /*should_clear_history_list=*/false,
          /*navigation_timing=*/blink::mojom::NavigationTiming::New(),
          blink::mojom::WasActivatedOption::kUnknown,
          /*navigation_token=*/base::UnguessableToken::Create(),
          /*prefetched_signed_exchanges=*/
          std::vector<blink::mojom::PrefetchedSignedExchangeInfoPtr>(),
#if BUILDFLAG(IS_ANDROID)
          /*data_url_as_string=*/std::string(),
#endif
          /*is_browser_initiated=*/false,
          /*has_ua_visual_transition*/ false,
          /*document_ukm_source_id=*/ukm::kInvalidSourceId,
          frame_tree_node->pending_frame_policy(),
          /*force_enabled_origin_trials=*/std::vector<std::string>(),
          blink::mojom::AgentClusterKey::NewSiteKey(GURL()),
          /*enabled_client_hints=*/
          std::vector<network::mojom::WebClientHintsType>(),
          /*is_cross_site_cross_browsing_context_group=*/false,
          /*should_have_sticky_user_activation=*/false,
          /*old_page_info=*/nullptr, http_response_code,
          blink::mojom::NavigationApiHistoryEntryArrays::New(),
          /*early_hints_preloaded_resources=*/
          std::vector<network::mojom::LinkHeaderPtr>(),
          /*early_hints_preconnects=*/
          std::vector<network::mojom::LinkHeaderPtr>(),
          /*navigation_preconnects=*/
          std::vector<network::mojom::LinkHeaderPtr>(),
          // This timestamp will be populated when the commit IPC is sent.
          /*commit_sent=*/base::TimeTicks(), /*srcdoc_value=*/std::string(),
          /*should_load_data_url=*/false,
          /*ancestor_or_self_has_cspee=*/
          frame_tree_node->AncestorOrSelfHasCSPEE(),
          /*reduced_accept_language=*/std::string(),
          /*navigation_delivery_type=*/
          network::mojom::NavigationDeliveryType::kDefault,
          /*view_transition_state=*/std::nullopt,
          /*soft_navigation_heuristics_task_id=*/std::nullopt,
          /*modified_runtime_features=*/
          base::flat_map<::blink::mojom::RuntimeFeature, bool>(),
          /*fenced_frame_properties=*/std::nullopt,
          /*not_restored_reasons=*/nullptr,
          /*load_with_storage_access=*/
          net::StorageAccessApiStatus::kNone,
          /*browsing_context_group_info=*/std::nullopt,
          /*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
          /*visited_link_salt=*/std::nullopt,
          /*local_surface_id=*/std::nullopt,
          render_frame_host->GetCachedPermissionStatuses(),
          /*should_skip_screenshot=*/false,
          /*force_new_document_sequence_number=*/false,
          /*navigation_metrics_token=*/base::UnguessableToken::Create(),
          /*commit_target_frame_token=*/std::nullopt,
          /*is_initial_webui=*/false,
          /*isolated_app_policy=*/std::nullopt,
          /*internal_scroll_to_text_fragment=*/std::nullopt,
          /*is_secure_context_root=*/false,
          blink::mojom::ScriptInjectionPolicy::kNone);
  blink::mojom::BeginNavigationParamsPtr begin_params =
      blink::mojom::BeginNavigationParams::New();
  std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest(
      frame_tree_node, std::move(common_params), std::move(begin_params),
      std::move(commit_params), false /* browser_initiated */,
      false /* from_begin_navigation */,
      true /* is_synchronous_renderer_commit */,
      nullptr /* frame_navigation_entry */, nullptr /* navigation_entry */,
      nullptr /* navigation_ui_data */, nullptr /* blob_url_loader_factory */,
      mojo::NullAssociatedRemote(),
      nullptr /* prefetched_signed_exchange_cache */,
      std::nullopt /* rfh_restored_from_back_forward_cache */,
      ChildProcessId() /* initiator_process_id */,
      nullptr /* initiator_navigation_state */,
      false /* should_ignore_initiator_policies_for_inheritance */,
      false /* was_opener_suppressed */, EmbedderIsolationInfo::Mode::kNone));

  std::optional<base::UnguessableToken> nonce = render_frame_host->ComputeNonce(
      navigation_request->is_credentialless(),
      navigation_request->ComputeFencedFrameNonce());
  url::Origin top_level_origin =
      render_frame_host->ComputeTopFrameOrigin(origin);
  if (nonce) {
    // If the nonce isn't null, we can use the simpler form of the constructor.
    navigation_request->commit_params_->storage_key =
        blink::StorageKey::CreateWithNonce(origin, *nonce);
  } else {
    // Otherwise we need to derive the top_level_site and ancestor_chain_bit.
    net::SchemefulSite top_level_site(top_level_origin);

    blink::mojom::AncestorChainBit ancestor_chain_bit =
        blink::mojom::AncestorChainBit::kSameSite;
    if (render_frame_host->ComputeSiteForCookies().IsNull() ||
        !top_level_site.IsSameSiteWith(origin) || !top_level_site.opaque() ||
        origin.opaque()) {
      ancestor_chain_bit = blink::mojom::AncestorChainBit::kCrossSite;
    }

    navigation_request->commit_params_->storage_key =
        blink::StorageKey::Create(origin, top_level_site, ancestor_chain_bit);
  }
  navigation_request->render_frame_host_ = render_frame_host->GetSafeRef();
  navigation_request->coep_reporter_ = std::move(coep_reporter);
  navigation_request->dip_reporter_ = std::move(dip_reporter);
  navigation_request->isolation_info_for_subresources_ =
      isolation_info_for_subresources;
  navigation_request->associated_rfh_type_ =
      AssociatedRenderFrameHostType::CURRENT;
  navigation_request->StartNavigation();
  CHECK(navigation_request->IsNavigationStarted());

  return navigation_request;
}

// static class variable used to generate unique navigation ids for
// NavigationRequest.
int64_t NavigationRequest::unique_id_counter_ = 0;

NavigationRequest::NavigationRequest(
    FrameTreeNode* frame_tree_node,
    blink::mojom::CommonNavigationParamsPtr common_params,
    blink::mojom::BeginNavigationParamsPtr begin_params,
    blink::mojom::CommitNavigationParamsPtr commit_params,
    bool browser_initiated,
    bool from_begin_navigation,
    bool is_synchronous_renderer_commit,
    const FrameNavigationEntry* frame_entry,
    NavigationEntryImpl* entry,
    std::unique_ptr<NavigationUIData> navigation_ui_data,
    scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
    mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
    scoped_refptr<PrefetchedSignedExchangeCache>
        prefetched_signed_exchange_cache,
    std::optional<base::SafeRef<RenderFrameHostImpl>>
        rfh_restored_from_back_forward_cache,
    ChildProcessId initiator_process_id,
    scoped_refptr<InitiatorNavigationState> initiator_navigation_state,
    bool should_ignore_initiator_policies_for_inheritance,
    bool was_opener_suppressed,
    EmbedderIsolationInfo::Mode embedder_isolation_mode,
    bool is_embedder_initiated_fenced_frame_navigation,
    mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
        renderer_cancellation_listener,
    mojo::PendingReceiver<
        mojom::NavigationRendererIgnoreDuplicateNavigationListener>
        renderer_ignore_duplicate_navigation_listener,
    mojo::PendingReceiver<blink::mojom::NavigationResumeDeferredCommitListener>
        deferred_commit_resume_listener)
    : frame_tree_node_(frame_tree_node),
      is_synchronous_renderer_commit_(is_synchronous_renderer_commit),
      common_params_(std::move(common_params)),
      begin_params_(std::move(begin_params)),
      commit_params_(std::move(commit_params)),
      navigation_ui_data_(std::move(navigation_ui_data)),
      blob_url_loader_factory_(std::move(blob_url_loader_factory)),
      restore_type_(entry ? entry->restore_type() : RestoreType::kNotRestored),
      // Some navigations, such as renderer-initiated subframe navigations,
      // won't have a NavigationEntryImpl. Set |reload_type_| if applicable
      // for them.
      reload_type_(
          entry ? entry->reload_type()
                : NavigationTypeToReloadType(common_params_->navigation_type)),
      nav_entry_id_(entry ? entry->GetUniqueID() : 0),
      from_begin_navigation_(from_begin_navigation),
      site_info_(
          frame_tree_node_->navigator().controller().GetBrowserContext()),
      navigation_entry_offset_(
          EstimateHistoryOffset(frame_tree_node_->navigator().controller(),
                                common_params_->should_replace_current_entry)),
      prefetched_signed_exchange_cache_(
          std::move(prefetched_signed_exchange_cache)),
      navigation_trace_track_(
          "NavigationRequest",
          navigation_id_,
          frame_tree_node->current_frame_host()->GetTracingTrack()),
      breakdown_trace_track_(
          "NavigationRequest:Breakdown",
          navigation_id_,
          frame_tree_node->current_frame_host()->GetTracingTrack()),
      async_before_unload_trace_track_(
          "NavigationRequest:AsyncBeforeUnload",
          navigation_id_,
          frame_tree_node->current_frame_host()->GetTracingTrack()),
      rfh_restored_from_back_forward_cache_(
          rfh_restored_from_back_forward_cache),
      is_back_forward_cache_restore_(
          rfh_restored_from_back_forward_cache.has_value()),
      // Store the old RenderFrameHost id at request creation to be used later.
      current_render_frame_host_id_at_construction_(
          frame_tree_node->current_frame_host()->GetGlobalId()),
      initiator_frame_token_(begin_params_->initiator_frame_token),
      initiator_process_id_(initiator_process_id),
      was_opener_suppressed_(was_opener_suppressed),
      is_credentialless_(
          IsDocumentToCommitAnonymous(frame_tree_node,
                                      is_synchronous_renderer_commit)),
      previous_page_ukm_source_id_(GetPageUkmSourceId(frame_tree_node_)),
      cookie_observers_(std::make_unique<CookieAccessObservers>(
          base::BindRepeating(&NavigationRequest::NotifyCookiesAccessed,
                              base::Unretained(this)))),
      embedder_isolation_info_(
          ResolveEmbedderIsolationInfo(frame_tree_node,
                                       embedder_isolation_mode,
                                       navigation_id_)),
      is_embedder_initiated_fenced_frame_navigation_(
          is_embedder_initiated_fenced_frame_navigation),
      fenced_frame_properties_(
          is_embedder_initiated_fenced_frame_navigation
              ? std::make_optional(FencedFrameProperties(common_params_->url))
              : std::nullopt),
      request_method_(common_params_->method),
      original_url_(common_params_->url),
      prerender_host_id_(
          GetPrerenderHostRegistry().GetPrerenderHostIdForNavigation(this)),
      initiator_navigation_state_(initiator_navigation_state),
      should_ignore_initiator_policies_for_inheritance_(
          should_ignore_initiator_policies_for_inheritance),
      initiator_state_token_to_commit_(base::UnguessableToken::Create()) {
  TRACE_EVENT("navigation", "NavigationRequest::NavigationRequest",
              perfetto::Flow::FromPointer(this),
              perfetto::protos::pbzero::ChromeTrackEvent::kNavigation, this);
  TRACE_EVENT_BEGIN("navigation", "StartToCommit", breakdown_trace_track_,
                    common_params_->navigation_start);
  CHECK(!common_params_->initiator_base_url ||
        !common_params_->initiator_base_url->is_empty());
  CHECK(!blink::IsRendererDebugURL(common_params_->url));
  CHECK(common_params_->method == "POST" || !common_params_->post_data);
  CHECK_EQ(common_params_->url, original_url_);
  if (base::FeatureList::IsEnabled(
          features::kSanitizeOriginalUrlDuringNavigation)) {
    // TODO(crbug.com/495463654): We need to get the origin of the original_url
    // here because the NavigationRequestTest helpers directly set original_url
    // without triggering paths that may sanitize it. This will no longer be an
    // issue once original_url is converted to be an original_origin.
    // TODO(523555340): CHECK-exclusion: Convert to a CHECK once we are
    // confident it won't be triggered.
    DCHECK_EQ(common_params_->url.DeprecatedGetOriginAsURL(),
              commit_params_->original_url.DeprecatedGetOriginAsURL());
  } else {
    // TODO(523555340): CHECK-exclusion: Convert to a CHECK once we are
    // confident it won't be triggered.
    DCHECK_EQ(common_params_->url, commit_params_->original_url);
  }
  // Navigations generally can't be both a replacement and a reload, except
  // when reloading on the initial entry before it has been replaced by the
  // first real navigation.
  NavigationEntryImpl* last_committed_entry =
      frame_tree_node_->navigator().controller().GetLastCommittedEntry();
  bool is_initial_nav_entry = frame_tree_node_->IsMainFrame() &&
                              last_committed_entry &&
                              last_committed_entry->IsInitialEntry();
  CHECK(!common_params_->should_replace_current_entry ||
        !NavigationTypeUtils::IsReload(common_params_->navigation_type) ||
        is_initial_nav_entry);
  CHECK(IsInOutermostMainFrame() ||
        common_params_->base_url_for_data_url.is_empty());
  // TODO(crbug.com/510258191): Check that |initiator_navigation_state| is not
  // null for renderer-initiated navigations.
#if BUILDFLAG(IS_ANDROID)
  CHECK(IsInOutermostMainFrame() || commit_params_->data_url_as_string.empty());
#endif
  CheckSoftNavigationHeuristicsInvariants();
  ScopedCrashKeys crash_keys(*this);

#if !BUILDFLAG(IS_ANDROID)
  // It should not be possible to navigate away from the initial WebUI page,
  // except when recovering from a crash. We allow navigating away to
  // about:blank if `kDebugTopChromeWebUI` is enabled so that DevTools can
  // reload.
  bool current_rfh_is_initial_webui =
      GetContentClient()->browser()->IsInitialWebUIURL(
          frame_tree_node_->current_frame_host()
              ->GetSiteInstance()
              ->GetSecurityPrincipal()
              .GetDeprecatedSiteURL());
  if (current_rfh_is_initial_webui && !IsInitialWebUINavigation() &&
      !(base::FeatureList::IsEnabled(features::kDebugTopChromeWebUI) &&
        GetURL().IsAboutBlank())) {
    // TODO(crbug.com/511971445): Turn this into a CHECK again once
    // investigation on what can cause this is finished.
    base::debug::DumpWithoutCrashing();
    // We should not allow navigating away from initial WebUI. Mark this
    // request for cancellation in BeginNavigation().
    should_cancel_on_leaving_initial_webui_ = true;
  }
  if (IsInitialWebUINavigation()) {
    // Initial WebUI navigations must satisfy all these conditions
    // - Is browser initiated
    // - Occur on the outermost main frame
    // - Have not navigated before, or is navigating away from the initial
    // WebUI in the cases mentioned above.
    // TODO(crbug.com/474228715): `browser_initiated` will be used to set the
    // `is_browser_initiated` field in `commit_params_`, but it might be
    // overridden by the NTP checks (`OverrideNavigationParams()`). NTP override
    // should not affect initial WebUI page so it should be acceptable to ignore
    // that, but we may consider moving the override logic to a earlier place so
    // that we have as many final values as possible before we perform all the
    // CHECKs.
    CHECK(browser_initiated);
    CHECK(!frame_tree_node_->GetParentOrOuterDocumentOrEmbedder());
    bool is_navigating_from_initial_empty_document =
        frame_tree_node_->is_on_initial_empty_document() &&
        frame_tree_node_->navigator()
            .controller()
            .GetLastCommittedEntry()
            ->IsInitialEntry();
    CHECK(base::FeatureList::IsEnabled(features::kDebugTopChromeWebUI) ||
          is_navigating_from_initial_empty_document ||
          current_rfh_is_initial_webui);
  }
#endif

  if (GetInitiatorFrameToken().has_value()) {
    RenderFrameHostImpl* initiator_rfh = RenderFrameHostImpl::FromFrameToken(
        GetInitiatorProcessId(), GetInitiatorFrameToken().value());
    if (initiator_rfh) {
      initiator_document_token_ = initiator_rfh->GetDocumentToken();
      is_opener_navigation_ =
          (initiator_rfh->frame_tree_node()->opener() == frame_tree_node_);
    }
  }

  ComputeDownloadPolicy();

  // Ensure the blink::RuntimeFeatureStateContext is initialized.
  runtime_feature_state_context_ = blink::RuntimeFeatureStateContext();

  TRACE_EVENT_BEGIN(
      "navigation", "NavigationRequest", GetNavigationTracingTrack(),
      perfetto::protos::pbzero::ChromeTrackEvent::kNavigation, this);
  TRACE_EVENT_BEGIN("navigation", "Initializing", GetNavigationTracingTrack());

  // Spec: https://github.com/whatwg/html/issues/8846
  // We only allow the parent to access a subframe resource timing if the
  // navigation is container-initiated, e.g. iframe changed src.
  if (begin_params_->is_container_initiated) {
    // Only same-origin navigations without cross-origin redirects can
    // expose response details (status-code / mime-type).
    // https://github.com/whatwg/fetch/issues/1602
    // Note that this condition checks this navigation is not cross origin.
    // Cross-origin redirects are checked as part of OnRequestRedirected().
    commit_params_->navigation_timing->parent_resource_timing_access =
        GetParentFrame()->GetLastCommittedOrigin().IsSameOriginWith(GetURL())
            ? blink::mojom::ParentResourceTimingAccess::
                  kReportWithResponseDetails
            : blink::mojom::ParentResourceTimingAccess::
                  kReportWithoutResponseDetails;
  }

  resume_after_deferred_commit_listener_ =
      std::move(deferred_commit_resume_listener);

  navigation_or_document_handle_ =
      NavigationOrDocumentHandle::CreateForNavigation(*this);

  policy_container_builder_.emplace(GetParentFrame(), frame_entry);

  NavigationControllerImpl* controller = GetNavigationController();

  if (frame_entry) {
    origin_related_state_.emplace(OriginRelatedState{
        .item_sequence_number = frame_entry->item_sequence_number(),
        .document_sequence_number = frame_entry->document_sequence_number(),
    });
  }

  // Sanitize the referrer.
  common_params_->referrer = Referrer::SanitizeForRequest(
      common_params_->url, *common_params_->referrer);

  if (IsInPrimaryMainFrame()) {
    loading_mem_tracker_ = PeakGpuMemoryTrackerFactory::Create(
        viz::PeakGpuMemoryTracker::Usage::PAGE_LOAD);
  }

  if (frame_tree_node_->IsInFencedFrameTree()) {
    commit_params_->frame_policy.sandbox_flags |=
        blink::kFencedFrameForcedSandboxFlags;
  }

  if (from_begin_navigation_) {
    // This is needed to commit data: and about: URLs in the same SiteInstance
    // as the initiator frame. Note that this may not necessarily match the
    // SiteInstance of the RenderFrameHost that sent the BeginNavigation IPC
    // (i.e., `frame_tree_node_->current_frame_host()->GetSiteInstance()`) with
    // SiteInstanceGroups, because some other frame in a different SiteInstance
    // but same SiteInstanceGroup could've initiated this navigation.
    source_site_instance_ =
        initiator_navigation_state_impl()
            ? initiator_navigation_state_impl()->site_instance()
            : nullptr;

    // TODO(crbug.com/510258191): This should not happen. Check that this is not
    // the case once we have fixed storage of the initiator navigation state.
    // If there is no initiator navigation state, fall back to the navigating
    // frame's current SiteInstance. This ensures that this renderer-initiated
    // navigation still has a valid source SiteInstance corresponding to the
    // renderer process that initiated the navigation, which is needed for
    // certain security checks based on `source_site_instance_`, such as the
    // CanRequestURL() check in `OnRequestRedirected()`.
    if (!source_site_instance_) {
      source_site_instance_ =
          frame_tree_node_->current_frame_host()->GetSiteInstance();
    }

    CHECK(navigation_client.is_valid());
    SetNavigationClient(std::move(navigation_client));

    // Wait for renderer-initiated cancellation if needed. Navigation can
    // proceed as soon as the corresponding JS task in the renderer finishes
    // without calling window.stop() or other navigation cancellation triggers.
    // That means there is no need to synchronize this signal with other
    // renderer events, so this interface doesn't have to be associated and can
    // use a prioritized task runner.
    // kNavigationNetworkResponse is used as CommitNavigation typically already
    // runs in on a task from this task runner (via OnResponseReceived message
    // received from the network service).
    if (renderer_cancellation_listener.is_valid()) {
      renderer_cancellation_listener_.Bind(
          std::move(renderer_cancellation_listener),
          GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse}));
    }
    if (renderer_ignore_duplicate_navigation_listener.is_valid()) {
      renderer_ignore_duplicate_navigation_listener_.Bind(
          std::move(renderer_ignore_duplicate_navigation_listener),
          GetUIThreadTaskRunner({BrowserTaskType::kDefault}));
    }
  } else if (entry) {
    CHECK(!navigation_client.is_valid());
    if (frame_entry) {
      source_site_instance_ = frame_entry->source_site_instance();
      dest_site_instance_ = frame_entry->site_instance();
      bindings_ = frame_entry->bindings();

      // Handle navigations that require a |source_site_instance| but do not
      // have one set yet. This can happen when navigation entries are restored
      // from PageState objects, because the serialized state does not contain a
      // SiteInstance so we need to use the |initiator_origin| to get an
      // appropriate source SiteInstance. This can also happen when the
      // |source_site_instance| was suppressed because of navigating in
      // "noopener" mode.
      //
      // History subframe, restore navigations and |was_opener_suppressed| are
      // the only cases where SetSourceSiteInstanceToInitiatorIfNeeded needs to
      // be called (i.e. the only cases that may have no |source_site_instance_|
      // even though RequiresInitiatorBasedSourceSiteInstance returns true).  We
      // verify that other cases which require a |source_site_instance_| indeed
      // have one with a CHECK below.
      if (common_params_->is_history_navigation_in_new_child_frame ||
          common_params_->navigation_type ==
              blink::mojom::NavigationType::RESTORE ||
          common_params_->navigation_type ==
              blink::mojom::NavigationType::RESTORE_WITH_POST ||
          was_opener_suppressed) {
        SetSourceSiteInstanceToInitiatorIfNeeded();
      }
    }
    isolation_info_ = entry->isolation_info();

    // Ensure that we always have a |source_site_instance_| for navigations
    // that require it at this point. This is needed to ensure that data: URLs
    // commit in the SiteInstance that initiated them.
    //
    // TODO(acolwell): Move this below so it can be enforced on all paths.
    // This requires auditing same-document and other navigations that don't
    // have |from_begin_navigation_| or |entry| set.
    // TODO(https://crbug.com/526542421): CHECK-exclusion: Convert to CHECK once
    // we are sure this isn't hit.
    DCHECK(!RequiresInitiatorBasedSourceSiteInstance() ||
           source_site_instance_);
  }

  // Let the NTP override the navigation params and pretend that this is a
  // browser-initiated, bookmark-like navigation.
  // TODO(crbug.com/40702467): determine why some link navigations on chrome://
  //     pages have |browser_initiated| set to true and others set to false.
  if (source_site_instance_) {
    bool is_renderer_initiated = !browser_initiated;
    Referrer referrer(*common_params_->referrer);
    ui::PageTransition transition =
        ui::PageTransitionFromInt(common_params_->transition);
    // TODO(crbug.com/388998723): Avoid unintentionally creating a process for
    // source_site_instance_ if it doesn't have one.
    GetContentClient()->browser()->OverrideNavigationParams(
        source_site_instance_
            ->GetOrCreateProcess(ProcessAllocationContext{
                ProcessAllocationSource::kOverrideNavigationParams})
            ->GetProcessLock()
            .site_url(),
        &transition, &is_renderer_initiated, &referrer,
        &common_params_->initiator_origin);
    common_params_->transition = transition;
    common_params_->referrer =
        blink::mojom::Referrer::New(referrer.url, referrer.policy);
    browser_initiated = !is_renderer_initiated;
  }
  commit_params_->is_browser_initiated = browser_initiated;

  // Update the load flags with cache information.
  UpdateLoadFlagsWithCacheFlags(&begin_params_->load_flags,
                                common_params_->navigation_type,
                                common_params_->method == "POST");

  // Add necessary headers that may not be present in the
  // blink::mojom::BeginNavigationParams.
  if (entry) {
    if (frame_tree_node->IsOutermostMainFrame() &&
        entry->back_forward_cache_metrics()) {
      entry->back_forward_cache_metrics()
          ->MainFrameDidStartNavigationToDocument();
    }

    // If this NavigationRequest is for the current pending entry, make sure
    // that we will discard the pending entry if all of its associated requests
    // go away, by creating a ref to it.
    if (entry == controller->GetPendingEntry())
      pending_entry_ref_ = controller->ReferencePendingEntry();

    // |commit_params->is_overriding_user_agent| is the single source of truth
    // in NavigationRequest. For history navigations, callers of this
    // constructor must not provide conflicting requirements. Only
    // |commit_params->is_overriding_user_agent| will be taken into account.
    CHECK_EQ(is_overriding_user_agent(), entry->GetIsOverridingUserAgent());
  }

  net::HttpRequestHeaders headers;
  // Only add specific headers when creating a NavigationRequest before the
  // network request is made, not at commit time.
  if (!is_synchronous_renderer_commit) {
    BrowserContext* browser_context = controller->GetBrowserContext();
    ClientHintsControllerDelegate* client_hints_delegate =
        browser_context->GetClientHintsControllerDelegate();
    // Loading an about:srcdoc url on the main frame will cause a failure later
    // and GetTentativeOriginAtRequestTime() can't handle it until then.
    if ((CheckAboutSrcDoc() != AboutSrcDocCheckResult::BLOCK_REQUEST) &&
        client_hints_delegate) {
      net::HttpRequestHeaders client_hints_headers;
      AddNavigationRequestClientHintsHeaders(
          GetTentativeOriginAtRequestTime(), &client_hints_headers,
          browser_context, client_hints_delegate, is_overriding_user_agent(),
          frame_tree_node_, commit_params_->frame_policy.container_policy,
          common_params_->url);
      headers.MergeFrom(client_hints_headers);
    }

    // Add reduced accept language header.
    if (auto reduce_accept_lang_utils =
            ReduceAcceptLanguageUtils::Create(browser_context);
        reduce_accept_lang_utils && !devtools_accept_language_override_ &&
        !ReduceAcceptLanguageUtils::CheckDisableReduceAcceptLanguageOriginTrial(
            common_params_->url, frame_tree_node_,
            browser_context->GetOriginTrialsControllerDelegate())) {
      // Add the Accept-Language header with the reduce accept language value.
      // Chromium network stack won't overwrite the value if Accept-Language
      // header was already added in the request header.
      net::HttpRequestHeaders accept_language_headers;
      std::optional<std::string> reduced_accept_language =
          reduce_accept_lang_utils.value()
              .AddNavigationRequestAcceptLanguageHeaders(
                  url::Origin::Create(common_params_->url), frame_tree_node_,
                  &accept_language_headers);
      commit_params_->reduced_accept_language =
          reduced_accept_language.value_or("");
      headers.MergeFrom(accept_language_headers);
    }

    headers.AddHeadersFromString(begin_params_->headers);
    AddAdditionalRequestHeaders(
        &headers, common_params_->url, common_params_->navigation_type,
        ui::PageTransitionFromInt(common_params_->transition),
        controller->GetBrowserContext(), common_params_->method,
        GetUserAgentOverride(), common_params_->initiator_origin,
        common_params_->referrer.get(), frame_tree_node,
        commit_params_->is_browser_initiated);

    if (begin_params_->is_form_submission) {
      // During form resubmit, `commit_params_->post_content_type` is populated
      // from the history. Use it.
      if (!commit_params_->post_content_type.empty()) {
        headers.SetHeaderIfMissing(net::HttpRequestHeaders::kContentType,
                                   commit_params_->post_content_type);
      }

      // Save the Content-Type in case the form is resubmitted. This will get
      // sent back to the renderer in the CommitNavigation IPC. The renderer
      // will then send it back with the post body so that we can access it
      // along with the body in FrameNavigationEntry::page_state_.
      if (std::optional<std::string> content_type =
              headers.GetHeader(net::HttpRequestHeaders::kContentType);
          content_type) {
        commit_params_->post_content_type = std::move(content_type).value();
      }
    }
  }

  begin_params_->headers = headers.ToString();

#if BUILDFLAG(IS_ANDROID)
  RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
      frame_tree_node_->current_frame_host()->GetRenderWidgetHost());
  if (NeedsUrlLoader() && IsInPrimaryMainFrame() && host && !host->IsHidden() &&
      host->GetView() && host->GetView()->GetNativeView() &&
      host->GetView()->GetNativeView()->GetWindowAndroid()) {
    // If the compositor changes, we will just let the lock timeout instead of
    // trying to deal with it explicitly.
    ui::WindowAndroidCompositor* compositor =
        host->GetView()->GetNativeView()->GetWindowAndroid()->GetCompositor();
    if (compositor) {
      compositor_lock_ = compositor->GetCompositorLock(kCompositorLockTimeout);
    }
  }

  navigation_handle_proxy_ = std::make_unique<NavigationHandleProxy>(this);
#endif

  if (NeedsUrlLoader() && common_params_->url.SchemeIsHTTPOrHTTPS()) {
    if (GetContentClient()->browser()->ShouldPreconnectNavigation(
            frame_tree_node_->current_frame_host()) &&
        IsAllowedByConnectionAllowlist(/*is_redirect=*/false)) {
      auto* storage_partition =
          frame_tree_node_->current_frame_host()->GetStoragePartition();

      // Initiator frame's `network_restriction_id` is not passed because the
      // preconnection has already been checked against the connection-allowlist
      // by the `IsAllowedByConnectionAllowlist(false)` call above.
      storage_partition->GetNetworkContext()->PreconnectSockets(
          1, common_params_->url, network::mojom::CredentialsMode::kInclude,
          GetIsolationInfo().network_anonymization_key(),
          network::GetNoOpNetworkRestrictionsId(),
          net::MutableNetworkTrafficAnnotationTag(),
          /*keepalive_config=*/std::nullopt, mojo::NullRemote());
    }
  }

  if (NeedsUrlLoader() && IsInOutermostMainFrame()) {
    MaybePrewarmHttpDiskCache(*controller->GetBrowserContext(), GetURL(),
                              GetInitiatorOrigin());
  }

  // Checking OriginCanAccessServiceWorkers() is needed before calling
  // GetTentativeOriginAtRequestTime() since loading an about:srcdoc URL
  // on the main frame will cause a failure while processing
  // GetTentativeOriginAtRequestTime().
  if (OriginCanAccessServiceWorkers(GetURL())) {
    // Preflight request for FindRegistrationForClientUrl. This
    // preflight request speeds-up the upcoming
    // FindRegistrationForClientUrl requests because the upcoming
    // requests will be merged into this preflight request in
    // `ServiceWorkerRegistry::FindRegistrationForClientUrl()` and
    // `ServiceWorkerRegistry::RunFindRegistrationCallbacks()` later.
    if (ServiceWorkerContext* context = GetStoragePartitionWithCurrentSiteInfo()
                                            ->GetServiceWorkerContext()) {
      const blink::StorageKey key = blink::StorageKey::CreateFirstParty(
          GetTentativeOriginAtRequestTime());
      if (context->MaybeHasRegistrationForStorageKey(key)) {
        // `CheckHasServiceWorker` calls `FindRegistrationForClientUrl`
        // internally.
        context->CheckHasServiceWorker(GetURL(), key, base::DoNothing());
      }
    }
  }

  // Only update the BackForwardCacheMetrics if this is for a navigation that
  // could've been served from the bfcache.
  if (IsBackForwardCacheEnabled() && !IsServedFromBackForwardCache() && entry &&
      BackForwardCacheMetrics::IsCrossDocumentMainFrameHistoryNavigation(
          this)) {
    // Update NotRestoredReasons and create a metrics object if there's none.
    entry->UpdateBackForwardCacheNotRestoredReasons(this);
    auto* metrics = entry->back_forward_cache_metrics();
    CHECK(metrics);
    if (base::FeatureList::IsEnabled(
            blink::features::kBackForwardCacheSendNotRestoredReasons)) {
      // Only populate the web-exposed NotRestoredReasons when needed by
      // the NotRestoredReasons API.
      commit_params_->not_restored_reasons =
          metrics->GetWebExposedNotRestoredReasons();

      if (!base::FeatureList::IsEnabled(
              blink::features::kBackForwardCacheUpdateNotRestoredReasonsName)) {
        // Check that the reasons are not null since |this| is not served from
        // back/forward cache.
        // Session restored cases will be reported as null with the flag on and
        // this check will no longer hold true.
        CHECK(!commit_params_->not_restored_reasons.is_null());
      }
    }
  }
  // Check that the reasons are null when |this| is served from back/forward
  // cache.
  if (base::FeatureList::IsEnabled(
          blink::features::kBackForwardCacheSendNotRestoredReasons) &&
      IsBackForwardCacheEnabled() && IsServedFromBackForwardCache()) {
    CHECK(commit_params_->not_restored_reasons.is_null());
  }

  // Record `SameDocumentCrossOriginInitiator` metric. It happens in the
  // NavigationRequest constructor, to catch every kind of same-document
  // navigation: the one initiated from the navigating frame's process, and the
  // others.
  if (common_params_->navigation_type ==
          blink::mojom::NavigationType::SAME_DOCUMENT &&
      GetInitiatorOrigin() &&
      !GetInitiatorOrigin()->IsSameOriginWith(
          GetTentativeOriginAtRequestTime())) {
    // This is reported to navigating frame's current document, because this is
    // the document that behave differently if this navigation was turned into a
    // cross-document one.
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        frame_tree_node_->current_frame_host(),
        blink::mojom::WebFeature::kSameDocumentCrossOriginInitiator);
  }

  if (!GetContentClient()->browser()->IsBrowserStartupComplete()) {
    confidence_level_ = blink::mojom::ConfidenceLevel::kLow;
  }

  UpdateNavigationHandleTimingsOnCreated();
}

NavigationRequest::~NavigationRequest() {
  TRACE_EVENT("navigation", "NavigationRequest::~NavigationRequest",
              perfetto::TerminatingFlow::FromPointer(this));
  is_destructing_ = true;

#if !BUILDFLAG(IS_ANDROID)
  // If |is_safe_to_delete_| is false, it means |this| is being deleted at an
  // unexpected time, more specifically a time that is likely to lead to
  // crashing when the stack unwinds (use after free). The typical scenario for
  // this is calling to the delegate when the delegate is not expected to make
  // any sort of state change. For example, when the delegate is informed that a
  // navigation has started the delegate is not expected to call Stop().
  CHECK(is_safe_to_delete_);
#endif

  // Close "Initializing", or the last child event emitted in
  // EnterChildTraceEvent().
  TRACE_EVENT_END("navigation",
                  navigation_trace_track_);  // Closes the last child event
                                             // (e.g. Initializing)
  TRACE_EVENT_END("navigation", navigation_trace_track_);  // NavigationRequest

  // If navigation has started but not finished, mark it as aborted for
  // Navigation callbacks.
  if (base::FeatureList::IsEnabled(
          features::kAbortNavigationsFromTabClosures) &&
      state_ < DID_COMMIT && net_error_ == net::OK) {
    net_error_ = net::ERR_ABORTED;
  }

  if (!early_navigation_failure_recorded_ && !response() &&
      IsInPrimaryMainFrame() && net_error_ == net::ERR_ABORTED &&
      CanRecordEarlyNavigationFailure()) {
    DeclarativePerformanceObserver::RecordEarlyNavigationFailure(
        this, GetStoragePartitionWithCurrentSiteInfo(), net::ERR_ABORTED);
  }

  // IMPORTANT NOTE: DO NOT return early from the destructor before this line.
  // Otherwise, a queued navigation might get stuck in a queueing state forever.
  // This navigation has finished. See if there is another NavigationRequest
  // that lives in the associated FrameTreeNode that satisfies these conditions:
  // - Is currently queued to wait for a pending commit navigation to finish
  // - Is not the NavigationRequest that is currently being destructed itself
  if (NavigationRequest* request = frame_tree_node_->navigation_request()) {
    if (request->IsQueued() && request != this) {
      // It might be possible for the pending commit RFH to still exist, e.g. if
      // the navigation being destructed is an unrelated navigation
      // (same-document navigation etc). In that case, don't continue the queued
      // navigation just yet.
      if (!request->ShouldQueueDueToExistingPendingCommitRFH()) {
        request->PostResumeCommitTask();
      }
    }
  }

  if (loading_mem_tracker_)
    loading_mem_tracker_->Cancel();
  ResetExpectedProcess();

#if BUILDFLAG(IS_ANDROID)
  if (IsInPrimaryMainFrame()) {
    if (auto* cache =
            GetNavigationController()->GetNavigationEntryScreenshotCache()) {
      cache->OnNavigationFinished(*this);
    }
  }
#endif  // BUILDFLAG(IS_ANDROID)

  if (HasCommitted()) {
    CHECK(!navigation_discard_reason_.has_value());
  } else {
    if (!navigation_discard_reason_.has_value()) {
      // If a navigation is destructed without having committed, it should have
      // a reason set.
      // TODO(crbug.com/390569133): Change this back to a CHECK once we make
      // sure this is never hit.
      SCOPED_CRASH_KEY_NUMBER("Bug390569133", "navigation_state", (int)state_);
      base::debug::DumpWithoutCrashing();
    }

    // If we're before WILL_START_NAVIGATION, we haven't reported request start
    // to DevTools yet.
    // If we're in WILL_FAIL_REQUEST, the failure has been reported already.
    if (state_ >= WILL_START_NAVIGATION && state_ != WILL_FAIL_REQUEST) {
      devtools_instrumentation::OnNavigationRequestFailed(
          *this, network::URLLoaderCompletionStatus(net::ERR_ABORTED));
    }

    // NavigationRequests with pending Navigation API keys must notify the
    // renderer when they fail.
    if (pending_navigation_api_key_) {
      frame_tree_node_->current_frame_host()
          ->GetAssociatedLocalFrame()
          ->TraverseCancelled(
              *pending_navigation_api_key_,
              blink::mojom::TraverseCancelledReason::kAbortedBeforeCommit);
    }

    // If subframe history navigations were deferred waiting for this request,
    // the cancellation of this request should cancel them, too.
    for (auto& throttle : subframe_history_navigation_throttles_) {
      if (throttle) {
        throttle->Cancel();
      }
    }
    subframe_history_navigation_throttles_.clear();
  }

  // If this NavigationRequest is the last one referencing the pending
  // NavigationEntry, the entry is discarded.
  //
  // Leaving a stale pending NavigationEntry with no matching navigation can
  // potentially lead to URL-spoof issues.
  //
  // Note: Discarding the pending NavigationEntry is done before notifying the
  // navigation finished to the observers. One class is relying on this:
  // org.chromium.chrome.browser.toolbar.ToolbarManager
  pending_entry_ref_.reset();

#if BUILDFLAG(IS_ANDROID)
  if (navigation_visible_to_embedder_)
    navigation_handle_proxy_->DidFinish();
#endif

  if (is_deferred_on_fenced_frame_url_mapping_) {
    CHECK(NeedFencedFrameURLMapping());
    GetFencedFrameURLMap().RemoveObserverForURN(common_params_->url, this);
  }

  RecordEarlyRenderFrameHostSwapMetrics();

  if (IsNavigationStarted()) {
    GetDelegate()->DidFinishNavigation(this);
    ProcessOriginAgentClusterEndResult();
    if (IsInMainFrame()) {
      MaybeRecordTraceEventsAndHistograms();
    }

    MaybeRecordNavigationStartAdjustments();

    // Abandon the prerender host reserved for activation if it exists.
    if (IsPrerenderedPageActivation()) {
      GetPrerenderHostRegistry().OnActivationFinished(
          activating_prerender_host_id_.value());
    }

    if (!HasCommitted()) {
      ResetViewTransitionState();
    }

    if (IsServedFromBackForwardCache()) {
      auto bfcache_entry =
          GetNavigationController()->GetBackForwardCache().GetOrEvictEntry(
              nav_entry_id());
      if (!bfcache_entry.has_value() &&
          bfcache_entry.error() ==
              BackForwardCacheImpl::kEntryIneligibleAndEvicted) {
        // DO NOT ADD CODE after this. When BFCache entry is evicted, the
        // current NavigationRequest has been destroyed.
        return;
      }
      if (bfcache_entry.has_value()) {
        RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(
            bfcache_entry.value()->render_frame_host()->GetGlobalId());
        // RFH could have been deleted. E.g. eviction timer fired
        if (rfh && rfh->IsInBackForwardCache()) {
          // rfh is still in the cache so the navigation must have failed. But
          // we have already disabled eviction so the safest thing to do here to
          // recover is to evict.
          // TODO(crbug.com/40283427): Only evict BFCache if the
          // `BackForwardCacheCommitDeferringCondition`, which unfreezes the
          // page and disables the eviction on the renderer side, is completed.
          rfh->EvictFromBackForwardCacheWithReason(
              BackForwardCacheMetrics::NotRestoredReason::
                  kNavigationCancelledWhileRestoring);
        }
      }
    }
  } else {
    GetDelegate()->DidCancelNavigationBeforeStart(this);
  }

  if (!navigation_start_to_commit_ended_) {
    TRACE_EVENT_END("navigation",
                    breakdown_trace_track_);  // Navigation StartToCommit
    navigation_start_to_commit_ended_ = true;
  }
}

void NavigationRequest::RegisterCommitDeferringConditionForTesting(
    std::unique_ptr<CommitDeferringCondition> condition) {
  commit_deferrer_->AddConditionForTesting(std::move(condition));  // IN-TEST
}

void NavigationRequest::SetCookieAccessObserversForTesting(
    std::unique_ptr<CookieAccessObservers> observers) {
  cookie_observers_ = std::move(observers);
}

bool NavigationRequest::IsCommitDeferringConditionDeferredForTesting() {
  if (!commit_deferrer_)
    return false;
  return commit_deferrer_->GetDeferringConditionForTesting();  // IN-TEST
}

CommitDeferringCondition*
NavigationRequest::GetCommitDeferringConditionForTesting() {
  if (!commit_deferrer_)
    return nullptr;
  return commit_deferrer_->GetDeferringConditionForTesting();  // IN-TEST
}

void NavigationRequest::BeginNavigation() {
  begin_navigation_time_ = base::TimeTicks::Now();
  TRACE_EVENT("navigation", "NavigationRequest::BeginNavigation",
              perfetto::Flow::FromPointer(this));
  EnterChildTraceEvent("BeginNavigation", this);
  CHECK(!loader_);
  CHECK(!HasRenderFrameHost());
  ScopedCrashKeys crash_keys(*this);
  UpdateNavigationHandleTimingsOnBeginNavigation();

  // Cancel the navigation if it is an invalid attempt to navigate away from
  // an initial WebUI page.
  if (should_cancel_on_leaving_initial_webui_) {
#if BUILDFLAG(IS_ANDROID)
    base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
#endif
    StartNavigation();
#if BUILDFLAG(IS_ANDROID)
    if (!this_ptr) {
      return;
    }
#endif
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_ABORTED),
        /*skip_throttles=*/false, /*error_page_content=*/std::nullopt,
        /*collapse_frame=*/false);
    return;
  }

  if (begin_navigation_callback_for_testing_) {
    std::move(begin_navigation_callback_for_testing_).Run();
  }

  if (MaybeStartPrerenderingActivationChecks()) {
    // BeginNavigationImpl() will be called after the checks.
    return;
  }

  MaybeAssignInvalidActivatingPrerenderHostId();

  // Fenced frames are not allowed to load if nested in iframes with CSPEE.
  bool is_fenced_frame = frame_tree_node_->IsFencedFrameRoot();
  if (is_fenced_frame) {
    CHECK(!frame_tree_node_->csp_attribute());
    if (GetParentFrameOrOuterDocument()->required_csp()) {
      GURL sanitized_blocked_url =
          common_params_->url.DeprecatedGetOriginAsURL();
      AddDeferredConsoleMessage(
          blink::mojom::ConsoleMessageLevel::kError,
          base::StringPrintf(
              "Refused to frame '%s' as a fenced frame because "
              "CSP Embedded Enforcement is specified by the embedder",
              sanitized_blocked_url.spec().c_str()));

#if BUILDFLAG(IS_ANDROID)
      base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
#endif
      StartNavigation();
#if BUILDFLAG(IS_ANDROID)
      if (!this_ptr) {
        // DO NOT ADD CODE after this. The previous call to StartNavigation has
        // destroyed the NavigationRequest.
        // TODO(crbug.com/504574017): Remove if we can disallow StartNavigation
        // from triggering synchronous deletion.
        return;
      }
#endif
      OnRequestFailedInternal(
          network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_CSP),
          false /*skip_throttles*/, std::nullopt /*error_page_content*/,
          false /*collapse_frame*/);
      // DO NOT ADD CODE after this. The previous call to
      // OnRequestFailedInternal has destroyed the NavigationRequest.
      return;
    }
  }

  // If this is a fenced frame with a urn:uuid, or an iframe with a urn::uuid
  // given blink::features::kAllowURNsInIframes is enabled, then convert it to a
  // url before starting the navigation; otherwise, proceed directly with the
  // navigation.
  // In long term, navigation support for urn::uuid in iframes will be
  // deprecated. Currently we issue a console warning when navigation starts.
  // TODO(crbug.com/40060657)
  if (NeedFencedFrameURLMapping()) {
    if (!is_fenced_frame) {
      // Iframes with urn::uuid.
      CHECK(!frame_tree_node_->IsMainFrame());
      CHECK(blink::features::IsAllowURNsInIframeEnabled());
      if (blink::features::DisplayWarningDeprecateURNIframesUseFencedFrames()) {
        AddDeferredConsoleMessage(
            blink::mojom::ConsoleMessageLevel::kWarning,
            "Protected Audience/selectURL will deprecate supporting iframes to "
            "render the winning ad/selected URL. "
            "Please use fenced frames instead. See "
            "https://developer.chrome.com/en/docs/privacy-sandbox/fenced-frame/"
            "#examples");
      }
    }

    UMA_HISTOGRAM_BOOLEAN(
        "Navigation.BrowserMappedUrnUuidInIframeOrFencedFrame",
        !is_fenced_frame);

    FencedFrameURLMapping& fenced_frame_urls_map = GetFencedFrameURLMap();

    // If the mapping finishes synchronously, OnFencedFrameURLMappingComplete
    // will be synchronously called and will reset
    // `is_deferred_on_fenced_frame_url_mapping_` to false.
    is_deferred_on_fenced_frame_url_mapping_ = true;

    fenced_frame_url_mapping_start_time_ = base::TimeTicks::Now();

    // OnFencedFrameURLMappingComplete() and BeginNavigationImpl() will be
    // invoked after this.
    fenced_frame_urls_map.ConvertFencedFrameURNToURL(common_params_->url,
                                                     /*observer=*/this);
    // DO NOT ADD CODE after this. The previous call to
    // ConvertFencedFrameURNToURL may cause the destruction of the
    // NavigationRequest.
    return;
  }

  // Send any potential navigation start automatic beacons for this frame.
  frame_tree_node_->current_frame_host()
      ->MaybeSendFencedFrameAutomaticReportingBeacon(
          *this, blink::mojom::AutomaticBeaconType::kTopNavigationStart);

  BeginNavigationImpl();
}

void NavigationRequest::UpdateNavigationStartTime(const base::TimeTicks& time,
                                                  bool for_legacy,
                                                  bool showed_dialog) {
  // Track the adjustment details for https://crbug.com/385170155.
  // Note: It is possible to get here more than once for a single request, which
  // might happen if a beforeunload ack for a different navigation is received
  // at the wrong time (see https://crbug.com/402545469). In that case, preserve
  // the existing `original_navigation_start_`.
  // TODO(crbug.com/404286908): Track which NavigationRequest should be updated
  // in response to a given beforeunload completion.
  if (original_navigation_start_.is_null()) {
    original_navigation_start_ = common_params_->navigation_start;
  }
  navigation_start_adjustment_for_legacy_ = for_legacy;
  beforeunload_dialog_shown_ = showed_dialog;

  common_params_->navigation_start = time;

  if (for_legacy) {
    // Legacy PostTasks do not run any actual beforeunload handlers and should
    // be treated as part of navigation overhead, rather than excluded.
    //
    // Note: Due to crbug.com/404286908, it is possible for this function to be
    // called with data from an earlier NavigationRequest that has been canceled
    // before this request started, and thus `for_legacy` may not be accurate.
    // Until that bug is fixed, though, it is still accurate to treat the
    // current request as legacy (even if it meant to run actual beforeunload
    // handlers) because the navigation will proceed anyway without waiting for
    // those handlers.
    beforeunload_phase2_start_time_ = base::TimeTicks();
    beforeunload_phase2_end_time_ = base::TimeTicks();
  } else {
    // Non-legacy cases that ran beforeunload handlers should set an end time
    // for BeforeUnload phase 2 if there was a corresponding start time. This
    // end time should be close to the end of running beforeunload.
    //
    // Note: Until crbug.com/404286908 is fixed, it is possible for `time` to be
    // before the start time, due to updating the wrong NavigationRequest. In
    // that rare case, use the current time instead to ensure the interval is
    // well-defined (even if that causes us to ignore a little more time than
    // necessary by making BeforeUnload phase 2 look a little longer). Also
    // avoid changing the end time once it is set, in case this is called more
    // than once.
    if (!beforeunload_phase2_start_time_.is_null() &&
        beforeunload_phase2_end_time_.is_null()) {
      beforeunload_phase2_end_time_ = (beforeunload_phase2_start_time_ <= time)
                                          ? time
                                          : base::TimeTicks::Now();
    }
  }
}

bool NavigationRequest::MaybeStartPrerenderingActivationChecks() {
  // Find an available prerendered page for this request. If it's found, this
  // request may activate it instead of loading a page via network.
  PrerenderHostId candidate_prerender_host_id =
      GetPrerenderHostRegistry().FindPotentialHostToActivate(*this);
  if (candidate_prerender_host_id.is_null()) {
    return false;
  }

  // Run CommitDeferringConditions before activating the prerendered page. See
  // the comment on RunCommitDeferringConditions() for details.
  //
  // The prerendered page can be destroyed while the conditions are running.
  // In that case, this request gives up activating it and instead falls back to
  // a regular navigation.
  commit_deferrer_ = CommitDeferringConditionRunner::Create(
      *this,
      CommitDeferringCondition::NavigationType::kPrerenderedPageActivation,
      PrerenderHost::GetFrameTreeNodeIdForId(candidate_prerender_host_id));
  is_running_potential_prerender_activation_checks_ = true;

  // Post a task to run the conditions in case BeginNavigation() is not expected
  // to run synchronously. OnPrerenderingActivationChecksComplete() will be
  // called after all the deferring conditions finish.
  auto commit_deferring_conditions_complete_callback = base::BindOnce(
      &NavigationRequest::OnPrerenderingActivationChecksComplete,
      weak_factory_.GetWeakPtr(),
      CommitDeferringCondition::NavigationType::kPrerenderedPageActivation,
      candidate_prerender_host_id);
  base::SequencedTaskRunner::GetCurrentDefault()->PostNonNestableTask(
      FROM_HERE,
      base::BindOnce(&NavigationRequest::RunCommitDeferringConditions,
                     weak_factory_.GetWeakPtr(),
                     std::move(commit_deferring_conditions_complete_callback)));
  return true;
}

void NavigationRequest::OnPrerenderingActivationChecksComplete(
    CommitDeferringCondition::NavigationType navigation_type,
    std::optional<PrerenderHostId> candidate_prerender_host_id) {
  TRACE_EVENT("navigation",
              "NavigationRequest::OnPrerenderingActivationChecksComplete",
              perfetto::Flow::FromPointer(this));
  // Prerendered page activation must run CommitDeferringConditions before
  // StartRequest().
  // TODO(524554855): CHECK-exclusion: Convert to a CHECK once we are confident
  // it won't be triggered.
  DCHECK_LT(state_, WILL_START_NAVIGATION);

  CHECK(candidate_prerender_host_id.has_value());
  CHECK(!activating_prerender_host_id_.has_value());
  CHECK(!reserved_prerender_host_info_.has_value());

  // Attempt to reserve the potential PrerenderHost.
  //
  // If it has been requested to cancel prerendered page activation during
  // CommitDeferringConditions, ReserveHostToActivate() returns an invalid
  // PrerenderHostId, and then NavigationRequest continues as regular
  // navigation.
  reserved_prerender_host_info_ =
      GetPrerenderHostRegistry().ReserveHostToActivate(
          *this, candidate_prerender_host_id.value());

  activating_prerender_host_id_ =
      reserved_prerender_host_info_.has_value()
          ? reserved_prerender_host_info_->prerender_host_id
          : PrerenderHostId();

  if (!activating_prerender_host_id_.value()) {
    // If we ran commit deferring conditions for a potential pre-render which
    // eventually wasn't activated, abort the ViewTransition. The state was
    // cached assuming this navigation will be same-origin which might not be
    // the case now that we need to make a network request.
    ResetViewTransitionState();
  } else {
    // The reserved host should match with the potential host. Otherwise the
    // reserved host may not be ready for activation yet as we haven't run
    // PrerenderCommitDeferringCondition for the host to finish navigation in
    // the prerendering main frame.
    CHECK_EQ(activating_prerender_host_id_.value(),
             candidate_prerender_host_id.value());
  }
  is_running_potential_prerender_activation_checks_ = false;
  commit_deferrer_.reset();

  // We can only activate top-level pages, which can never be at a fenced frame
  // URN that needs to be mapped.
  CHECK(!NeedFencedFrameURLMapping());

  BeginNavigationImpl();
  // DO NOT ADD CODE after this. The previous call to
  // BeginNavigationImpl may cause the destruction of the NavigationRequest.
}

FencedFrameURLMapping& NavigationRequest::GetFencedFrameURLMap() {
  // The usual case here is a fenced frame root navigating to a URNs, in which
  // case we need to consult the `FencedFrameURLMapping` in the *outer*
  // FrameTree.
  bool is_fenced_frame_root =
      frame_tree_node_->current_frame_host()->IsFencedFrameRoot();
  FrameTreeNode* node_to_use = frame_tree_node_->frame_tree()
                                   .root()
                                   ->render_manager()
                                   ->GetOuterDelegateNode();

  // However the very unusual case is an *iframe* (that supports navigations to
  // URNs via `blink::features::IsAllowURNsInIframeEnabled`) navigating to a
  // URN, possibly *inside* of a fenced frame. We can remove support for this
  // case once third party cookies are removed.
  if (!is_fenced_frame_root) {
    node_to_use = frame_tree_node_;
  }
  CHECK(node_to_use);
  return node_to_use->current_frame_host()->GetPage().fenced_frame_urls_map();
}

bool NavigationRequest::NeedFencedFrameURLMapping() {
  if (!blink::IsValidUrnUuidURL(common_params_->url)) {
    return false;
  }
  if (blink::features::IsAllowURNsInIframeEnabled() &&
      !frame_tree_node_->IsMainFrame() &&
      !frame_tree_node_->IsFencedFrameRoot()) {
    // When urn iframes are enabled, any urn:uuid navigation to an iframe is
    // resolved using the urn mapping.
    is_embedder_initiated_fenced_frame_navigation_ = true;
  }
  return is_embedder_initiated_fenced_frame_navigation_;
}

void NavigationRequest::OnFencedFrameURLMappingComplete(
    const std::optional<FencedFrameProperties>& properties) {
  TRACE_EVENT("navigation",
              "NavigationRequest::OnFencedFrameURLMappingComplete",
              perfetto::Flow::FromPointer(this));
  is_deferred_on_fenced_frame_url_mapping_ = false;

  // The URL mapping might have failed (e.g. because the urn is invalid):
  if (!properties.has_value()) {
    // For iframes, try the urn as-is to maintain existing behavior which will
    // abort the navigation as the url is unresolvable.
    if (!frame_tree_node_->IsFencedFrameRoot()) {
      BeginNavigationImpl();  // DO NOT ADD CODE after this, because it might
                              // have destroyed `this`.
      return;
    }

#if BUILDFLAG(IS_ANDROID)
    base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
#endif
    StartNavigation();
#if BUILDFLAG(IS_ANDROID)
    if (!this_ptr) {
      // DO NOT ADD CODE after this. The previous call to StartNavigation has
      // destroyed the NavigationRequest.
      // TODO(crbug.com/504574017): Remove if we can disallow StartNavigation
      // from triggering synchronous deletion.
      return;
    }
#endif
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_INVALID_URL),
        false /* skip_throttles */, std::nullopt /* error_page_content*/,
        false /* collapse_frame */);
    return;
  }

  if (properties->on_navigate_callback()) {
    properties->on_navigate_callback().Run();
  }

  // Currently, all fenced frame use cases include mapped urls. Patch up
  // url-related fields to use the underlying mapped url, rather than the
  // original urn.
  CHECK(properties->mapped_url().has_value());
  const GURL& mapped_url_value =
      properties->mapped_url()->GetValueIgnoringVisibility();
  common_params_->url = mapped_url_value;
  original_url_ = mapped_url_value;
  if (base::FeatureList::IsEnabled(
          features::kSanitizeOriginalUrlDuringNavigation)) {
    // It is safe to convert GURL to an Origin and back in the code below
    // because we only want to discard the rest of the URL (e.g., path and
    // params). The actual underlying Origin is not needed, which could be
    // inherited or opaque in sandbox cases.
    commit_params_->original_url = mapped_url_value.DeprecatedGetOriginAsURL();
  } else {
    commit_params_->original_url = mapped_url_value;
  }

  // Store the browser's view of the fenced frame properties along with any
  // embedder context for shared storage in the`NavigationRequest`. Upon commit,
  // it will be stored in the fenced frame root `FrameTreeNode`.
  fenced_frame_properties_ = properties;

  // For urns loaded into iframes, we disable certain aspects of fenced frames:
  // * a storage/network partition nonce
  if (!frame_tree_node_->IsFencedFrameRoot()) {
    CHECK(blink::features::IsAllowURNsInIframeEnabled());
    fenced_frame_properties_->AdjustPropertiesForUrnIframe();
  }

  // This implies the URN is created from shared storage.
  if (fenced_frame_properties_->shared_storage_budget_metadata()) {
    base::TimeDelta time_spent_in_fenced_frame_url_mapping =
        base::TimeTicks::Now() - fenced_frame_url_mapping_start_time_;

    base::UmaHistogramTimes(
        "Storage.SharedStorage.Timing.UrlMappingDuringNavigation",
        time_spent_in_fenced_frame_url_mapping);
  }

  BeginNavigationImpl();  // DO NOT ADD CODE after this, because it might have
                          // destroyed `this`.
}

void NavigationRequest::BeginNavigationImpl() {
  TRACE_EVENT("navigation", "NavigationRequest::BeginNavigationImpl",
              perfetto::Flow::FromPointer(this));
  base::ElapsedTimer timer;
  SetState(WILL_START_NAVIGATION);
#if BUILDFLAG(IS_ANDROID)
  base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
  bool should_override_url_loading = false;

  if (!GetContentClient()->browser()->ShouldOverrideUrlLoading(
          frame_tree_node_->frame_tree_node_id(),
          commit_params_->is_browser_initiated, original_url_, request_method_,
          common_params_->has_possibly_filtered_user_gesture, false,
          frame_tree_node_->IsOutermostMainFrame(),
          frame_tree_node_->frame_tree().is_prerendering(),
          ui::PageTransitionFromInt(common_params_->transition),
          &should_override_url_loading)) {
    if (reserved_prerender_host_info_.has_value()) {
      // Prerender activation must not fail but some reports imply it can
      // actually be failing: crbug.com/408969974. This dump is useful for
      // debugging it.
      std::string prerender_type = GeneratePrerenderHistogramSuffix(
          GetPrerenderTriggerType(), GetPrerenderHistogramSuffix());
      SCOPED_CRASH_KEY_STRING64("Bug411566699", "prerender_type",
                                prerender_type);
      base::debug::DumpWithoutCrashing();
    }

    // A Java exception was thrown by the embedding application; we
    // need to return from this task. Specifically, it's not safe from
    // this point on to make any JNI calls.
    return;
  }

  // The content/ embedder might cause |this| to be deleted while
  // |ShouldOverrideUrlLoading| is called.
  // See https://crbug.com/770157.
  if (!this_ptr)
    return;

  if (should_override_url_loading) {
    // Don't create a NavigationHandle here to simulate what happened with the
    // old navigation code path (i.e. doesn't fire onPageFinished notification
    // for aborted loads).
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_ABORTED);
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kShouldOverrideUrlLoading;
    OnRequestFailedInternal(completion_status, false /*skip_throttles*/,
                            std::nullopt /*error_page_content*/,
                            false /*collapse_frame*/);
    return;
  }
#endif

  if (base::FeatureList::IsEnabled(features::kNavigationFastFetchDryRun)) {
    fast_fetch_manager_ = NavigationFastFetchManager::Create(*this);
    navigation_handle_timing_.fast_fetch_eligibility_check_time =
        fast_fetch_manager_->eligibility_check_time();
    navigation_handle_timing_.is_fast_fetch_eligible =
        (fast_fetch_manager_->eligibility_reason() ==
         NavigationFastFetchManager::EligibilityReason::kEligible);
  }

  // Check Content Security Policy before the NavigationThrottles run. This
  // gives CSP a chance to modify requests that NavigationThrottles would
  // otherwise block. Similarly, the NavigationHandle is created afterwards, so
  // that it gets the request URL after potentially being modified by CSP.
  net::Error net_error = CheckContentSecurityPolicy(
      false /* has_followed redirect */,
      false /* url_upgraded_after_redirect */, false /* is_response_check */);
  if (net_error != net::OK) {
    SCOPED_CRASH_KEY_NUMBER("NavReq", "net_error", static_cast<int>(net_error));
    SCOPED_CRASH_KEY_NUMBER("NavReq", "navigating_frame_type",
                            static_cast<int>(GetNavigatingFrameType()));
    // Create a navigation handle so that the correct error code can be set on
    // it by OnRequestFailedInternal().
    StartNavigation();
#if BUILDFLAG(IS_ANDROID)
    if (!this_ptr) {
      // DO NOT ADD CODE after this. The previous call to StartNavigation has
      // destroyed the NavigationRequest.
      // TODO(crbug.com/504574017): Remove if we can disallow StartNavigation
      // from triggering synchronous deletion.
      return;
    }
#endif
    OnRequestFailedInternal(network::URLLoaderCompletionStatus(net_error),
                            false /* skip_throttles */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (CheckCredentialedSubresource() ==
      CredentialedSubresourceCheckResult::BLOCK_REQUEST) {
    // Create a navigation handle so that the correct error code can be set on
    // it by OnRequestFailedInternal().
    StartNavigation();
#if BUILDFLAG(IS_ANDROID)
    if (!this_ptr) {
      // DO NOT ADD CODE after this. The previous call to StartNavigation has
      // destroyed the NavigationRequest.
      // TODO(crbug.com/504574017): Remove if we can disallow StartNavigation
      // from triggering synchronous deletion.
      return;
    }
#endif
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_ABORTED);
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kCredentialedSubresourceBlocked;
    OnRequestFailedInternal(completion_status, false /* skip_throttles  */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  // Connection Allowlist: check whether navigation to the url is allowed.
  if (!IsAllowedByConnectionAllowlist(/*is_redirect=*/false)) {
    // Record that this navigation is blocked by the initiator's
    // Connection-Allowlist before StartNavigation() notifies observers. This
    // lets observers (e.g. //chrome's LoadingPredictor) suppress speculative
    // network activity that would otherwise leak the destination host even
    // though the navigation is blocked. See
    // https://github.com/WICG/connection-allowlists.
    is_blocked_by_connection_allowlist_ = true;
    // Create a navigation handle so that the correct error code can be set on
    // it by OnRequestFailedInternal().
    StartNavigation();
#if BUILDFLAG(IS_ANDROID)
    if (!this_ptr) {
      // DO NOT ADD CODE after this. The previous call to StartNavigation has
      // destroyed the NavigationRequest.
      // TODO(crbug.com/504574017): Remove if we can disallow StartNavigation
      // from triggering synchronous deletion.
      return;
    }
#endif
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_NETWORK_ACCESS_REVOKED);
    OnRequestFailedInternal(completion_status, false /* skip_throttles  */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  StartNavigation();
#if BUILDFLAG(IS_ANDROID)
  if (!this_ptr) {
    // DO NOT ADD CODE after this. The previous call to StartNavigation has
    // destroyed the NavigationRequest.
    // TODO(crbug.com/504574017): Remove if we can disallow StartNavigation
    // from triggering synchronous deletion.
    return;
  }
#endif

  // The previous call to `StartNavigation()` could have changed the
  // is_overriding_user_agent value in CommitNavigationParams. If we're trying
  // to restore an entry from the back-forward cache, we need to ensure that
  // the is_overriding_user_agent used in the RenderFrameHost to restore matches
  // the value set in CommitNavigationParams.
  if (IsServedFromBackForwardCache() &&
      GetRenderFrameHostRestoredFromBackForwardCache()
              ->GetPage()
              .is_overriding_user_agent() !=
          commit_params_->is_overriding_user_agent) {
    // Trigger an eviction, which will cancel this navigation and trigger a new
    // one to the same entry (but won't try to restore the entry from the
    // back-forward cache) asynchronously.
    GetRenderFrameHostRestoredFromBackForwardCache()
        ->EvictFromBackForwardCacheWithReason(
            BackForwardCacheMetrics::NotRestoredReason::
                kUserAgentOverrideDiffers);
    // DO NOT ADD CODE after this. The previous call to
    // `EvictFromBackForwardCacheWithReason()`
    // has destroyed the NavigationRequest.
    return;
  }

  if (CheckAboutSrcDoc() == AboutSrcDocCheckResult::BLOCK_REQUEST) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_INVALID_URL),
        true /* skip_throttles */, std::nullopt /* error_page_content*/,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (browser_initiated_error_navigation_type_ !=
      BrowserInitiatedErrorNavigationType::kNone) {
    OnRequestFailedInternal(network::URLLoaderCompletionStatus(net_error_),
                            true /* skip_throttles  */,
                            error_page_html_ /* error_page_content */,
                            false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (IsForMhtmlSubframe())
    is_mhtml_or_subframe_ = true;

  // TODO(antoniosartori): This takes a snapshot of the 'csp' attribute. This
  // should be done at the beginning of the navigation instead. Otherwise, the
  // attribute might have change while waiting for the beforeunload handlers to
  // complete.
  SetupCSPEmbeddedEnforcement();
  SetupConnectionAllowlistEmbeddedEnforcement();

  if (!NeedsUrlLoader()) {
    // The types of pages that don't need a URL Loader should never get served
    // from the BackForwardCache or activated from a prerender.
    CHECK(!IsServedFromBackForwardCache());
    CHECK(!IsPrerenderedPageActivation());

    // There is no need to make a network request for this navigation, so commit
    // it immediately.
    EnterChildTraceEvent("ResponseStarted", this);

    // |CheckCSPEmbeddedEnforcement()| below populates the |required_csp_|. No
    // URLs will be blocked, because they are either:
    // - allowing blanket enforcement of CSP (about:blank, about:srcdoc, ...).
    // - MHTML document, not supported by CSPEE (https://crbug.com/1164353).
    if (CheckCSPEmbeddedEnforcement() ==
        CSPEmbeddedEnforcementResult::BLOCK_RESPONSE) {
      NOTREACHED();
    }

    // Likewise, these local-scheme / MHTML navigations always allow blanket
    // enforcement of an embedder-required Connection-Allowlist, so they are
    // never blocked here.
    if (CheckConnectionAllowlistEmbeddedEnforcement() ==
        ConnectionAllowlistEmbeddedEnforcementResult::BLOCK_RESPONSE) {
      NOTREACHED();
    }

    ComputePoliciesToCommit();

    // Same-document navigations occur in the currently loaded document. See
    // also RenderFrameHostManager::DidCreateNavigationRequest() which will
    // expect us to use the current RenderFrameHost for this NavigationRequest,
    // and https://crbug.com/1125106.
    if (IsSameDocument()) {
      render_frame_host_ = frame_tree_node_->current_frame_host()->GetSafeRef();

      // The SiteInstance should have a site already from the navigation that
      // committed the document, unless the scheme does not require a site. Same
      // document navigations cannot change scheme or origin, so it should be
      // equivalent to check the current vs destination UrlInfo.
      CHECK(render_frame_host_.value()->GetSiteInstance()->HasSite() ||
            !SiteInstanceImpl::ShouldAssignSiteForUrlInfo(GetUrlInfo()));

      WillCommitWithoutUrlLoader();
      return;
    } else {
      // [spec]: https://html.spec.whatwg.org/C/#process-a-navigate-response
      // 4. if [...] the result of checking a navigation response's adherence to
      // its embedder policy [...], then set failure to true.
      if (!CheckResponseAdherenceToCoep(common_params_->url)) {
        OnRequestFailedInternal(network::URLLoaderCompletionStatus(
                                    network::mojom::BlockedByResponseReason::
                                        kCoepFrameResourceNeedsCoepHeader),
                                false /* skip_throttles */,
                                std::nullopt /* error_page_content */,
                                false /* collapse_frame */);
        return;
        // DO NOT ADD CODE after this. The previous call to
        // OnRequestFailedInternal has destroyed the NavigationRequest.
      }

      // Enforce cross-origin-opener-policy for about:blank, about:srcdoc and
      // MHTML iframe, before selecting the RenderFrameHost.
      const url::Origin origin = GetOriginForURLLoaderFactoryUnchecked();
      net::SchemefulSite site = net::SchemefulSite(origin);

      coop_status_.EnforceCOOP(
          policy_container_builder_->FinalPolicies().cross_origin_opener_policy,
          origin,
          net::NetworkAnonymizationKey::CreateSameSite(std::move(site)));

      SelectFrameHostForCrossDocumentNavigationWithNoUrlLoader();
      return;
    }
  }

  base::UmaHistogramTimes(
      base::StrCat({"Navigation.BeginNavigationImpl.",
                    IsInMainFrame() ? "MainFrame" : "Subframe"}),
      timer.Elapsed());
  WillStartRequest();
  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

void NavigationRequest::
    SelectFrameHostForCrossDocumentNavigationWithNoUrlLoader() {
  CHECK(!NeedsUrlLoader());
  CHECK(!HasRenderFrameHost())
      << "`render_frame_host_` should not be set before the "
         "`NavigationRequest` starts to select the RFH.";

  if (auto result =
          frame_tree_node_->render_manager()->GetFrameHostForNavigation(
              this, &browsing_context_group_swap_,
              ProcessAllocationContext::CreateForNavigationRequest(
                  ProcessAllocationNavigationStage::kNoURLLoader,
                  navigation_id_, IsInOutermostMainFrame()));
      result.has_value()) {
    render_frame_host_ = result.value()->GetSafeRef();
  } else {
    switch (result.error()) {
      case GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame:
        // TODO(crbug.com/40250311): This was unhandled before and
        // remains explicitly unhandled. This branch may be removed in the
        // future.
        break;
      case GetFrameHostForNavigationFailed::kBlockedByPendingCommit:
        resume_commit_closure_ = base::BindOnce(
            &NavigationRequest::
                SelectFrameHostForCrossDocumentNavigationWithNoUrlLoader,
            weak_factory_.GetWeakPtr());
        frame_tree_node_->render_manager()
            ->speculative_frame_host()
            ->RecordMetricsForBlockedGetFrameHostAttempt(
                /* commit_attempt=*/true);
        return;
    }
  }

  CHECK(Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
      &*render_frame_host_.value(), GetUrlInfo(),
      /*is_renderer_initiated_check=*/false));

  auto* site_instance = render_frame_host_.value()->GetSiteInstance();
  if (!site_instance->HasSite() &&
      SiteInstanceImpl::ShouldAssignSiteForUrlInfo(GetUrlInfo())) {
    site_instance->ConvertToDefaultOrSetSite(GetUrlInfo());
  }

  WillCommitWithoutUrlLoader();
}

void NavigationRequest::SetWaitingForRendererResponse() {
  EnterChildTraceEvent("WaitingForRendererResponse", this);
  SetState(WAITING_FOR_RENDERER_RESPONSE);
}

bool NavigationRequest::ShouldAddCookieChangeListener() {
  // Cookies can only be set for http/http(s) URLs, so only create listeners
  // for those navigations. Also only create for non-activation cross-document
  // primary main frame navigations (or if
  // `ShouldAddDeviceBoundSessionObserver()` returns true, which allows
  // prerenders).
  return (!IsPageActivation() && !IsSameDocument() && IsInPrimaryMainFrame() &&
          common_params_->url.SchemeIsHTTPOrHTTPS()) ||
         ShouldAddDeviceBoundSessionObserver();
}

bool NavigationRequest::DidCookiesChangeAfterStart(
    bool exclude_http_only) const {
  CHECK(HasCookieChangeListener());
  if (exclude_http_only) {
    return cookie_change_listener_->cookie_change_info()
        .non_http_only_cookie_modification_count;
  }
  return cookie_change_listener_->cookie_change_info()
      .cookie_modification_count;
}

bool NavigationRequest::ShouldAddDeviceBoundSessionObserver() {
  // Only add if all of these are true:
  // (1) the navigation's protocol is HTTP(s).
  // (2) we allow a document with `Cache-control: no-store` header to
  // enter back/forward.
  // (3) the navigation is neither a same-document navigation nor a page
  // activation, since in these cases, an existing `RenderFrameHost` will be
  // used, and it would already have an existing listener, so we should skip the
  // initialization.
  // (4) the navigation is a primary main frame navigation or it's for
  // prerendering a main frame, as the device-bound session change information
  // will only be used to determine if a page can be restored from back/forward
  // cache, so subframe navigation can be ignored.
  return (IsInPrimaryMainFrame() || IsInPrerenderedMainFrame()) &&
         !IsPageActivation() && !IsSameDocument() &&
         common_params_->url.SchemeIsHTTPOrHTTPS() &&
         frame_tree_node_->current_frame_host()
             ->delegate()
             ->GetBackForwardCache()
             .should_allow_storing_pages_with_cache_control_no_store();
}

void NavigationRequest::StartNavigation() {
  TRACE_EVENT("navigation", "NavigationRequest::StartNavigation",
              perfetto::Flow::FromPointer(this));
  CHECK(frame_tree_node_->navigation_request() == this ||
        is_synchronous_renderer_commit_);
  FrameTreeNode* frame_tree_node = frame_tree_node_;

  MaybeAssignInvalidActivatingPrerenderHostId();

  // This is needed to get site URLs and assign the expected RenderProcessHost.
  // This is not always the same as |source_site_instance_|, as it only depends
  // on the current frame host, and does not depend on |entry|.
  // The |starting_site_instance_| needs to be set here instead of the
  // constructor since a navigation can be started after the constructor and
  // before here, which can set a different RenderFrameHost and a different
  // starting SiteInstance.
  starting_site_instance_ =
      frame_tree_node->current_frame_host()->GetSiteInstance();
  site_info_ = GetSiteInfoForCommonParamsURL();

  // It's important to start listening to the cookie changes before the network
  // request of the navigation begins in order to ensure the listener won't miss
  // any cookie changes that happen after the network request is sent that
  // potentially modify some cookie values that are used in this request.
  // The information of cookie modification will be used to determine if the
  // document that this navigation will load should be eligible for BFCache.
  // The listener eventually will be transferred over to the committed
  // `RenderFrameHost`.
  // The listener should receive the change events of the cookies from the
  // the domain of the main-frame navigation url.
  // If the navigation gets redirected, it will be reset with the new URL when
  // `NavigationRequest::OnRequestRedirected()` is called.
  if (ShouldAddCookieChangeListener()) {
    // The listener should receive the change events of the cookies from the
    // the domain of the main-frame navigation url.
    // If the navigation gets redirected, it will be reset with the new URL when
    // `NavigationRequest::OnRequestRedirected()` is called.
    cookie_change_listener_ =
        std::make_unique<RenderFrameHostImpl::CookieChangeListener>(
            GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
  }

  if (ShouldAddDeviceBoundSessionObserver()) {
    device_bound_session_observer_ =
        std::make_unique<RenderFrameHostImpl::DeviceBoundSessionObserver>(
            GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
  }

  // Compute the redirect chain.
  // TODO(clamy): Try to simplify this and have the redirects be part of
  // CommonNavigationParams.
  redirect_chain_.clear();
  if (!begin_params_->client_side_redirect_url.is_empty()) {
    // |begin_params_->client_side_redirect_url| will be set when the navigation
    // was triggered by a client-side redirect.
    redirect_chain_.push_back(begin_params_->client_side_redirect_url);
  } else if (!commit_params_->redirects.empty()) {
    // Redirects that were specified at NavigationRequest creation time should
    // be added to the list of redirects. In particular, if the
    // NavigationRequest was created at commit time, redirects that happened
    // during the navigation have been added to |commit_params_->redirects| and
    // should be passed to the NavigationHandle.
    for (const auto& url : commit_params_->redirects)
      redirect_chain_.push_back(url);
  }

  // Finally, add the current URL to the vector of redirects.
  // Note: for NavigationRequests created at commit time, the current URL has
  // been added to |commit_params_->redirects|, so don't add it a second time.
  if (!is_synchronous_renderer_commit_) {
    if (!common_params_->base_url_for_data_url.is_empty()) {
      // If this is a loadDataWithBaseURL/loadDataAsStringWithBaseUrl
      // navigation, use the base URL instead of the data: URL used for commit.
      redirect_chain_.push_back(common_params_->base_url_for_data_url);
    } else {
      redirect_chain_.push_back(common_params_->url);
    }
  }

  // Mirrors the logic in RenderFrameImpl::SendDidCommitProvisionalLoad.
  if (common_params_->transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
    // If the page contained a client redirect (meta refresh,
    // document.location), set the referrer appropriately.
    // Note that this value will stay the same, even after cross-origin
    // redirects. This means the referrer URL and policy in
    // `sanitized_referrer_` might not be the same as the actual referrer sent
    // for the final navigation request (which will be updated/re-sanitized on
    // each redirect).
    // TODO(crbug.com/40771822): Remove this special case, and also
    // `sanitized_referrer_` entirely, in favor of CommonNavigationParams'
    // `referrer`, which will be properly sanitized after each redirect.
    sanitized_referrer_ = blink::mojom::Referrer::New(
        redirect_chain_[0], Referrer::SanitizeForRequest(
                                common_params_->url, *common_params_->referrer)
                                ->policy);
  } else {
    sanitized_referrer_ = Referrer::SanitizeForRequest(
        common_params_->url, *common_params_->referrer);
  }

  // If the navigation explicitly requested for history list clearing (e.g. when
  // running layout tests), don't do a replacement (since there won't be any
  // entry to replace after the navigation).
  if (commit_params_->should_clear_history_list) {
    common_params_->should_replace_current_entry = false;
  } else if (
      ShouldReplaceCurrentEntryForSameUrlNavigation() ||
      ShouldReplaceCurrentEntryForNavigationFromInitialEmptyDocumentOrEntry()) {
    common_params_->should_replace_current_entry = true;
  }

  // Set the expected process for this navigation, if we can. The navigation
  // might not have an associated RenderFrameHost yet, which is possible if it
  // can't create a speculative RenderFrameHost when there's a pending commit
  // navigation (when navigation queueing is enabled), or it had an associated
  // RenderFrameHost when the NavigationRequest was created but another
  // navigation had committed in between that time and StartNavigation, which
  // invalidates the `associated_rfh_type_`, or it's intentionally deferred with
  // feature flag DeferSpeculativeRFHCreation. It's fine to skip setting the
  // expected process in this case, as we'll set the expected process again from
  // ReadyToCommitNavigation(), when we know the final RenderFrameHost for the
  // navigation.
  SetExpectedProcessIfAssociated();

  CHECK(!IsNavigationStarted());
  SetState(WILL_START_REQUEST);
  is_navigation_started_ = true;

  headers_update_params_.Clear();

  throttle_registry_ = std::make_unique<NavigationThrottleRegistryImpl>(this);

  // For prerendered page activation, CommitDeferringConditions have already run
  // at the beginning of the navigation, so we won't run them again.
  // ProcessSelectionDeferringConditions also don't need to run for prerendered
  // page activations because those have already selected a process.
  // For initial WebUI navigations, CommitDeferringConditions and
  // ProcessSelectionDeferringConditions are not run, so that the navigation can
  // run synchronously from start to commit.
  if (!IsPrerenderedPageActivation() && !IsInitialWebUINavigation()) {
    commit_deferrer_ = CommitDeferringConditionRunner::Create(
        *this, CommitDeferringCondition::NavigationType::kOther,
        /*candidate_prerender_frame_tree_node_id=*/std::nullopt);
    if (base::FeatureList::IsEnabled(
            features::kProcessSelectionDeferringConditions)) {
      process_selection_deferrer_ =
          ProcessSelectionDeferringConditionRunner::Create(*this);
    }
  }

  navigation_visible_to_embedder_ = true;
#if BUILDFLAG(IS_ANDROID)
  // Once the navigation has started, fill in the details in the Java side
  // navigation handle.
  navigation_handle_proxy_->DidStart();
#endif

  if (IsSameDocument()) {
    EnterChildTraceEvent("Same document", this);
  }

  {
#if !BUILDFLAG(IS_ANDROID)
    {
      CHECK(is_safe_to_delete_);
      base::AutoReset<bool> resetter(&is_safe_to_delete_, false);
      base::AutoReset<bool> resetter2(&ua_change_requires_reload_, false);
      GetDelegate()->DidStartNavigation(this);
    }
#else
    // Since there's no `is_safe_to_delete_` check, do a manual
    // `ua_change_requires_reload_` reset so that AutoReset does not write to a
    // deleted object if observers of DidStartNavigation cancel this
    // NavigationRequest.
    const bool saved_ua_change_requires_reload = ua_change_requires_reload_;
    ua_change_requires_reload_ = false;
    base::WeakPtr<NavigationRequest> weak_this = weak_factory_.GetWeakPtr();
    GetDelegate()->DidStartNavigation(this);
    if (weak_this) {
      ua_change_requires_reload_ = saved_ua_change_requires_reload;
    }
#endif
  }
}

void NavigationRequest::ResetForCrossDocumentRestart() {
  CHECK(IsSameDocument());

  // TODO(crbug.com/40055210): A same document history navigation was performed
  // but the renderer thinks there's a different document loaded. Where did
  // this navigation come from?
  if (common_params_->navigation_type ==
      blink::mojom::NavigationType::HISTORY_SAME_DOCUMENT) {
    CaptureTraceForNavigationDebugScenario(
        DebugScenario::kDebugSameDocNavigationDocIdMismatch);
  }

  // Reset the NavigationHandle, which is now incorrectly marked as
  // same-document. Ensure |loader_| does not exist as it can hold raw pointers
  // to objects owned by the handle (see the comment in the header).
  CHECK(!loader_);

#if BUILDFLAG(IS_ANDROID)
  if (navigation_visible_to_embedder_)
    navigation_handle_proxy_->DidFinish();
#endif

  // Set this bit so the observers on `DidFinishNavigation()` are also aware of
  // the restart.
  was_reset_for_cross_document_restart_ = true;

  // It is necessary to call DidFinishNavigation before resetting
  // |navigation_handle_proxy_|. See https://crbug.com/958396.
  if (IsNavigationStarted()) {
    GetDelegate()->DidFinishNavigation(this);
    TRACE_EVENT_END("navigation", breakdown_trace_track_, "URL",
                    common_params_->url.spec(), "Net Error Code",
                    net_error_);  // Navigation StartToCommit
    navigation_start_to_commit_ended_ = true;
  }

  // Reset the state of the NavigationRequest, and the navigation_handle_id.
  StopCommitTimeout();
  if (fast_fetch_manager_) {
    fast_fetch_manager_->SuppressEligibilityReasonRecording();
    fast_fetch_manager_.reset();
  }
  SetState(NOT_STARTED);
  is_navigation_started_ = false;
  processing_navigation_throttle_ = false;

  navigation_visible_to_embedder_ = false;
#if BUILDFLAG(IS_ANDROID)
  if (navigation_handle_proxy_) {
    navigation_handle_proxy_.reset();
    navigation_handle_proxy_ = std::make_unique<NavigationHandleProxy>(this);
  }
#endif

  // Reset the previously selected RenderFrameHost. This is expected to be null
  // at the beginning of a new navigation. See https://crbug.com/936962.
  CHECK(HasRenderFrameHost());
  render_frame_host_ = std::nullopt;

  // Convert the navigation type to the appropriate cross-document one.
  common_params_->navigation_type =
      ConvertToCrossDocumentType(common_params_->navigation_type);

  // Reset navigation handle timings.
  navigation_handle_timing_ = NavigationHandleTiming();

  policy_container_builder_->ResetForCrossDocumentRestart();
  commit_params_->soft_navigation_heuristics_task_id = std::nullopt;

  CheckSoftNavigationHeuristicsInvariants();
}

void NavigationRequest::ResetStateForSiteInstanceChange() {
  // This method should only be called when there is a dest_site_instance.
  CHECK(dest_site_instance_);

  // When a request has a destination SiteInstance (e.g., reload or session
  // history navigation) but it changes during the navigation (e.g., due to
  // redirect or error page), it's important not to remember privileges or
  // attacker-controlled state from the original entry.

  // Reset bindings (e.g., since error pages for WebUI URLs don't get them).
  bindings_.reset();

  // Reset any existing *and* non-empty PageState with a clean PageState, so
  // that old attacker-controlled state is not pulled into the new process.
  // Note that `PageState::IsValid()` is really just "is the encoded page state
  // non-empty", so the net effect of this is "replace a non-empty encoded page
  // state with a fresh encoded page state".
  blink::PageState page_state = blink::PageState::CreateFromEncodedData(
      std::move(commit_params_->page_state));
  if (page_state.IsValid())
    commit_params_->page_state =
        blink::PageState::CreateFromURL(GetURL()).ToEncodedData();

  // Clear any state specific to the origin, such as ISNs and DSNs.
  origin_related_state_.reset();

  // If this was not a redirect that preserves POST submissions (e.g., 307), or
  // if this will be an error page that may end up in another process, then
  // clear the post_data as well to prevent leaking file references to a
  // different SiteInstance.
  if (!IsPost() || DidEncounterError()) {
    common_params_->post_data.reset();
  }
}

void NavigationRequest::RegisterSubresourceOverride(
    blink::mojom::TransferrableURLLoaderPtr transferrable_loader) {
  if (!transferrable_loader)
    return;
  if (!subresource_overrides_)
    subresource_overrides_.emplace();

  subresource_overrides_->push_back(std::move(transferrable_loader));
}

mojom::NavigationClient* NavigationRequest::GetCommitNavigationClient() {
  if (commit_navigation_client_ && commit_navigation_client_.is_bound())
    return commit_navigation_client_.get();

  // Instantiate a new NavigationClient interface.
  commit_navigation_client_ =
      GetRenderFrameHost()->GetNavigationClientFromInterfaceProvider();
  HandleInterfaceDisconnection(commit_navigation_client_);
  return commit_navigation_client_.get();
}

void NavigationRequest::SetRequiredCSP(
    network::mojom::ContentSecurityPolicyPtr csp) {
  CHECK(!required_csp_);
  required_csp_ = std::move(csp);
  if (required_csp_)
    SetRequestHeader("Sec-Required-CSP", required_csp_->header->header_value);
}

network::mojom::ContentSecurityPolicyPtr NavigationRequest::TakeRequiredCSP() {
  return std::move(required_csp_);
}

std::optional<network::ConnectionAllowlist>
NavigationRequest::TakeRequiredConnectionAllowlist() {
  return std::move(required_connection_allowlist_);
}

const PolicyContainerPolicies*
NavigationRequest::GetInitiatorPolicyContainerPolicies() const {
  return initiator_navigation_state_impl()
             ? &(initiator_navigation_state_impl()->policy_container_policies())
             : nullptr;
}

const PolicyContainerPolicies*
NavigationRequest::GetInitiatorPolicyContainerPoliciesForInheritance() const {
  // This function cannot be called before the navigation has started, as the
  // StoragePartition for the navigation will only be valid after
  // StartNavigation has properly computed the SiteInfo based on the current
  // RenderFrameHost and the navigation params. Before that, a default
  // StoragePartition will be returned by GetStoragePartition() when using
  // `site_info_`. Using it in the checks below would result in always dropping
  // the initiator policies in navigations happening in a non-default
  // StoragePartition, which is wrong.
  CHECK(state_ >= WILL_START_REQUEST);

  // If there is no navigation initiator, return null.
  if (!initiator_navigation_state_) {
    return nullptr;
  }

  // If there is an initiator, check if its policies can be inherited. If they
  // cannot, return null.

  // The NavigationRequest creator might have specified that initiator policies
  // cannot be inherited.
  if (should_ignore_initiator_policies_for_inheritance_) {
    return nullptr;
  }

  // If the StoragePartition of the navigation is different from the
  // StoragePartition of the initiator, the initiator's policies should not be
  // inherited.
  if (site_info_.GetStoragePartitionConfig() !=
      initiator_navigation_state_impl()
          ->site_instance()
          ->GetSiteInfo()
          .GetStoragePartitionConfig()) {
    return nullptr;
  }

  return &(initiator_navigation_state_impl()->policy_container_policies());
}

const blink::DocumentToken& NavigationRequest::GetDocumentToken() const {
  CHECK(!IsSameDocument());
  CHECK_GE(state_, READY_TO_COMMIT);

  return *document_token_;
}

const std::optional<blink::DocumentToken>&
NavigationRequest::GetInitiatorDocumentToken() const {
  return initiator_document_token_;
}

const PolicyContainerPolicies& NavigationRequest::GetPolicyContainerPolicies()
    const {
  CHECK(state_ >= WILL_PROCESS_RESPONSE &&
        policy_container_builder_->HasComputedPolicies());

  return policy_container_builder_->FinalPolicies();
}

blink::mojom::PolicyContainerPtr
NavigationRequest::CreatePolicyContainerForBlink() {
  CHECK_GE(state_, READY_TO_COMMIT);

  // From here on, the PolicyContainer in Blink might attempt to modify the
  // policies and update the `initiator_state_token`. Register the
  // NavigationRequest as client of the PolicyContainerHost to be notified about
  // such changes and update the `initiator_state_token_to_commit`.
  policy_container_builder_->GetPolicyContainerHost()->SetClient(this);

  return policy_container_builder_->CreatePolicyContainerForBlink();
}
scoped_refptr<PolicyContainerHost> NavigationRequest::GetPolicyContainerHost() {
  CHECK_GE(state_, READY_TO_COMMIT);
  // It is invalid calling this method after `TakePolicyContainerHost()`.
  CHECK(policy_container_builder_);
  return policy_container_builder_->GetPolicyContainerHost();
}

scoped_refptr<PolicyContainerHost>
NavigationRequest::TakePolicyContainerHost() {
  CHECK_GE(state_, READY_TO_COMMIT);

  // Move the host out of the data member, then reset the member. This ensures
  // we do not use the helper after we moved its contents.
  scoped_refptr<PolicyContainerHost> host =
      std::move(*policy_container_builder_).TakePolicyContainerHost();
  policy_container_builder_ = std::nullopt;

  // Ensure the NavigationRequest is no longer the client of the
  // PolicyContainerHost.
  host->SetClient(nullptr);

  return host;
}

void NavigationRequest::CreateCoepReporter(
    StoragePartition* storage_partition) {
  CHECK(!isolation_info_for_subresources_.IsEmpty());

  const PolicyContainerPolicies& policies =
      policy_container_builder_->FinalPolicies();
  coep_reporter_ = std::make_unique<CrossOriginEmbedderPolicyReporter>(
      static_cast<StoragePartitionImpl*>(storage_partition)->GetWeakPtr(),
      common_params_->url,
      policies.cross_origin_embedder_policy.reporting_endpoint,
      policies.cross_origin_embedder_policy.report_only_reporting_endpoint,
      GetRenderFrameHost()->GetFrameToken().value(),
      isolation_info_for_subresources_.network_anonymization_key());
}

std::unique_ptr<CrossOriginEmbedderPolicyReporter>
NavigationRequest::TakeCoepReporter() {
  return std::move(coep_reporter_);
}

void NavigationRequest::CreateDipReporter(StoragePartition* storage_partition) {
  CHECK(!isolation_info_for_subresources_.IsEmpty());

  const PolicyContainerPolicies& policies =
      policy_container_builder_->FinalPolicies();
  dip_reporter_ = std::make_unique<DocumentIsolationPolicyReporter>(
      static_cast<StoragePartitionImpl*>(storage_partition)->GetWeakPtr(),
      common_params_->url,
      policies.document_isolation_policy.reporting_endpoint,
      policies.document_isolation_policy.report_only_reporting_endpoint,
      GetRenderFrameHost()->GetFrameToken().value(),
      isolation_info_for_subresources_.network_anonymization_key());
}

std::unique_ptr<DocumentIsolationPolicyReporter>
NavigationRequest::TakeDipReporter() {
  return std::move(dip_reporter_);
}

ukm::SourceId NavigationRequest::GetPreviousPageUkmSourceId() {
  return previous_page_ukm_source_id_;
}

bool NavigationRequest::ShouldClearParsedHeadersOnTestReceiveRedirect() {
  // This method is only for testing and thus must do nothing and return `false`
  // here.
  return false;
}

void NavigationRequest::OnRequestRedirected(
    const net::RedirectInfo& redirect_info,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    network::mojom::URLResponseHeadPtr response_head) {
  TRACE_EVENT("navigation", "NavigationRequest::OnRequestRedirected",
              perfetto::Flow::FromPointer(this));
  ScopedCrashKeys crash_keys(*this);

  if (!was_redirected_ &&
      !IsAllowedByConnectionAllowlist(/*is_redirect=*/true)) {
    // As in BeginNavigationImpl(), record that this navigation is blocked by
    // the initiator's Connection-Allowlist so observers can suppress
    // speculative network activity for it. (Today no speculative work runs
    // after a blocked redirect, but this keeps IsBlockedByConnectionAllowlist()
    // truthful for any future post-redirect consumer.)
    is_blocked_by_connection_allowlist_ = true;
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT);
    error_navigation_trigger_ = ErrorNavigationTrigger::kRedirectNotAllowed;
    OnRequestFailedInternal(completion_status, false /* skip_throttles */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  // Sanity check - this can only be set at commit time.
  CHECK(!auth_challenge_info_);

  CHECK(response_head);
  CHECK(response_head->parsed_headers);
  response_head_ = std::move(response_head);
  ssl_info_ = response_head_->ssl_info;
  BrowserContext* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();

  if (SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel(
          browser_context, GetURL())) {
    GetContentClient()->browser()->EnsureRequiredHeadersForIsolatedApp(
        browser_context, GetURL(), response_head_.get(),
        frame_tree_node_->frame_tree_node_id());
  }

  // Reset the page state as it can no longer be used at commit time since the
  // navigation was redirected.
  commit_params_->page_state = std::string();

  // Reset NotRestoredReasons as the reasons are for the original page and not
  // for the redirected one.
  commit_params_->not_restored_reasons = nullptr;

  // Reset the internal scroll-to-text-fragment as it was generated for the
  // original page and must not be applied to a (potentially cross-origin)
  // redirect target.
  commit_params_->internal_scroll_to_text_fragment = std::nullopt;

  // Reset the tentative origin_to_commit, as the redirected one is different.
  tentative_data_origin_to_commit_ = std::nullopt;

  // A request was made. Record it before we decide to block this response for
  // a reason or another.
  RecordAddressSpaceFeature();

#if BUILDFLAG(IS_ANDROID)
  base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());

  bool should_override_url_loading = false;
  if (!GetContentClient()->browser()->ShouldOverrideUrlLoading(
          frame_tree_node_->frame_tree_node_id(),
          commit_params_->is_browser_initiated, redirect_info.new_url,
          redirect_info.new_method,
          // Redirects are always not counted as from user gesture.
          false, true, frame_tree_node_->IsOutermostMainFrame(),
          frame_tree_node_->frame_tree().is_prerendering(),
          ui::PageTransitionFromInt(common_params_->transition),
          &should_override_url_loading)) {
    // A Java exception was thrown by the embedding application; we
    // need to return from this task. Specifically, it's not safe from
    // this point on to make any JNI calls.
    return;
  }

  // The content/ embedder might cause |this| to be deleted while
  // |ShouldOverrideUrlLoading| is called.
  // See https://crbug.com/770157.
  if (!this_ptr)
    return;

  if (should_override_url_loading) {
    net_error_ = net::ERR_ABORTED;
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kShouldOverrideUrlLoading;
    common_params_->url = redirect_info.new_url;
    common_params_->method = redirect_info.new_method;
    // Update the navigation handle to point to the new url to ensure
    // AwWebContents sees the new URL and thus passes that URL to onPageFinished
    // (rather than passing the old URL).
    UpdateStateFollowingRedirect(GURL(redirect_info.new_referrer));
    frame_tree_node_->ResetNavigationRequest(
        NavigationDiscardReason::kInternalCancellation);
    return;
  }
#endif
  if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRedirectToURL(
          redirect_info.new_url)) {
    DVLOG(1) << "Denied redirect for "
             << redirect_info.new_url.possibly_invalid_spec();
    // TODO(arthursonzogni): Redirect to a javascript URL should display an
    // error page with the net::ERR_UNSAFE_REDIRECT error code. Instead, the
    // browser simply ignores the navigation, because some extensions use this
    // edge case to silently cancel navigations. See https://crbug.com/941653.
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_ABORTED);
    error_navigation_trigger_ = ErrorNavigationTrigger::kRedirectNotAllowed;
    OnRequestFailedInternal(completion_status, false /* skip_throttles */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  // For renderer-initiated navigations we need to check if the source has
  // access to the URL. Browser-initiated navigations only rely on the
  // |CanRedirectToURL| test above.
  // TODO(crbug.com/388998723): The check may unintentionally create a process
  // for the source site instance. Consider removing this potential process
  // creation while keeping the security check.
  if (!commit_params_->is_browser_initiated && GetSourceSiteInstance() &&
      !ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(
          GetSourceSiteInstance()
              ->GetOrCreateProcess(ProcessAllocationContext{
                  ProcessAllocationSource::kCanRequestURL})
              ->GetDeprecatedID(),
          redirect_info.new_url)) {
    DVLOG(1) << "Denied unauthorized redirect for "
             << redirect_info.new_url.possibly_invalid_spec();
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_ABORTED);
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kRendererInitiatedCanNotRequestURL;
    // TODO(arthursonzogni): This case uses ERR_ABORTED to be consistent with
    // the javascript URL redirect case above, though ideally it would use
    // net::ERR_UNSAFE_REDIRECT and an error page. See https://crbug.com/941653.
    OnRequestFailedInternal(completion_status, false /* skip_throttles */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  const std::optional<network::mojom::BlockedByResponseReason>
      coop_requires_blocking =
          coop_status_.SanitizeResponse(response_head_.get());
  if (coop_requires_blocking) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(*coop_requires_blocking),
        false /* skip_throttles */, std::nullopt /* error_page_content */,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }
  const url::Origin origin = GetOriginForURLLoaderFactoryUnchecked();
  coop_status_.EnforceCOOP(
      response()->parsed_headers->cross_origin_opener_policy, origin,
      network_anonymization_key);

  const std::optional<network::mojom::BlockedByResponseReason>
      coep_requires_blocking = EnforceCOEP();
  if (coep_requires_blocking) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(*coep_requires_blocking),
        false /* skip_throttles */, std::nullopt /* error_page_content */,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }

  // If the navigation is no longer a POST, the POST data should be reset.
  if (redirect_info.new_method != "POST") {
    common_params_->post_data.reset();
  }

  const bool is_first_response = commit_params_->redirects.empty();
  UpdateNavigationHandleTimingsOnResponseReceived(/*is_redirect=*/true,
                                                  is_first_response);

  // Mark time for the Navigation Timing API.
  if (commit_params_->navigation_timing->redirect_start.is_null()) {
    commit_params_->navigation_timing->redirect_start =
        commit_params_->navigation_timing->fetch_start;
  }
  commit_params_->navigation_timing->redirect_end = base::TimeTicks::Now();
  commit_params_->navigation_timing->fetch_start = base::TimeTicks::Now();

  commit_params_->redirect_params.emplace_back(
      blink::mojom::NavigationRedirectParams::New(redirect_info,
                                                  response_head_.Clone()));

  // TODO(rakina): This should use `GetTentativeOriginAtRequestTime()` instead
  // of the url from `common_params_`.
  const bool is_same_origin_redirect =
      url::Origin::Create(common_params_->url)
          .IsSameOriginWith(redirect_info.new_url);

  did_encounter_cross_origin_redirect_ |= !is_same_origin_redirect;

  // Reset the LCPP hint as the hint is for the original page and not for the
  // redirected one. Only for cross-origin redirects.
  if (!is_same_origin_redirect) {
    commit_params_->lcpp_hint = nullptr;
  }

  // Only same-origin navigations without cross-origin redirects can
  // expose response details (status-code / mime-type).
  // https://github.com/whatwg/fetch/issues/1602
  if (!is_same_origin_redirect &&
      commit_params_->navigation_timing->parent_resource_timing_access ==
          blink::mojom::ParentResourceTimingAccess::
              kReportWithResponseDetails) {
    commit_params_->navigation_timing->parent_resource_timing_access =
        blink::mojom::ParentResourceTimingAccess::kReportWithoutResponseDetails;
  }

  did_receive_early_hints_before_cross_origin_redirect_ |=
      did_create_early_hints_manager_params_ && !is_same_origin_redirect;

  commit_params_->redirects.push_back(common_params_->url);
  common_params_->url = redirect_info.new_url;
  common_params_->method = redirect_info.new_method;
  common_params_->referrer->url = GURL(redirect_info.new_referrer);
  common_params_->referrer = Referrer::SanitizeForRequest(
      common_params_->url, *common_params_->referrer);

  // On redirects, the initial referrer is no longer correct, so it must
  // be updated.  (A parallel process updates the outgoing referrer in the
  // network stack.)
  commit_params_->redirect_params.back()->redirect_info.new_referrer =
      common_params_->referrer->url.spec();

  // When the redirection happens, the cookie_change_listener_ should be
  // re-initialized if needed.
  if (ShouldAddCookieChangeListener()) {
    cookie_change_listener_ =
        std::make_unique<RenderFrameHostImpl::CookieChangeListener>(
            GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
  } else {
    cookie_change_listener_.reset();
  }

  if (ShouldAddDeviceBoundSessionObserver()) {
    device_bound_session_observer_ =
        std::make_unique<RenderFrameHostImpl::DeviceBoundSessionObserver>(
            GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
  } else {
    device_bound_session_observer_.reset();
  }

  // Check Content Security Policy before the NavigationThrottles run. This
  // gives CSP a chance to modify requests that NavigationThrottles would
  // otherwise block.
  net::Error net_error =
      CheckContentSecurityPolicy(true /* has_followed_redirect */,
                                 redirect_info.insecure_scheme_was_upgraded,
                                 false /* is_response_check */);
  if (net_error != net::OK) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net_error), false /*skip_throttles*/,
        std::nullopt /*error_page_content*/, false /*collapse_frame*/);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (CheckCredentialedSubresource() ==
      CredentialedSubresourceCheckResult::BLOCK_REQUEST) {
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_ABORTED);
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kCredentialedSubresourceBlocked;
    OnRequestFailedInternal(completion_status, false /*skip_throttles*/,
                            std::nullopt /*error_page_content*/,
                            false /*collapse_frame*/);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (process_selection_deferrer_) {
    // Start any process selection dependencies using the redirected URL.
    process_selection_deferrer_->OnRequestRedirected();
  }

  // Compute the SiteInstance to use for the redirect and pass its
  // RenderProcessHost if it has a process. Keep a reference if it has a
  // process, so that the SiteInstance and its associated process aren't deleted
  // before the navigation is ready to commit.
  scoped_refptr<SiteInstance> site_instance =
      frame_tree_node_->render_manager()->GetSiteInstanceForNavigationRequest(
          this, &browsing_context_group_swap_);
  speculative_site_instance_ =
      site_instance->HasProcess() ? site_instance : nullptr;

  // If the new site instance doesn't yet have a process, then tell the
  // SpareRenderProcessHostManager so it can decide whether to start warming up
  // the spare at this time (note that the actual behavior depends on
  // RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes).
  if (!site_instance->HasProcess()) {
    RenderProcessHostImpl::NotifySpareManagerAboutRecentlyUsedSiteInstance(
        site_instance.get());
  }

  // Check what the process of the SiteInstance is. It will be passed to the
  // NavigationHandle, and informed to expect a navigation to the redirected
  // URL.
  // TODO(crbug.com/388998723): Remove the comment about the side effect
  // of GetProcess() after the full migration to GetOrCreateProcess().
  // Note: calling GetProcess on the SiteInstance can lead to the creation of a
  // new process if it doesn't have one. In this case, it should only be called
  // on a SiteInstance that already has a process.
  RenderProcessHost* expected_process =
      site_instance->HasProcess() ? site_instance->GetProcess() : nullptr;

  WillRedirectRequest(common_params_->referrer->url, expected_process);
}

base::WeakPtr<NavigationRequest> NavigationRequest::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

base::SafeRef<NavigationHandle> NavigationRequest::GetSafeRef() {
  return weak_factory_.GetSafeRef();
}

bool NavigationRequest::ExistingDocumentWasDiscarded() const {
  return commit_params_->was_discarded;
}

void NavigationRequest::SetContentSettings(
    blink::mojom::RendererContentSettingsPtr content_settings) {
  commit_params_->content_settings = std::move(content_settings);
}

blink::mojom::RendererContentSettingsPtr
NavigationRequest::GetContentSettingsForTesting() {
  return commit_params_->content_settings->Clone();
}

void NavigationRequest::SetIsAdTagged() {
  if (ad_status_ == AdStatus::kNone) {
    ad_status_ = AdStatus::kAdTagged;
  }
}

void NavigationRequest::SetIsAdTaggedByHostFilter() {
  ad_status_ = AdStatus::kAdTaggedByHostFilter;
}

bool NavigationRequest::IsAdTaggedByHostFilter() const {
  return ad_status_ == AdStatus::kAdTaggedByHostFilter;
}

bool NavigationRequest::HasSameSiteAdAncestor() {
  if (!base::FeatureList::IsEnabled(features::kExcludeAdsFromOriginIsolation)) {
    return false;
  }

  std::optional<url::Origin> target_origin =
      state_ < WILL_PROCESS_RESPONSE ? GetTentativeOriginAtRequestTime()
                                     : GetOriginToCommit();
  if (!target_origin.has_value()) {
    return false;
  }

  for (RenderFrameHostImpl* ancestor_rfh = frame_tree_node_->parent();
       ancestor_rfh; ancestor_rfh = ancestor_rfh->GetParent()) {
    if (!ancestor_rfh->IsAdFrame()) {
      continue;
    }

    // Check if the ancestor was forced site-keyed by default. Under
    // OriginKeyedProcessesByDefault, kSiteKeyedByDefault is assigned to an ad
    // frame only when its URL matched the ad host filter list (which excludes
    // script-tagged ads).
    const SiteInfo& ancestor_site_info =
        ancestor_rfh->GetSiteInstance()->GetSiteInfo();
    if (ancestor_site_info.agent_cluster_key().oac_status() !=
        AgentClusterKey::OACStatus::kSiteKeyedByDefault) {
      continue;
    }

    const url::Origin& ancestor_origin = ancestor_rfh->GetLastCommittedOrigin();
    if (net::SchemefulSite::IsSameSite(ancestor_origin,
                                       target_origin.value())) {
      return true;
    }
  }

  return false;
}

void NavigationRequest::CheckForIsolationOptIn(const GURL& url) {
  // Check whether an origin-keyed agent cluster is explicitly requested, either
  // opting in or out, before attempting to isolate it. If an explicit request
  // was made, then we must check if the origin has been previously
  // encountered in order to remain consistent within the isolation context
  // (BrowserContext). Note: we only do the global walk for explicit opt-outs
  // when OriginAgentCluster-by-default is enabled, but that check is made in
  // IsOriginAgentClusterOptOutRequested().
  if (!IsOriginAgentClusterOptInRequested() &&
      !IsOriginAgentClusterOptOutRequested())
    return;

  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
  url::Origin origin = url::Origin::Create(url);
  auto* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();
  if (policy->RecordOriginAgentClusterRequestIfNew(browser_context, origin)) {
    // This is a new request for isolating |origin|, either by explicitly opting
    // it in or out. Do a global walk of session history to find any existing
    // instances of |origin|, so that those existing BrowsingInstances can give
    // it default isolation. Only new BrowsingInstances and ones that have not
    // seen |origin| before will honor the request. We don't always have a value
    // for render_frame_host_ at this point, so we map the global-walk call onto
    // NavigatorDelegate to get it into WebContents. We definitely need to do
    // the global walk prior to deciding on the render_frame_host_ to commit to.
    // We must exclude ourselves from the global walk otherwise we may mark our
    // origin as having default isolation before it gets the change to register
    // itself as opted-in/out.
    frame_tree_node_->navigator()
        .GetDelegate()
        ->RegisterExistingOriginAsHavingDefaultIsolation(
            origin, this /* navigation_request_to_exclude */);
  }
}

void NavigationRequest::AddOriginAgentClusterStateIfNecessary(
    const IsolationContext& isolation_context) {
  std::optional<OriginAgentClusterIsolationState> oac_isolation_state;
  if (IsOriginAgentClusterOptInRequested()) {
    // Normally for explicit opt-ins the origin is tracked when we create the
    // SiteInstance, but there are two cases where that fails. (1) If process-
    // isolation for OAC is not enabled we need to track opt-in here (used for
    // origin-agent-cluster-by-default), and (2) if origin-keyed processes by
    // default is enabled, then it's possible we got here due to using a
    // speculative RenderFrameHost. In this latter case, the opt-in header had
    // not arrived when the SiteInstance was created, so the origin was not
    // tracked earlier.
    oac_isolation_state =
        OriginAgentClusterIsolationState::CreateForOriginAgentCluster(
            /*had_oac_request=*/true,
            SiteIsolationPolicy::
                IsProcessIsolationForOriginAgentClusterEnabled());
  } else if (IsOriginAgentClusterOptOutRequested()) {
    // Since opt-outs are asking not to have OAC or
    // requires_origin_keyed_process, they don't get their own SiteInstance, and
    // so we must register their opt-out here.
    oac_isolation_state =
        OriginAgentClusterIsolationState::CreateNonIsolatedByHeader();
  }

  if (!oac_isolation_state.has_value() && response() &&
      (IsAdTaggedByHostFilter() || HasSameSiteAdAncestor()) &&
      SiteIsolationPolicy::AreOriginAgentClustersEnabledByDefault(
          isolation_context.browser_context()) &&
      SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
          isolation_context.browser_context()) &&
      base::FeatureList::IsEnabled(features::kExcludeAdsFromOriginIsolation)) {
    // If it's an ad frame, OAC is on by default, and we know that there isn't
    // an OAC response header, force the eventual process to be site-keyed for
    // process isolation for this BrowsingInstance, bypassing the default
    // origin-keyed behavior. Note that we leave the origin agent cluster
    // logically origin-keyed to avoid web-visible changes to the OAC state.
    oac_isolation_state =
        OriginAgentClusterIsolationState::CreateForOriginAgentCluster(
            /*had_oac_request=*/false,
            /*requires_origin_keyed_process=*/false);
  }

  if (!oac_isolation_state.has_value()) {
    return;
  }

  // We never register isolation state for default states, only for states
  // explicitly requested by the document or decided by browser policy (e.g. for
  // ad frames).
  CHECK(
      oac_isolation_state->logical_oac_status() ==
          AgentClusterKey::OACStatus::kSiteKeyedByHeader ||
      oac_isolation_state->logical_oac_status() ==
          AgentClusterKey::OACStatus::kOriginKeyedByHeader ||
      (base::FeatureList::IsEnabled(features::kExcludeAdsFromOriginIsolation) &&
       oac_isolation_state->logical_oac_status() ==
           AgentClusterKey::OACStatus::kOriginKeyedByDefault));

  // Note: we don't handle IsIsolationImplied() cases here, since those only
  // occur when OAC-by-default is enabled, and in that case we only proactively
  // record explicit opt-ins and opt-outs. Implicitly isolated origins only end
  // up recorded if a future request from the same origin attempts to opt-in or
  // opt-out, which would trigger a normal global walk and record that the
  // origin has already been implicitly isolated in some BrowsingInstances.

  // TODO(crbug.com/40910871): investigate using one of NavigationRequest's
  // Get*Origin*() functions to compute this, instead of assuming we can just
  // convert directly from GetURL().
  url::Origin origin = url::Origin::Create(GetURL());
  // Since this origin is using a site-keyed process (either because
  // origin-keyed processes are disabled, not used for this origin, or the
  // origin has opted out), we can't rely on a newly created SiteInstance to add
  // the origin as OAC/not-OAC, so we do it manually here.
  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
  // If there is already a state registered for `origin` in `isolation_context`,
  // then the following call does nothing.
  policy->AddOriginAgentClusterStateForBrowsingInstance(
      isolation_context, origin, oac_isolation_state.value());
}

bool NavigationRequest::IsOriginAgentClusterOptInRequested() {
  // We explicitly do not honor Origin-Agent-Cluster headers in redirects and
  // may only consider them in final responses, according to spec.
  // https://crbug.com/1329061
  if (state_ < WILL_PROCESS_RESPONSE || state_ == WILL_FAIL_REQUEST) {
    return false;
  }

  if (!response()) {
    return false;
  }

  // Do not attempt isolation if the feature is not enabled.
  if (!SiteIsolationPolicy::IsOriginAgentClusterEnabled()) {
    return false;
  }

  return response_head_->parsed_headers->origin_agent_cluster ==
         network::mojom::OriginAgentClusterValue::kTrue;
}

bool NavigationRequest::IsOriginAgentClusterOptOutRequested() {
  // We explicitly do not honor Origin-Agent-Cluster headers in redirects and
  // may only consider them in final responses, according to spec.
  // https://crbug.com/1329061
  if (state_ < WILL_PROCESS_RESPONSE || state_ == WILL_FAIL_REQUEST) {
    return false;
  }

  if (!response()) {
    return false;
  }

  // We only allow explicit opt-outs when OAC-by-default is enabled. The
  // following check will be false if IsOriginAgentClusterEnabled() is false.
  if (!SiteIsolationPolicy::AreOriginAgentClustersEnabledByDefault(
          frame_tree_node_->navigator().controller().GetBrowserContext())) {
    return false;
  }

  return response_head_->parsed_headers->origin_agent_cluster ==
         network::mojom::OriginAgentClusterValue::kFalse;
}

bool NavigationRequest::IsIsolationImplied() {
  if (!SiteIsolationPolicy::AreOriginAgentClustersEnabledByDefault(
          frame_tree_node_->navigator().controller().GetBrowserContext())) {
    return false;
  }

  return !response() || response_head_->parsed_headers->origin_agent_cluster ==
                            network::mojom::OriginAgentClusterValue::kAbsent;
}

void NavigationRequest::DetermineAgentClusterKeyForCommit() {
  CHECK(state_ == WILL_PROCESS_RESPONSE ||
        state_ == WILL_COMMIT_WITHOUT_URL_LOADER ||
        state_ == WILL_FAIL_REQUEST || state_ == CANCELING);
  // First, determine the final OAC status for the document.
  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
  url::Origin origin = GetOriginToCommit().value();
  const IsolationContext& isolation_context =
      GetRenderFrameHost()->GetSiteInstance()->GetIsolationContext();

  OriginAgentClusterIsolationState requested_isolation_state =
      OriginAgentClusterIsolationState::CreateForDefaultIsolation(
          frame_tree_node_->navigator().controller().GetBrowserContext());

  if (IsOriginAgentClusterOptInRequested()) {
    requested_isolation_state =
        OriginAgentClusterIsolationState::CreateForOriginAgentCluster(
            /*had_oac_request=*/true,
            SiteIsolationPolicy::
                IsProcessIsolationForOriginAgentClusterEnabled());
  } else if (IsOriginAgentClusterOptOutRequested()) {
    requested_isolation_state =
        OriginAgentClusterIsolationState::CreateNonIsolatedByHeader();
  }

  const bool got_origin_agent_cluster =
      policy
          ->DetermineOriginAgentClusterIsolation(isolation_context, origin,
                                                 requested_isolation_state)
          .is_origin_agent_cluster();

  if (SiteIsolationPolicy::AreOriginAgentClustersEnabledByDefault(
          frame_tree_node_->navigator().controller().GetBrowserContext())) {
    // When OAC is enabled by default, report enum values that distinguish
    // between explicitly requesting OAC (on or off) and having no related
    // header.
    bool was_explicitly_requested =
        response_head_ &&
        response_head_->parsed_headers->origin_agent_cluster ==
            network::mojom::OriginAgentClusterValue::kTrue;
    bool was_explicitly_not_requested =
        response_head_ &&
        response_head_->parsed_headers->origin_agent_cluster ==
            network::mojom::OriginAgentClusterValue::kFalse;

    if (got_origin_agent_cluster) {
      if (was_explicitly_requested) {
        origin_agent_cluster_end_result_ =
            OriginAgentClusterEndResult::kExplicitlyRequestedAndOriginKeyed;
      } else if (was_explicitly_not_requested) {
        origin_agent_cluster_end_result_ =
            OriginAgentClusterEndResult::kExplicitlyNotRequestedButOriginKeyed;
      } else {
        origin_agent_cluster_end_result_ =
            OriginAgentClusterEndResult::kNotExplicitlyRequestedAndOriginKeyed;
      }
    } else {
      if (was_explicitly_requested) {
        origin_agent_cluster_end_result_ =
            OriginAgentClusterEndResult::kExplicitlyRequestedButNotOriginKeyed;
      } else if (was_explicitly_not_requested) {
        origin_agent_cluster_end_result_ = OriginAgentClusterEndResult::
            kExplicitlyNotRequestedAndNotOriginKeyed;
      } else {
        origin_agent_cluster_end_result_ = OriginAgentClusterEndResult::
            kNotExplicitlyRequestedButNotOriginKeyed;
      }
    }
  } else {
    // When OAC is not enabled by default, report enum values that only indicate
    // if OAC was requested or not vs whether it took effect.
    if (requested_isolation_state.logical_oac_status() ==
        AgentClusterKey::OACStatus::kOriginKeyedByHeader) {
      origin_agent_cluster_end_result_ =
          got_origin_agent_cluster
              ? OriginAgentClusterEndResult::kRequestedAndOriginKeyed
              : OriginAgentClusterEndResult::kRequestedButNotOriginKeyed;
    } else {
      origin_agent_cluster_end_result_ =
          got_origin_agent_cluster
              ? OriginAgentClusterEndResult::kNotRequestedButOriginKeyed
              : OriginAgentClusterEndResult::kNotRequestedAndNotOriginKeyed;
    }
  }

  // This needs to be computed separately from origin.opaque() because, per
  // https://crbug.com/1041376, we don't have a notion of the true origin yet.
  const bool is_opaque_origin_because_sandbox =
      (policy_container_builder_->FinalPolicies().sandbox_flags &
       network::mojom::WebSandboxFlags::kOrigin) ==
      network::mojom::WebSandboxFlags::kOrigin;

  // Compute whether the renderer should use an origin-keyed or a site-keyed
  // AgentClusterKey to commit the navigation. Note that we do not update
  // origin_agent_cluster_end_result_ since it's used for warnings and use
  // counters, i.e. things that don't apply to this sort of "automatic"
  // origin-keying.
  bool is_origin_keyed_for_renderer = is_opaque_origin_because_sandbox ||
                                      origin.opaque() ||
                                      got_origin_agent_cluster ||
                                      GetRenderFrameHost()
                                          ->GetSiteInstance()
                                          ->GetSiteInfo()
                                          .agent_cluster_key()
                                          .IsOriginKeyed();

  // Cross-origin isolated pages are always origin-keyed, with a cross-origin
  // isolation key. Currently, pages cross-origin isolated through COOP and COEP
  // are not given a cross-origin isolated AgentClusterKey in their
  // SiteInstance. Create one to send to the renderer process.
  std::optional<AgentClusterKey::CrossOriginIsolationKey> coi_key =
      GetRenderFrameHost()
          ->GetSiteInstance()
          ->GetSiteInfo()
          .agent_cluster_key()
          .GetCrossOriginIsolationKey();
  if (!coi_key.has_value() && GetRenderFrameHost()
                                  ->GetSiteInstance()
                                  ->GetWebExposedIsolationInfo()
                                  .is_isolated()) {
    coi_key = AgentClusterKey::CrossOriginIsolationKey(
        GetRenderFrameHost()
            ->GetSiteInstance()
            ->GetWebExposedIsolationInfo()
            .origin(),
        blink::mojom::CrossOriginIsolationMode::kConcrete,
        /*cross_origin_isolated_through_dip=*/false);
  }

  // If the PolicyContainer has an override for the CrossOriginIsolationKey,
  // apply it. This happens on Android WebView, where it is currently impossible
  // to switch SiteInstances and we cannot rely on SiteInstances to track the
  // cross-origin isolation state.
  // TODO(crbug.com/419595581): Remove this once default SiteInstanceGroups
  // ships on Android WebView.
  if (const auto& override_key = policy_container_builder_->FinalPolicies()
                                     .cross_origin_isolation_key_override) {
    coi_key = *override_key;
  }

  // Update the AgentClusterKey in CommitNavigationParams to the correct
  // AgentClusterKey to use for navigation. Unfortunately, we cannot use the
  // AgentClusterKey of the SiteInstance directly, as there are many edge cases
  // where the AgentClusterKey of the SiteInstance is not what we expect per
  // spec due to particular process allocation constraints. So it is simpler to
  // compute a brand new key here rather than relying on the SiteInstance's
  // AgentClusterKey (except for checking its cross-origin isolation key and
  // whether it is origin keyed).
  commit_params_->agent_cluster_key =
      AgentClusterKey::CreateAgentClusterKeyForNavigationCommit(
          origin, is_origin_keyed_for_renderer, coi_key);
}

void NavigationRequest::ProcessOriginAgentClusterEndResult() {
  if (!HasCommitted() || IsErrorPage() || IsSameDocument())
    return;

  if (origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kRequestedAndOriginKeyed ||
      origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kRequestedButNotOriginKeyed ||
      origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kExplicitlyRequestedAndOriginKeyed ||
      origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kExplicitlyRequestedButNotOriginKeyed) {
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        GetRenderFrameHost(),
        blink::mojom::WebFeature::kOriginAgentClusterHeader);
  }

  const url::Origin origin = url::Origin::Create(GetURL());

  if (origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kRequestedButNotOriginKeyed ||
      origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kExplicitlyRequestedButNotOriginKeyed) {
    GetRenderFrameHost()->AddMessageToConsole(
        blink::mojom::ConsoleMessageLevel::kWarning,
        base::StringPrintf(
            "The page requested an origin-keyed agent cluster using the "
            "Origin-Agent-Cluster header, but could not be origin-keyed since "
            "the origin '%s' had previously been placed in a site-keyed agent "
            "cluster. Update your headers to uniformly request origin-keying "
            "for all pages on the origin.",
            origin.Serialize().c_str()));
  }

  if (origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kNotRequestedButOriginKeyed ||
      origin_agent_cluster_end_result_ ==
          OriginAgentClusterEndResult::kExplicitlyNotRequestedButOriginKeyed) {
    GetRenderFrameHost()->AddMessageToConsole(
        blink::mojom::ConsoleMessageLevel::kWarning,
        base::StringPrintf(
            "The page did not request an origin-keyed agent cluster, but was "
            "put in one anyway because the origin '%s' had previously been "
            "placed in an origin-keyed agent cluster. Update your headers to "
            "uniformly request origin-keying for all pages on the origin.",
            origin.Serialize().c_str()));
  }
}

void NavigationRequest::PopulateDocumentTokenForCrossDocumentNavigation() {
  CHECK(!IsSameDocument());
  CHECK_GE(state_, READY_TO_COMMIT);
  const auto* token_to_reuse =
      GetRenderFrameHost()->GetDocumentTokenForCrossDocumentNavigationReuse(
          /* passkey */ {});
  document_token_.emplace(token_to_reuse ? *token_to_reuse
                                         : blink::DocumentToken());
}

bool NavigationRequest::HasCommittingOrigin(const url::Origin& origin) {
  // We are only interested in checking requests that have been assigned a
  // SiteInstance.
  if (state() < WILL_PROCESS_RESPONSE)
    return false;

  // This origin conversion won't be correct for about:blank, but origin
  // isolation shouldn't need to care about that case because a previous
  // instance of the origin would already have determined its isolation status
  // in that BrowsingInstance.
  // TODO(crbug.com/40092527): Use the computed origin here just to be safe.
  // Note, however, that if this is done, we need to be careful to still match
  // sandboxed frame origins, which GetURL() currently accomplishes "by
  // accident" (see https://crbug.com/446157743).
  return origin == url::Origin::Create(GetURL());
}

bool NavigationRequest::ShouldRequestSiteIsolationForCOOP() {
  if (!SiteIsolationPolicy::IsSiteIsolationForCOOPEnabled())
    return false;

  // COOP headers are only served once a response is available.
  if (state_ < WILL_PROCESS_RESPONSE)
    return false;

  // COOP isolation can only be triggered from main frames.  COOP headers
  // aren't honored in subframes.
  if (!IsInMainFrame())
    return false;

  // Filter out URLs with origins that are considered invalid for being
  // isolated. Note that the origin we'll eventually attempt to isolate should
  // be based on agent_cluster_key(), so that we apply isolation to the actual
  // site rather than the effective URL in the case of hosted apps.
  url::Origin origin =
      site_info_.agent_cluster_key().IsOriginKeyed()
          ? site_info_.agent_cluster_key().GetOrigin()
          : url::Origin::Create(site_info_.agent_cluster_key().GetSite());
  if (!IsolatedOriginUtil::IsValidIsolatedOrigin(origin))
    return false;

  // Check the COOP header value. All same-origin values are considered to be
  // an implicit hint for site isolation.
  bool should_header_value_trigger_isolation = false;
  switch (coop_status_.current_coop().value) {
    case network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin:
    case network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep:
    case network::mojom::CrossOriginOpenerPolicyValue::kSameOriginAllowPopups:
    case network::mojom::CrossOriginOpenerPolicyValue::kNoopenerAllowPopups:
      should_header_value_trigger_isolation = true;
      break;
    case network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone:
      should_header_value_trigger_isolation = false;
      break;
      // Don't handle the default case on purpose to force a compiler error if
      // new COOP values are added, so that they are explicitly handled here.
  }
  if (!should_header_value_trigger_isolation)
    return false;

  // There's no need for additional isolation if the site already requires a
  // dedicated process via other isolation mechanisms.  However, we still
  // return true if the site has been isolated due to COOP previously, so that
  // we can go through the COOP isolation flow to update the timestamp of when
  // the COOP isolation for this site was last used.
  //
  // Note: we can use `site_info_` here, since that has been assigned at
  // request start time and updated by redirects, but it is not (currently)
  // recomputed when response is received, so it does not include the COOP
  // isolation request (which would cause RequiresDedicatedProcess to return
  // true regardless of prior isolation). If we ever decide to update
  // `site_info_` at response time, we should revisit this and ensure that we
  // call RequiresDedicatedProcess on a SiteInfo that does not already have an
  // isolation request (enforced by CHECK below).
  CHECK(!site_info_.does_site_request_dedicated_process_for_coop());
  if (site_info_.RequiresDedicatedProcess(
          GetStartingSiteInstance()->GetIsolationContext())) {
    bool is_already_isolated_due_to_coop =
        ChildProcessSecurityPolicyImpl::GetInstance()->IsIsolatedSiteFromSource(
            origin,
            ChildProcessSecurityPolicy::IsolatedOriginSource::WEB_TRIGGERED);
    return is_already_isolated_due_to_coop;
  }
  return true;
}

UrlInfo NavigationRequest::GetUrlInfo() {
  // If the navigation has an OAC header, pass along an
  // OriginAgentClusterIsolationState request that represents the origin
  // isolation requested by the OAC header (either opt-in or opt-out). If there
  // is no OAC header, a final OAC state will be computed when creating the
  // SiteInfo for this URLInfo based on the default state for the chosen
  // BrowsingInstance.
  std::optional<OriginAgentClusterIsolationState> oac_header_request;
  if (IsOriginAgentClusterOptOutRequested()) {
    oac_header_request =
        OriginAgentClusterIsolationState::CreateNonIsolatedByHeader();
  } else if (IsOriginAgentClusterOptInRequested()) {
    // An origin-keyed agent cluster is used if explicitly requested by header.
    oac_header_request =
        OriginAgentClusterIsolationState::CreateForOriginAgentCluster(
            /*had_oac_request=*/true,
            SiteIsolationPolicy::
                IsProcessIsolationForOriginAgentClusterEnabled());
  }

  // Compute the WebExposedIsolationInfo that will be bundled into UrlInfo.
  auto web_exposed_isolation_info = ComputeWebExposedIsolationInfo();

  UrlInfoInit url_info_init(GetURL());
  url_info_init.WithOACHeaderRequest(oac_header_request)
      .WithCOOPSiteIsolation(ShouldRequestSiteIsolationForCOOP())
      .WithWebExposedIsolationInfo(web_exposed_isolation_info)
      .WithEmbedderIsolationInfo(embedder_isolation_info_);

  if (base::FeatureList::IsEnabled(features::kExcludeAdsFromOriginIsolation)) {
    // When excluding ads from origin isolation, an ad navigation is treated as
    // site-keyed either if its URL matches the ad host filter list, or if it is
    // same-site to an ancestor ad frame that was forced site-keyed.
    url_info_init.WithIsAdTaggedForSiteKeying(
        (response() && IsAdTaggedByHostFilter()) || HasSameSiteAdAncestor());
  }

  // Compute the CrossOriginIsolationKey for the navigation.
  std::optional<AgentClusterKey::CrossOriginIsolationKey>
      cross_origin_isolation_key = ComputeCrossOriginIsolationKey();

  // If the process model supports SiteInstance switching, pass the
  // CrossOriginIsolationKey to the URLinfo.
  if (SiteIsolationPolicy::UseDedicatedProcessesForAllSites() ||
      SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled() ||
      ShouldUseDefaultSiteInstanceGroup()) {
    url_info_init.WithCrossOriginIsolationKey(cross_origin_isolation_key);
  } else if (cross_origin_isolation_key.has_value()) {
    // On Android WebView, we cannot switch SiteInstances. Instead of passing
    // the CrossOriginIsolationKey to URLInfo to have it be used to select a
    // SiteInstance, we store it in the PolicyContainer. It will be picked up in
    // DetermineCrossOriginIsolationKey for commit, and sent to the renderer
    // process.
    policy_container_builder_->SetCrossOriginIsolationKeyOverride(
        cross_origin_isolation_key.value());
  }

  // Navigations with SiteInstances which have fixed storage partition (e.g.
  // <webview> tags) should always stay in the current StoragePartition.
  SiteInstanceImpl* current_instance =
      frame_tree_node_->current_frame_host()->GetSiteInstance();
  if (current_instance->IsFixedStoragePartition()) {
    url_info_init.WithStoragePartitionConfig(
        current_instance->GetSecurityPrincipal().GetStoragePartitionConfig());
  }

  // Child frames (including fenced frames) should always use the
  // same StoragePartition as their parent.
  RenderFrameHostImpl* parent = GetParentFrameOrOuterDocument();
  if (parent) {
    url_info_init.WithStoragePartitionConfig(parent->GetSiteInstance()
                                                 ->GetSecurityPrincipal()
                                                 .GetStoragePartitionConfig());
  }

  if (IsLoadDataWithBaseURL()) {
    // LoadDataWithBaseURL() navigations also need to explicitly set the origin
    // to the origin of the base URL.  This ensures that the process for this
    // navigation will eventually be locked to the right origin (i.e., origin of
    // the base URL rather than the data: URL).
    //
    // Note that while LoadDataWithBaseURL() is supported in <webview> tags on
    // desktop platforms and on Android Webview, only <webview> tags currently
    // utilize this special case when running in site-isolated mode. Android
    // Webview doesn't currently lock processes for LoadDataWithBaseURL()
    // navigations.
    url_info_init.WithOrigin(
        url::Origin::Create(common_params().base_url_for_data_url));
  } else if (GetURL().IsAboutBlank() && GetInitiatorOrigin().has_value()) {
    // about:blank inherits its origin from the initiator, so ensure that this
    // is reflected in the UrlInfo.  In the common case, this isn't needed for
    // process model decisions, since we already leave about:blank in the
    // source SiteInstance, which corresponds to the initiator (see
    // `RenderFrameHostManager::CanUseSourceSiteInstance()`). However, in
    // certain corner cases, the source SiteInstance can't be used, but we
    // will still need to assign a proper process for about:blank. In that
    // case, we should honor the initiator origin, so that about:blank ends up
    // in a process that's locked to that origin, rather than an unlocked
    // process with an unassigned SiteInstance.  The latter would be violating
    // site isolation guarantees and would be problematic for Citadel
    // enforcements in
    // ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin().  See
    // https://crbug.com/1426928.
    //
    // TODO(alexmos): Consider also specifying UrlInfo::origin for about:srcdoc
    // navigations. This is not currently needed in the SiteInstance
    // and process assignment paths for srcdoc frames, but doing this might
    // simplify some of that code and would be good for consistency, since both
    // about:blank and about:srcdoc inherit the origin per spec
    // (https://html.spec.whatwg.org/multipage/document-sequences.html#determining-the-origin).
    url_info_init.WithOrigin(*GetInitiatorOrigin());
  } else {
    // Overriding the origin for a URL is dangerous and only allowed in very
    // narrow cases which are handled explicitly above.  Please think very
    // carefully about any new cases that need to do this.
    CHECK(!url_info_init.origin().has_value());
  }

  // Propagate the tentative origin to commit value (for data: URLs that will be
  // rendered) to the UrlInfo, to make sure the nonce remains the same
  // throughout the navigation.
  if (GetURL().SchemeIs(url::kDataScheme)) {
    // The function for computing the request's origin depends on the stage of
    // the request, but the same opaque nonce value is preserved across both
    // functions for data: URLs.
    if (state_ < WILL_PROCESS_RESPONSE) {
      url_info_init.WithOrigin(GetTentativeOriginAtRequestTime());
    } else if (response_should_be_rendered_) {
      // The origin to commit is nullopt for cases that are not rendered (e.g.,
      // downloads), but the UrlInfo does not need the origin for data: URLs in
      // such cases.
      url_info_init.WithOrigin(GetOriginToCommit().value());
    }
  }

  // Determine if the request is for a sandboxed frame or not, and if so whether
  // the sandboxed frame should get a dedicated process. Setting
  // `has_origin_restricted_sandbox_flag` to true indicates it should get
  // process isolation, but only if the site/origin would have qualified for a
  // dedicated process even without the sandbox flags.
  //
  // If PolicyContainer::ComputePoliciesToCommit() has run
  // `policy_container_builder_` will be valid, but even if it hasn't, we can
  // speculatively take `commit_params_->frame_policy.sandbox_flags` if we
  // haven't received the response yet and don't have the final
  // `policy_container_builder_`, and if the state of the kOrigin flag changes,
  // we'll detect the change and recompute the target SiteInstance elsewhere.
  //
  // In general, about:blank documents should stay in their initiator's process.
  // If neither the initiator or about:blank is sandboxed, or if both are, then
  // the about:blank should stay in its parent's process, if only to avoid
  // needing an extra process that only shows the empty frame (if the parent and
  // about:blank are sandboxed, they parent cannot script the about:blank
  // frame). If the initiator is not sandboxed but the about:blank document is
  // (e.g., due to iframe attributes), then nothing else will be able to script
  // the empty about:blank document and it is safe to leave it in the same
  // process. (It is not possible for the initiator to be sandboxed and the
  // about:blank to not be sandboxed, because about:blank inherits the
  // sandboxing of its initiator.)
  bool is_eligible_for_sandboxing =
      !GetURL().IsAboutBlank() ||
      (source_site_instance_ &&
       source_site_instance_->GetSecurityPrincipal().IsSandboxed());
  if (SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled() &&
      is_eligible_for_sandboxing) {
    // Determine if the frame has the sandbox flag or not.
    bool has_origin_restricted_sandbox_flag = false;
    if (policy_container_builder_->HasComputedPolicies()) {
      has_origin_restricted_sandbox_flag =
          (policy_container_builder_->FinalPolicies().sandbox_flags &
           network::mojom::WebSandboxFlags::kOrigin) ==
          network::mojom::WebSandboxFlags::kOrigin;
    } else {
      // Note: We'll end up here if this function is called before
      // ComputePoliciesToCommit(), such as when computing a speculative
      // RenderFrameHost's SiteInstance before receiving a response. In that
      // event we use the sandbox flags in commit_params_ as a current "best
      // estimate".
      has_origin_restricted_sandbox_flag =
          (commit_params_->frame_policy.sandbox_flags &
           network::mojom::WebSandboxFlags::kOrigin) ==
          network::mojom::WebSandboxFlags::kOrigin;
    }

    // It's possible that a sandbox attribute can disappear from a frame that
    // still contains a sandboxed initiator, meaning we won't have sandbox
    // flags here, but should still respect the sandbox of the initiator.
    bool should_inherit_initiators_sandbox =
        GetURL().IsAboutBlank() && source_site_instance_ &&
        source_site_instance_->GetSecurityPrincipal().IsSandboxed();

    // Consider isolating sandboxed frames that won't end up as downloads or
    // 204s.
    if ((has_origin_restricted_sandbox_flag ||
         should_inherit_initiators_sandbox) &&
        response_should_be_rendered_) {
      // If the URL under consideration wouldn't qualify for a dedicated process
      // without the sandbox flags, then it shouldn't qualify even with the
      // sandbox flag. This is most likely to occur when site isolation is only
      // partial, as on Android.
      //
      // Ideally the IsolationContext would be the one used for the committed
      // RenderFrameHost at the end of the navigation, since a different set of
      // origins may require isolation if a BrowsingInstance swap occurs. This
      // isn't known at the start of the navigation, though, so we use the
      // current IsolationContext instead.
      const IsolationContext& isolation_context =
          current_instance->GetIsolationContext();
      if (SiteInfo::Create(isolation_context, UrlInfo(url_info_init))
              .RequiresDedicatedProcess(isolation_context)) {
        // Embedders can identify, via ContentBrowserClient, cases that should
        // not use isolated sandboxed frames.
        ContentBrowserClient* client = GetContentClient()->browser();
        BrowserContext* context =
            frame_tree_node_->navigator().controller().GetBrowserContext();
        url::SchemeHostPort precursor;
        if (state_ < WILL_PROCESS_RESPONSE) {
          precursor = GetTentativeOriginAtRequestTime()
                          .GetTupleOrPrecursorTupleIfOpaque();
        } else if (GetOriginToCommit()) {
          precursor = GetOriginToCommit()->GetTupleOrPrecursorTupleIfOpaque();
        } else {
          NOTREACHED() << "No origin-to-commit for sandboxed url = "
                       << GetURL();
        }

        bool client_allows_cross_process_sandboxed_frames =
            client->ShouldAllowCrossProcessSandboxedFrameForPrecursor(
                context, precursor.GetURL(), GetURL());
        if (client_allows_cross_process_sandboxed_frames) {
          url_info_init.WithSandbox(true);
          // If an isolated sandbox is required, and the "per-document" grouping
          // mode has been specified with kIsolateSandboxedIframes, then we use
          // a unique document identifier, provided by `navigation_id_`, to
          // guarantee that each sandboxed iframe gets its own SiteInstance,
          // even if two or more such documents share a site/origin. Using
          // navigation_id_ means that each new NavigationRequest (and thus each
          // document) will get a different value.
          if (blink::features::kIsolateSandboxedIframesGroupingParam.Get() ==
              blink::features::IsolateSandboxedIframesGrouping::kPerDocument) {
            url_info_init.WithUniqueSandboxId(navigation_id_);
          }
        }
      }
    }
  }

  // If a prefetch might have been affected by cross-site state, the
  // relationship with other windows should be severed to make this more
  // difficult to use to leak cross-site state.
  // https://crbug.com/1439246
  if (base::FeatureList::IsEnabled(
          features::kPrefetchStateContaminationMitigation) &&
      response_head_ &&
      response_head_->is_prefetch_with_cross_site_contamination) {
    url_info_init.WithCrossSitePrefetchContamination(true);
  }

  if (base::FeatureList::IsEnabled(
          features::kProcessSelectionDeferringConditions)) {
    url_info_init.WithProcessSelectionUserData(
        GetProcessSelectionUserData().GetSafeRef());
  }

  return UrlInfo(url_info_init);
}

const GURL& NavigationRequest::GetOriginalRequestURL() {
  // If this is a loadData navigation we should return the URL used to commit,
  // even if the navigation went through redirects. This is to preserve the
  // previous behavior where we use the redirect chain from the renderer to get
  // the original request URL. When we commit a loadDataWithBaseURL, or a
  // loadDataAsStringWithBaseUrl navigation, the redirect chain in the renderer
  // used to only contain the commit URL.
  if (IsLoadDataWithBaseURL())
    return GetURL();

  // Otherwise, return the first URL in the redirect chain. If the navigation
  // is started by a client redirect, this will be the URL of the document that
  // started the redirect. Otherwise, this will be the first destination URL
  // of the navigation, before any server redirects.
  // TODO(crbug.com/40168423): Reconsider the behavior with client
  // redirects, as all script-initiated navigations are considered client
  // redirects, which means the client redirect might not always trigger
  // immediately (or at all, if the navigation depends on user interaction)
  // if we decide to do a reload with the original URL.
  CHECK(!redirect_chain_.empty());
  return redirect_chain_[0];
}

void NavigationRequest::OnResponseStarted(
    network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
    network::mojom::URLResponseHeadPtr response_head,
    mojo::ScopedDataPipeConsumerHandle response_body,
    GlobalRequestID request_id,
    bool is_download,
    net::NetworkAnonymizationKey network_anonymization_key,
    SubresourceLoaderParams subresource_loader_params,
    EarlyHints early_hints) {
  receive_response_time_ = base::TimeTicks::Now();
  const int32_t http_response_code =
      response_head->headers ? response_head->headers->response_code()
                             : -1 /* no http_response_code */;
  TRACE_EVENT(
      "navigation", "NavigationRequest::OnResponseStarted",
      perfetto::Flow::FromPointer(this), [&](perfetto::EventContext ctx) {
        auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
        auto* response_info = event->set_response_info();
        if (http_response_code != -1) {
          response_info->set_response_code(http_response_code);
        }
        response_info->set_was_http_cache(response_head->was_fetched_via_cache);
      });

  ScopedCrashKeys crash_keys(*this);

  // The |loader_|'s job is finished. It must not call the NavigationRequest
  // anymore from now.
  loader_.reset();
  if (is_download)
    RecordDownloadUseCountersPrePolicyCheck();
  is_download_ = is_download && download_policy().IsDownloadAllowed();
  if (is_download_)
    RecordDownloadUseCountersPostPolicyCheck();
  request_id_ = request_id;

  CHECK(IsNavigationStarted());
  CHECK(response_head);
  CHECK(response_head->parsed_headers);
  EnterChildTraceEvent("OnResponseStarted", this);
  SetState(WILL_PROCESS_RESPONSE);
  response_head_ = std::move(response_head);
  response_body_ = std::move(response_body);

  // If the navigation is activating a prefetched response, and the response
  // contains a valid same-origin activation beacon endpoint, we record it to
  // send the beacon when the navigation commits.
  // We must ensure this is actually a prefetch activation (not a normal network
  // request that happened to return the header) by checking `is_prefetched`.
  // `was_in_prefetch_cache` and `kNavigationalPrefetch` are browser-controlled
  // flags set by the prefetch serving loaders, so they are safe from server
  // spoofing. Prerender commits are excluded here because the page is not yet
  // presented to the user; the beacon should be sent when the prerender is
  // activated.
  if (IsPrefetchActivationBeaconEnabled(GetURL(),
                                        response_head_->headers.get()) &&
      !IsInPrerenderedMainFrame() && response_head_->parsed_headers &&
      response_head_->parsed_headers->prefetch_activation_beacon_endpoint
          .has_value()) {
    const GURL& endpoint =
        *response_head_->parsed_headers->prefetch_activation_beacon_endpoint;
    bool is_prefetched =
        response_head_->was_in_prefetch_cache ||
        response_head_->navigation_delivery_type ==
            network::mojom::NavigationDeliveryType::kNavigationalPrefetch;
    bool is_prerender_activation_sending_beacon =
        IsPrerenderedPageActivation() &&
        base::FeatureList::IsEnabled(features::kPrerenderActivationBeacon);
    if (is_prefetched && endpoint.is_valid() &&
        url::Origin::Create(endpoint).IsSameOriginWith(
            url::Origin::Create(GetURL())) &&
        !is_prerender_activation_sending_beacon) {
      activation_beacon_url_ = endpoint;
    }
  }
  ssl_info_ = response_head_->ssl_info;
  auth_challenge_info_ = response_head_->auth_challenge_info;
  BrowserContext* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();

  if (SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel(
          browser_context, GetURL())) {
    GetContentClient()->browser()->EnsureRequiredHeadersForIsolatedApp(
        browser_context, GetURL(), response_head_.get(),
        frame_tree_node_->frame_tree_node_id());
  }

  // TODO(crbug.com/40218207): Store the whole EarlyHints struct instead
  // of duplicating all of its fields.
  was_resource_hints_received_ = early_hints.was_resource_hints_received;
  early_hints_manager_ = std::move(early_hints.manager);
  if (early_hints_manager_ &&
      early_hints_manager_->first_early_hints_receive_time()) {
    base::UmaHistogramTimes(
        "Navigation.EarlyHints.WillStartRequestToEarlyHintsTime",
        *early_hints_manager_->first_early_hints_receive_time() -
            will_start_request_time_);
    base::UmaHistogramTimes(
        "Navigation.EarlyHints.EarlyHintsToResponseStartTime",
        base::TimeTicks::Now() -
            *early_hints_manager_->first_early_hints_receive_time());
  }

  // A request was made. Record it before we decide to block this response for
  // a reason or another.
  RecordAddressSpaceFeature();

  const bool is_mhtml_archive = IsMhtmlMimeType(response_head_->mime_type);
  if (is_mhtml_archive)
    is_mhtml_or_subframe_ = true;

  if (CheckCSPEmbeddedEnforcement() ==
      CSPEmbeddedEnforcementResult::BLOCK_RESPONSE) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_CSP),
        true /* skip_throttles */, std::nullopt /* error_page_content*/,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (CheckConnectionAllowlistEmbeddedEnforcement() ==
      ConnectionAllowlistEmbeddedEnforcementResult::BLOCK_RESPONSE) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_RESPONSE),
        true /* skip_throttles */, std::nullopt /* error_page_content*/,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  // See https://github.com/whatwg/fetch/pull/1579
  if (!response_head_->timing_allow_passed) {
    commit_params_->navigation_timing->parent_resource_timing_access =
        blink::mojom::ParentResourceTimingAccess::kDoNotReport;
  }

  {
    const std::optional<network::mojom::BlockedByResponseReason>
        coop_requires_blocking =
            coop_status_.SanitizeResponse(response_head_.get());
    if (coop_requires_blocking) {
      // TODO(crbug.com/40166503): Investigate what must be done in case
      // of a download.
      OnRequestFailedInternal(
          network::URLLoaderCompletionStatus(*coop_requires_blocking),
          false /* skip_throttles */, std::nullopt /* error_page_content */,
          false /* collapse_frame */);
      // DO NOT ADD CODE after this. The previous call to
      // OnRequestFailedInternal has destroyed the NavigationRequest.
      return;
    }
    policy_container_builder_->SetCrossOriginOpenerPolicy(
        response_head_->parsed_headers->cross_origin_opener_policy);
  }

  ComputePoliciesToCommit();
  // After this line. The sandbox flags to commit have been computed. The origin
  // can be determined. This is needed for enforcing COOP below.

  {
    const url::Origin origin = GetOriginForURLLoaderFactoryBeforeResponse(
        policy_container_builder_->FinalPolicies().sandbox_flags);
    coop_status_.EnforceCOOP(
        policy_container_builder_->FinalPolicies().cross_origin_opener_policy,
        origin, network_anonymization_key);

    // Set embedded documents' cross-origin-opener-policy from their top level:
    //  - Use top level's policy if they are same-origin.
    //  - Use the default policy otherwise.
    // This COOP value is not used to enforce anything on this frame, but will
    // be inherited to every local-scheme document created from them. It will
    // also be inherited by the initial empty document from its opener.
    if (frame_tree_node_->parent()) {
      const network::CrossOriginOpenerPolicy& top_level_coop =
          frame_tree_node_->parent()
              ->GetMainFrame()
              ->cross_origin_opener_policy();
      if (frame_tree_node_->parent()
              ->GetMainFrame()
              ->GetLastCommittedOrigin()
              .IsSameOriginWith(origin)) {
        policy_container_builder_->SetCrossOriginOpenerPolicy(top_level_coop);
      } else {
        policy_container_builder_->SetCrossOriginOpenerPolicy(
            network::CrossOriginOpenerPolicy());
      }
    }
  }

  // The navigation may have encountered a header that requests isolation for
  // the url's origin. Before we pick the renderer, make sure we update the
  // origin-isolation opt-ins appropriately.
  CheckForIsolationOptIn(GetURL());

  const bool is_first_response = commit_params_->redirects.empty();
  UpdateNavigationHandleTimingsOnResponseReceived(/*is_redirect=*/false,
                                                  is_first_response);

  commit_params_->http_response_code = http_response_code;

  // Update fetch start timing. While NavigationRequest updates fetch start
  // timing for redirects, it's not aware of service worker interception so
  // fetch start timing could happen earlier than worker start timing. Use
  // worker ready time if it is greater than the current value to make sure
  // fetch start timing always comes after worker start timing (if a service
  // worker intercepted the navigation).
  commit_params_->navigation_timing->fetch_start =
      std::max(commit_params_->navigation_timing->fetch_start,
               response_head_->load_timing.service_worker_ready_time);

  // A navigation is user activated if it contains a user gesture or the frame
  // received a gesture and the navigation is renderer initiated. If the
  // navigation is browser initiated, it has to come from the context menu.
  // In all cases, the previous and new URLs have to match the
  // `ShouldPropagateUserActivation` requirements (same eTLD+1).
  // There are two different checks:
  // 1. if the `frame_tree_node_` has an origin and is following the rules above
  //    with the target URL, it is used and the bit is set if the navigation is
  //    renderer initiated and the `frame_tree_node_` had a gesture. This should
  //    apply to same page navigations and is preferred over using the referrer
  //    as it can be changed.
  // 2. if referrer and the target url are following the rules above, two
  //    conditions will set the bit: navigation comes from a gesture and is
  //    renderer initiated (middle click/ctrl+click) or it is coming from a
  //    context menu. This should apply to pages that open in a new tab and we
  //    have to follow the referrer. It means that the activation might not be
  //    transmitted if it should have.
  if (commit_params_->was_activated ==
      blink::mojom::WasActivatedOption::kUnknown) {
    commit_params_->was_activated = blink::mojom::WasActivatedOption::kNo;

    if (!commit_params_->is_browser_initiated &&
        (frame_tree_node_->HasStickyUserActivation() ||
         frame_tree_node_->has_received_user_gesture_before_nav()) &&
        ShouldPropagateUserActivation(
            frame_tree_node_->current_origin(),
            url::Origin::Create(common_params_->url))) {
      commit_params_->was_activated = blink::mojom::WasActivatedOption::kYes;
      // TODO(crbug.com/41367031): the next check is relying on
      // sanitized_referrer_ but should ideally use a more reliable source for
      // the originating URL when the navigation is renderer initiated.
    } else if (((common_params_->has_possibly_filtered_user_gesture &&
                 !commit_params_->is_browser_initiated) ||
                common_params_->started_from_context_menu) &&
               ShouldPropagateUserActivation(
                   url::Origin::Create(sanitized_referrer_->url),
                   url::Origin::Create(common_params_->url))) {
      commit_params_->was_activated = blink::mojom::WasActivatedOption::kYes;
    }
  }

  // Check if the response should be sent to a renderer.
  // Regular downloads should not be rendered, but downloads with an
  // unsuccessful response code will cause an error page to be rendered.
  response_should_be_rendered_ =
      (!is_download ||
       IsFailedDownload(is_download, response_head_->headers.get())) &&
      (!response_head_->headers.get() ||
       (response_head_->headers->response_code() != net::HTTP_NO_CONTENT &&
        response_head_->headers->response_code() != net::HTTP_RESET_CONTENT &&
        !ShouldRenderFallbackContentForResponse(*response_head_->headers)));

  if (!response_should_be_rendered_) {
    net_error_ = net::ERR_ABORTED;
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kShouldNotRenderResponse;
    SelectFrameHostForOnResponseStarted(std::move(url_loader_client_endpoints),
                                        is_download,
                                        std::move(subresource_loader_params));
    return;
  }

  // MHTML document can't be framed into non-MHTML document (and vice versa).
  // The full page must load from the MHTML archive or none of it.
  if (is_mhtml_archive && !IsInMainFrame()) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_RESPONSE),
        false /* skip_throttles */, std::nullopt /* error_page_content */,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }

  const std::optional<network::mojom::BlockedByResponseReason>
      coep_requires_blocking = EnforceCOEP();
  if (coep_requires_blocking) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(*coep_requires_blocking),
        false /* skip_throttles */, std::nullopt /* error_page_content */,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }

  const auto& url = common_params_->url;

  // The fenced frame root and the nested iframes are required to have the
  // Supports-Loading-Mode HTTP response header "fenced-frame" to be able to
  // load. Otherwise a console error is emitted.
  const bool should_enforce_fenced_frame_opt_in =
      response_head_->headers && frame_tree_node_->IsInFencedFrameTree() &&
      !(url.IsAboutBlank() || url.SchemeIsBlob() ||
        url.SchemeIs(url::kDataScheme));
  if (should_enforce_fenced_frame_opt_in &&
      !IsOptedInFencedFrame(*response_head_->headers)) {
    blink::RecordFencedFrameCreationOutcome(
        blink::FencedFrameCreationOutcome::kResponseHeaderNotOptIn);
    AddDeferredConsoleMessage(
        blink::mojom::ConsoleMessageLevel::kError,
        "Supports-Loading-Mode HTTP response header 'fenced-frame' is required "
        "to load the fenced frame root and its nested iframes.");
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_RESPONSE),
        false /* skip_throttles */, std::nullopt /* error_page_content */,
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }

  // [spec]: https://html.spec.whatwg.org/C/#process-a-navigate-response
  // 4. if [...] the result of checking a navigation response's adherence to its
  // embedder policy [...], then set failure to true.
  if (!CheckResponseAdherenceToCoep(url)) {
    OnRequestFailedInternal(network::URLLoaderCompletionStatus(
                                network::mojom::BlockedByResponseReason::
                                    kCoepFrameResourceNeedsCoepHeader),
                            false /* skip_throttles */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }

  if (process_selection_deferrer_) {
    // When process selection deferral is enabled, give process selection
    // dependencies the opportunity to defer before finalizing the selected
    // process.
    process_selection_deferrer_->WillSelectFinalProcess(base::BindOnce(
        &NavigationRequest::SelectFrameHostForOnResponseStarted,
        weak_factory_.GetWeakPtr(), std::move(url_loader_client_endpoints),
        is_download, std::move(subresource_loader_params)));
  } else {
    SelectFrameHostForOnResponseStarted(std::move(url_loader_client_endpoints),
                                        is_download,
                                        std::move(subresource_loader_params));
  }
}

void NavigationRequest::SelectFrameHostForOnResponseStarted(
    network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
    bool is_download,
    SubresourceLoaderParams subresource_loader_params) {
  TRACE_EVENT("navigation",
              "NavigationRequest::SelectFrameHostForOnResponseStarted",
              perfetto::Flow::FromPointer(this));
  CHECK(!HasRenderFrameHost())
      << "`render_frame_host_` should not be set before the "
         "`NavigationRequest` starts to select the RFH.";
  ScopedCrashKeys crash_keys(*this);
  std::string rfh_selected_reason;

  // Select an appropriate renderer to commit the navigation.
  if (IsServedFromBackForwardCache()) {
    NavigationControllerImpl* controller = GetNavigationController();
    auto entry =
        controller->GetBackForwardCache().GetOrEvictEntry(nav_entry_id_);
    if (!entry.has_value() &&
        entry.error() == BackForwardCacheImpl::kEntryIneligibleAndEvicted) {
      // If the RenderFrameHost to restore has been evicted and deleted, or the
      // current navigation is being restarted due to the `GetOrEvictEntry`
      // call, we should stop processing this back/forward cache restore
      // navigation, as the navigation will soon be restarted as a normal
      // history navigation and the current NavigationRequest will be reset.
      // DO NOT ADD CODE after this. The previous call to
      // `GetOrEvictEntry()` has destroyed the NavigationRequest.
      return;
    }
    CHECK(entry.has_value() && entry.value());
    CHECK(entry.value()->render_frame_host());
    render_frame_host_ = entry.value()->render_frame_host()->GetSafeRef();
  } else if (IsPrerenderedPageActivation()) {
    // Prerendering requires changing pages starting at the root node.
    CHECK(IsInMainFrame());

    render_frame_host_ =
        GetPrerenderHostRegistry()
            .GetRenderFrameHostForReservedHost(*activating_prerender_host_id_)
            ->GetSafeRef();
  } else if (response_should_be_rendered_) {
    std::string* reason_output =
        IsInitialWebUINavigation() ? &rfh_selected_reason : nullptr;

    if (auto result =
            frame_tree_node_->render_manager()->GetFrameHostForNavigation(
                this, &browsing_context_group_swap_,
                ProcessAllocationContext::CreateForNavigationRequest(
                    ProcessAllocationNavigationStage::kAfterResponse,
                    navigation_id_, IsInOutermostMainFrame()),
                reason_output);
        result.has_value()) {
      render_frame_host_ = result.value()->GetSafeRef();
    } else {
      switch (result.error()) {
        case GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame:
          // TODO(crbug.com/40250311): This was unhandled before and
          // remains explicitly unhandled. This branch may be removed in the
          // future.
          break;
        case GetFrameHostForNavigationFailed::kBlockedByPendingCommit:
          CHECK(ShouldQueueDueToExistingPendingCommitRFH());
          // This closure is posted to the event loop, so it must use WeakPtr.
          resume_commit_closure_ = base::BindOnce(
              &NavigationRequest::SelectFrameHostForOnResponseStarted,
              weak_factory_.GetWeakPtr(),
              std::move(url_loader_client_endpoints), is_download,
              std::move(subresource_loader_params));
          frame_tree_node_->render_manager()
              ->speculative_frame_host()
              ->RecordMetricsForBlockedGetFrameHostAttempt(
                  /* commit_attempt=*/true);
          return;
      }
    }

    // GetFrameHostForNavigation() should update associated_rfh_type_, so it
    // should never be NONE here.
    CHECK_NE(AssociatedRenderFrameHostType::NONE, associated_rfh_type_);

    CHECK(Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
        GetRenderFrameHost(), GetUrlInfo(),
        /* is_renderer_initiated_check */ false));
  } else {
    render_frame_host_ = std::nullopt;
  }
  if (!HasRenderFrameHost()) {
    CHECK(!response_should_be_rendered_);
  }

  if (!commit_params_->is_browser_initiated && HasRenderFrameHost() &&
      GetRenderFrameHost()->GetProcess() !=
          frame_tree_node_->current_frame_host()->GetProcess()) {
    // Allow the embedder to cancel the cross-process commit if needed.
    if (!frame_tree_node_->navigator()
             .GetDelegate()
             ->ShouldAllowRendererInitiatedCrossProcessNavigation(
                 GetRenderFrameHost(),
                 frame_tree_node_->IsOutermostMainFrame())) {
      net_error_ = net::ERR_ABORTED;
      error_navigation_trigger_ = ErrorNavigationTrigger::
          kRenderInitiatedCrossProcessNavigationNotAllowed;
      frame_tree_node_->ResetNavigationRequest(
          NavigationDiscardReason::kInternalCancellation);
      return;
    }
  }

  // Store the URLLoaderClient endpoints until checks have been processed.
  url_loader_client_endpoints_ = std::move(url_loader_client_endpoints);

  subresource_loader_params_ = std::move(subresource_loader_params);

  // Most cases where ShouldAssignSiteForUrlInfo() is false should never load
  // actual content and reach this.  Since only empty document schemes are
  // allowed to leave a SiteInstance's site unassigned, they should follow the
  // !NeedsUrlLoader() path for committing the navigation early without ever
  // making a network request, and hence they should never reach the response
  // processing code here.
  //
  // The sole exception to this is about:blank URLs, since extensions are
  // allowed to redirect to them after a regular network request/response has
  // started.  Hence, about:blank is the only possible URL which both uses
  // unassigned SiteInstances and can reach this point (via an extension
  // redirect).
  if (common_params_->url.IsAboutBlank()) {
    // TODO(alexmos): Convert to a CHECK after verifying that this doesn't
    // happen in practice.
    if (!WasServerRedirect()) {
      DVLOG(1) << "about:blank should only go through the network stack "
               << "when an extension redirects to it.";
      base::debug::DumpWithoutCrashing();
    }
  } else {
    // TODO(alexmos): Convert to a CHECK after verifying that this doesn't
    // happen in practice.
    if (!SiteInstanceImpl::ShouldAssignSiteForUrlInfo(GetUrlInfo())) {
      DVLOG(1) << "This URL was unexpectedly loaded through the network stack: "
               << common_params_->url;
      base::debug::DumpWithoutCrashing();
    }
  }

  if (HasRenderFrameHost()) {
    // Set the site URL now if it hasn't been set already. If the site requires
    // a dedicated process, this will lock the process to that site, which will
    // prevent other sites from incorrectly reusing this process. See
    // https://crbug.com/738634.
    SiteInstanceImpl* instance = GetRenderFrameHost()->GetSiteInstance();
    if (!instance->HasSite() &&
        SiteInstanceImpl::ShouldAssignSiteForUrlInfo(GetUrlInfo())) {
      instance->ConvertToDefaultOrSetSite(GetUrlInfo());
    }

    // Since we've made the final pick for the RenderFrameHost above, the picked
    // RenderFrameHost's process should be considered "tainted" for future
    // process reuse decisions. That is, a site requiring a dedicated process
    // should not reuse this process, unless it's same-site with the URL we're
    // committing.
    //
    // The process must be marked used after calling ConvertToDefaultOrSetSite,
    // because that call verifies that a SiteInstance with an unassigned site
    // (e.g., about:blank) can only be locked to a site if it is still unused.
    //
    // Note that although NavigationThrottles could still cancel the navigation
    // as part of WillProcessResponse below, we must update the process here,
    // since otherwise there could be a race if a NavigationThrottle defers the
    // navigation, and in the meantime another navigation reads the incorrect
    // IsUnused() value from the same process when making a process reuse
    // decision.
    GetRenderFrameHost()->GetProcess()->SetIsUsed();

    // Now that we know the IsolationContext for the assigned SiteInstance, we
    // opt the origin into OAC here if needed. Note that this doesn't need to
    // account for loading data URLs with a base URL, because such a base URL
    // can never opt into OAC.
    // TODO(wjmaclean): Remove this call/function when same-process
    // OriginAgentCluster moves to SiteInstanceGroup, as then all OAC origins
    // will get a SiteInstance (regardless of process isolation) and tracking
    // will be handled by the existing pathway in
    // SiteInstanceImpl::SetSiteInfoInternal().
    const IsolationContext& isolation_context = instance->GetIsolationContext();
    AddOriginAgentClusterStateIfNecessary(isolation_context);

    // TODO(wjmaclean): Once this is all working, consider combining the
    // following code into the function above. If this navigation request didn't
    // opt-in to origin isolation, we need to check here in case the origin has
    // previously requested isolation and should be marked as opted-out in this
    // SiteInstance. At this point we know that |render_frame_host_|'s
    // SiteInstance has been finalized, so it's safe to use it here to get the
    // correct |IsolationContext|.
    //
    // When loading a data URL with a base URL, use the base URL to calculate
    // the origin; otherwise, `RecordDefaultOriginAgentClusterOriginIfNew()`
    // will simply do nothing as a data: URL has an opaque origin.
    //
    // TODO(wjmaclean): this won't handle cases like about:blank (where it
    // inherits an origin we care about).  We plan to compute the origin before
    // commit time (https://crbug.com/888079), which may make it possible to
    // compute the right origin here.
    const url::Origin origin =
        IsLoadDataWithBaseURL()
            ? url::Origin::Create(common_params_->base_url_for_data_url)
            : url::Origin::Create(common_params_->url);
    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
    policy->RecordDefaultOriginAgentClusterOriginIfNew(
        isolation_context, origin, false /* is_global_walk_or_frame_removal */);

    url::Origin process_lock_origin =
        url::Origin::Create(instance->GetSiteInfo().GetProcessLockURL());
    // Cache the computed v8 optimization state so that all instances of an
    // origin in a BrowsingInstance are assigned to the same process.
    policy->AddV8OptimizationDisabledStateForOriginIfNotCached(
        isolation_context.browsing_instance_id(), process_lock_origin,
        instance->GetProcess()->AreV8OptimizationsDisabled());

    // Replace the SiteInstance of the previously committed entry if it's for a
    // url that doesn't require a site assignment, if this new commit will be
    // assigning an incompatible site to the previous SiteInstance. This ensures
    // the new SiteInstance can be used with the old entry if we return to it.
    // See http://crbug.com/992198 for further context.
    NavigationEntryImpl* nav_entry =
        frame_tree_node_->navigator().controller().GetLastCommittedEntry();
    if (nav_entry && !nav_entry->GetURL().IsAboutBlank() &&
        !SiteInstance::ShouldAssignSiteForURL(nav_entry->GetURL()) &&
        SiteInstanceImpl::ShouldAssignSiteForUrlInfo(GetUrlInfo())) {
      scoped_refptr<FrameNavigationEntry> frame_entry =
          nav_entry->root_node()->frame_entry;
      scoped_refptr<SiteInstanceImpl> new_site_instance =
          base::WrapRefCounted<SiteInstanceImpl>(static_cast<SiteInstanceImpl*>(
              instance->GetRelatedSiteInstance(frame_entry->url()).get()));
      nav_entry->AddOrUpdateFrameEntry(
          frame_tree_node_, NavigationEntryImpl::UpdatePolicy::kReplace,
          frame_entry->item_sequence_number(),
          frame_entry->document_sequence_number(),
          frame_entry->navigation_api_key(), new_site_instance.get(),
          frame_entry->source_site_instance(), frame_entry->url(),
          frame_entry->committed_origin(), frame_entry->referrer(),
          frame_entry->initiator_origin(), frame_entry->initiator_base_url(),
          frame_entry->redirect_chain(), frame_entry->page_state(),
          frame_entry->method(), frame_entry->post_id(),
          frame_entry->blob_url_loader_factory(),
          frame_entry->policy_container_policies()
              ? frame_entry->policy_container_policies()->ClonePtr()
              : nullptr);
    }
  }

  devtools_instrumentation::OnNavigationResponseReceived(*this,
                                                         *response_head_);

  // The response code indicates that this is an error page, but we don't
  // know how to display the content.  We follow Firefox here and show our
  // own error page instead of intercepting the request as a stream or a
  // download.
  if (IsFailedDownload(is_download, response_head_->headers.get())) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(net::ERR_INVALID_RESPONSE),
        false /* skip_throttles */, std::nullopt /* error_page_content */,
        false /* collapse_frame */);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  net::Error net_error = CheckContentSecurityPolicy(
      was_redirected_ /* has_followed_redirect */,
      false /* url_upgraded_after_redirect */, true /* is_response_check */);
  CHECK_NE(net_error, net::ERR_BLOCKED_BY_CLIENT);
  if (net_error != net::OK) {
    OnRequestFailedInternal(network::URLLoaderCompletionStatus(net_error),
                            false /* skip_throttles */,
                            std::nullopt /* error_page_content */,
                            false /* collapse_frame */);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  // TODO(crbug.com/399783247): Remove
  SCOPED_CRASH_KEY_STRING1024("Bug1454273", "rfh_selected_reason",
                              rfh_selected_reason);

  if (HasRenderFrameHost() &&
      !CheckPermissionsPoliciesForFencedFrames(GetOriginToCommit().value())) {
    auto completion_status =
        network::URLLoaderCompletionStatus(net::ERR_ABORTED);
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kFencedFramesPermissionPolicyBlocked;
    OnRequestFailedInternal(completion_status, false /*skip_throttles*/,
                            std::nullopt /*error_page_content*/,
                            false /*collapse_frame*/);
    // DO NOT ADD CODE after this. The previous call to
    // OnRequestFailedInternal has destroyed the NavigationRequest.
    return;
  }

  // Check if the navigation should be allowed to proceed.
  WillProcessResponse();
}

void NavigationRequest::OnRequestFailed(
    const network::URLLoaderCompletionStatus& status) {
  CHECK_NE(status.error_code, net::OK);

  OnRequestFailedInternal(
      status, false /* skip_throttles */, std::nullopt /* error_page_content */,
      status.should_collapse_initiator /* collapse_frame */);
}

std::optional<NavigationEarlyHintsManagerParams>
NavigationRequest::CreateNavigationEarlyHintsManagerParams(
    const network::mojom::EarlyHints& early_hints) {
  TRACE_EVENT("navigation",
              "NavigationRequest::CreateNavigationEarlyHintsManagerParams",
              perfetto::Flow::FromPointer(this));
  // Early Hints preloads should happen only before the final response is
  // received, and limited only in the main frame for now.
  CHECK(!HasRenderFrameHost());
  CHECK(loader_);
  CHECK_LT(state_, WILL_PROCESS_RESPONSE);
  CHECK(!IsSameDocument());
  CHECK(IsInMainFrame());
  CHECK(!IsPageActivation());

  // Getting a RenderProcessHost from a tentative RenderFrameHost during
  // navigation is generally discouraged because it has potential performance
  // impact (the RenderProcessHost could be discarded without actually being
  // used after a cross origin redirect). However, Early Hints preloads require
  // the RenderProcessHost for the tentative RenderFrameHost to set up
  // URLLoaderFactoryParams accordingly. The performance implication should be
  // negligible for Early Hints because these are rarely followed by cross
  // origin redirects. Early Hints preloads before a cross origin redirect don't
  // make sense since such preloads are not available for the redirected page.
  // The CrossOriginRedirectAfterEarlyHints variant of
  // Navigation.MainFrame.TimeToReadyToCommit2 histogram tracks the performance
  // impacts.
  auto result = frame_tree_node_->render_manager()->GetFrameHostForNavigation(
      this, &browsing_context_group_swap_,
      ProcessAllocationContext::CreateForNavigationRequest(
          ProcessAllocationNavigationStage::kHandlingEarlyHints, navigation_id_,
          IsInOutermostMainFrame()));

  // Early hints is an optimization; if it is not possible to get a suitable
  // RenderFrameHost for any reason, just bail out.
  if (!result.has_value()) {
    return std::nullopt;
  }

  RenderProcessHost* process = result.value()->GetProcess();

  // The process is shutting down.
  if (!process->GetBrowserContext())
    return std::nullopt;

  // Compute sandbox flags. Currently just inherit from the frame.
  // TODO(crbug.com/40188470): Think about the right way the specification
  // should handle sandbox flags with Early Hints.
  network::mojom::WebSandboxFlags sandbox_flags =
      commit_params_->frame_policy.sandbox_flags;

  const url::Origin tentative_origin =
      GetOriginForURLLoaderFactoryBeforeResponse(sandbox_flags);

  mojo::PendingRemote<network::mojom::CookieAccessObserver> cookie_observer;
  cookie_observers_->Add(cookie_observer.InitWithNewPipeAndPassReceiver(),
                         CookieAccessDetails::Source::kNonNavigation);

  mojo::PendingRemote<network::mojom::TrustTokenAccessObserver>
      trust_token_observer;
  Clone(trust_token_observer.InitWithNewPipeAndPassReceiver());

  mojo::PendingRemote<network::mojom::SharedDictionaryAccessObserver>
      shared_dictionary_observer;
  Clone(shared_dictionary_observer.InitWithNewPipeAndPassReceiver());

  mojo::PendingRemote<network::mojom::DeviceBoundSessionAccessObserver>
      device_bound_session_observer;
  Clone(device_bound_session_observer.InitWithNewPipeAndPassReceiver());

  network::mojom::URLLoaderFactoryParamsPtr url_loader_factory_params =
      URLLoaderFactoryParamsHelper::CreateForEarlyHintsPreload(
          process, tentative_origin, *this, early_hints,
          std::move(cookie_observer), std::move(trust_token_observer),
          std::move(shared_dictionary_observer),
          std::move(device_bound_session_observer));

  net::IsolationInfo isolation_info = url_loader_factory_params->isolation_info;

  // TODO(crbug.com/40188470): Support DevTools instrumentation and extension's
  // WebRequest API.
  auto loader_factory = url_loader_factory::CreatePendingRemote(
      ContentBrowserClient::URLLoaderFactoryType::kEarlyHints,
      url_loader_factory::TerminalParams::ForNetworkContext(
          process->GetStoragePartition()->GetNetworkContext(),
          std::move(url_loader_factory_params)));

  did_create_early_hints_manager_params_ = true;
  return NavigationEarlyHintsManagerParams(
      tentative_origin, std::move(isolation_info),
      mojo::Remote<network::mojom::URLLoaderFactory>(
          std::move(loader_factory)));
}

void NavigationRequest::OnRequestFailedInternal(
    const network::URLLoaderCompletionStatus& status,
    bool skip_throttles,
    const std::optional<std::string>& error_page_content,
    bool collapse_frame) {
  TRACE_EVENT("navigation", "NavigationRequest::OnRequestFailedInternal",
              perfetto::Flow::FromPointer(this));
  CheckStateTransition(WILL_FAIL_REQUEST);
  CHECK(!(status.error_code == net::ERR_ABORTED &&
          error_page_content.has_value()));
  ScopedCrashKeys crash_keys(*this);

  if (fast_fetch_manager_) {
    // TODO(crbug.com/529425553): Some failed navigations don't call
    // OnRequestFailedInternal(), e.g. this request gets deleted directly
    // (OnNavigationClientDisconnected, new NavigationRequest takes over etc).
    // Consider whether those cases also need to be covered.
    fast_fetch_manager_->OnRequestFailed(*this, status, skip_throttles);
  }

  if (!response() && IsInPrimaryMainFrame() && status.error_code != net::OK &&
      CanRecordEarlyNavigationFailure()) {
    DeclarativePerformanceObserver::RecordEarlyNavigationFailure(
        this, GetStoragePartitionWithCurrentSiteInfo(), status.error_code);
    early_navigation_failure_recorded_ = true;
  }

  if (reserved_prerender_host_info_.has_value()) {
    // Prerender activation must not fail but some reports imply it can actually
    // be failing: crbug.com/411566699, crbug.com/408969974. This dump is useful
    // for debugging it.
    std::string prerender_type = GeneratePrerenderHistogramSuffix(
        GetPrerenderTriggerType(), GetPrerenderHistogramSuffix());
    SCOPED_CRASH_KEY_STRING64("Bug411566699", "prerender_type", prerender_type);
    base::debug::DumpWithoutCrashing();
  }

  if (MaybeEvictFromBackForwardCacheBySubframeNavigation(
          frame_tree_node_->current_frame_host())) {
    // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
    // deleted by the previous calls.
    return;
  }

  // The request failed, the |loader_| must not call the NavigationRequest
  // anymore from now while the error page is being loaded.
  loader_.reset();

  // Reset the RenderFrameHost R1 that had been computed for committing the
  // failed navigation. This breaks the binding between the current
  // NavigationRequest and R1, so that if we create another speculative
  // RenderFrameHost R2 to commit an error page after this, deleting R1 won't
  // try to delete this NavigationRequest along with it.
  render_frame_host_ = std::nullopt;

  // Do not update ssl_info_ on HTTP_RESPONSE_CODE_FAILURE (e.g., HTTP 4xx/5xx
  // errors). In these cases, URLLoaderCompletionStatus does not have ssl_info.
  // The existing ssl_info_ should be preserved by design, so the certificate is
  // retained for error pages, ensuring correct security UI and metrics.
  if (status.error_code != net::ERR_HTTP_RESPONSE_CODE_FAILURE) {
    ssl_info_ = status.ssl_info;
  }

  devtools_instrumentation::OnNavigationRequestFailed(*this, status);

  // TODO(crbug.com/41340435): Check that ssl_info.has_value() if
  // net_error is a certificate error.
  EnterChildTraceEvent("OnRequestFailed", this, "error", status.error_code);
  SetState(WILL_FAIL_REQUEST);
  processing_navigation_throttle_ = false;

  // Ensure the pending entry also gets discarded if it has no other active
  // requests.
  pending_entry_ref_.reset();

  net_error_ = static_cast<net::Error>(status.error_code);
  extended_error_code_ = status.extended_error_code;
  resolve_error_info_ = status.resolve_error_info;
  navigation_handle_timing_.request_failed_time = base::TimeTicks::Now();

  if (MaybeCancelFailedNavigation())
    return;

  // Only notify the NavigationHandleTiming update if the navigation will commit
  // an error page (instead of getting ignored and deleted above).
  GetDelegate()->DidUpdateNavigationHandleTiming(this);

  if (collapse_frame) {
    CHECK_EQ(net::ERR_BLOCKED_BY_CLIENT, status.error_code);
    frame_tree_node_->SetCollapsed(true);
  }

  is_mhtml_or_subframe_ = false;
  // TODO(crbug.com/40736932): Apparently, error pages inherit sandbox
  // flags from their parent/opener. Document loaded from the network
  // shouldn't have any influence over Chrome's internal error page. We should
  // define our own flags, preferably the strictest ones instead.
  ComputePoliciesToCommitForError();

  const auto origin = url::Origin();
  // Set the COOP origin in the policy container builder before FinalPolicies()
  // is called.
  coop_status_.EnforceCOOP(
      policy_container_builder_->FinalPolicies().cross_origin_opener_policy,
      origin, net::NetworkAnonymizationKey::CreateTransient());

  SelectFrameHostForOnRequestFailedInternal(status.exists_in_cache,
                                            skip_throttles, error_page_content);
}

void NavigationRequest::SelectFrameHostForOnRequestFailedInternal(
    bool exists_in_cache,
    bool skip_throttles,
    const std::optional<std::string>& error_page_content) {
  CHECK(!HasRenderFrameHost())
      << "`render_frame_host_` should not be set before the "
         "`NavigationRequest` starts to select the RFH.";

  switch (ComputeErrorPageProcess()) {
    case ErrorPageProcess::kCurrentProcess:
      // There's no way to get here with a same-document navigation, it would
      // need to be on a document that was not blocked but became blocked, but
      // same document navigations don't go to the network so it wouldn't know
      // about the change.
      CHECK(!IsSameDocument());
      break;
    case ErrorPageProcess::kIsolatedProcess:
      // In this case we are isolating the error page from the source and
      // destination process, and want it to go to a new process.
      //
      // TODO(nasko): Investigate whether GetFrameHostForNavigation can properly
      // account for clearing the expected process if it clears the speculative
      // RenderFrameHost. See https://crbug.com/793127.
      ResetExpectedProcess();
      [[fallthrough]];
    case ErrorPageProcess::kDestinationProcess:
      // A same-document navigation would normally attempt to navigate the
      // current document, but since we will be presenting an error instead and
      // there will not be a document to navigate. We always make an error here
      // into a cross-document navigation. See https://crbug.com/1018385 and
      // https://crbug.com/1125106.
      common_params_->navigation_type =
          ConvertToCrossDocumentType(common_params_->navigation_type);
      break;
    case ErrorPageProcess::kNotErrorPage:
    case ErrorPageProcess::kPostCommitErrorPage:
      NOTREACHED();
  }

  RenderFrameHostImpl* render_frame_host = nullptr;
  if (auto result =
          frame_tree_node_->render_manager()->GetFrameHostForNavigation(
              this, &browsing_context_group_swap_,
              ProcessAllocationContext::CreateForNavigationRequest(
                  ProcessAllocationNavigationStage::kAfterFailure,
                  navigation_id_, IsInOutermostMainFrame()));
      result.has_value()) {
    render_frame_host = result.value();
  } else {
    switch (result.error()) {
      case GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame:
        // TODO(crbug.com/40250311): This was unhandled
        // before and remains explicitly unhandled. This branch may be
        // removed in the future.
        break;
      case GetFrameHostForNavigationFailed::kBlockedByPendingCommit:
        resume_commit_closure_ = base::BindOnce(
            &NavigationRequest::SelectFrameHostForOnRequestFailedInternal,
            weak_factory_.GetWeakPtr(), exists_in_cache, skip_throttles,
            error_page_content);
        frame_tree_node_->render_manager()
            ->speculative_frame_host()
            ->RecordMetricsForBlockedGetFrameHostAttempt(
                /* commit_attempt=*/true);
        return;
    }
  }

  render_frame_host_ = render_frame_host->GetSafeRef();

  // Update the associated RenderFrameHost type.
  SetAssociatedRFHType(
      GetRenderFrameHost() ==
              frame_tree_node_->render_manager()->current_frame_host()
          ? AssociatedRenderFrameHostType::CURRENT
          : AssociatedRenderFrameHostType::SPECULATIVE);

  // Set the site URL now if it hasn't been set already.  It's possible to get
  // here if we navigate to an error out of an initial "blank" SiteInstance.
  // Also mark the process as used, since it will be hosting an error page.
  SiteInstanceImpl* instance = GetRenderFrameHost()->GetSiteInstance();
  if (!instance->HasSite())
    instance->ConvertToDefaultOrSetSite(GetUrlInfo());
  GetRenderFrameHost()->GetProcess()->SetIsUsed();

  // The check for WebUI should be performed only if error page isolation is
  // enabled for this failed navigation. It is possible for subframe error page
  // to be committed in a WebUI process as shown in https://crbug.com/944086.
  if (frame_tree_node_->IsErrorPageIsolationEnabled()) {
    CHECK(Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
        GetRenderFrameHost(), GetUrlInfo(),
        /* is_renderer_initiated_check */ false));
  }

  has_stale_copy_in_cache_ = exists_in_cache;

  if (skip_throttles) {
    // The NavigationHandle shouldn't be notified about renderer-debug URLs.
    // They will be handled by the renderer process.
    PrepareToCommitErrorPage(error_page_content);
  } else {
    // Check if the navigation should be allowed to proceed.
    WillFailRequest();
  }
}

NavigationRequest::ErrorPageProcess
NavigationRequest::ComputeErrorPageProcess() {
  if (net_error_ == net::OK) {
    return ErrorPageProcess::kNotErrorPage;
  }

  if (state_ < NavigationRequest::CANCELING) {
    if (browser_initiated_error_navigation_type_ ==
        BrowserInitiatedErrorNavigationType::kPostCommit) {
      // Post-commit error page normally goes through the "non-error page"
      // navigation path, so treat them specially here too.
      return ErrorPageProcess::kPostCommitErrorPage;
    }

    // Otherwise, this is a normal error navigation, which should fall out of
    // this block and use existing process selection behavior.
  }

  // By policy we can isolate all error pages from both the current and
  // destination processes.
  if (frame_tree_node_->IsErrorPageIsolationEnabled())
    return ErrorPageProcess::kIsolatedProcess;

  // Decide whether to leave the error page in the original process.
  // * If this was a renderer-initiated navigation, and the request is blocked
  //   because the initiating document wasn't allowed to make the request,
  //   commit the error in the existing process. This is a strategy to to
  //   avoid creating a process for the destination, which may belong to an
  //   origin with a higher privilege level.
  // * Error pages resulting from errors like network outage, no network, or
  //   DNS error can reasonably expect that a reload at a later point in time
  //   would work. These should be allowed to transfer away from the current
  //   process: they do belong to whichever process that will host the
  //   destination URL, as a reload will end up committing in that process
  //   anyway.
  // * Error pages that arise during browser-initiated navigations to blocked
  //   URLs should be allowed to transfer away from the current process, which
  //   didn't request the navigation and may have a higher privilege level
  //   than the blocked destination.
  if (net::IsRequestBlockedError(net_error_) && !browser_initiated()) {
    return ErrorPageProcess::kCurrentProcess;
  }
  return ErrorPageProcess::kDestinationProcess;
}

void NavigationRequest::OnStartChecksComplete(
    NavigationThrottle::ThrottleCheckResult result) {
  TRACE_EVENT("navigation", "NavigationRequest::OnStartChecksComplete",
              perfetto::Flow::FromPointer(this));
  CHECK(result.action() != NavigationThrottle::DEFER);
  CHECK(result.action() != NavigationThrottle::BLOCK_RESPONSE);

  if (on_start_checks_complete_closure_)
    std::move(on_start_checks_complete_closure_).Run();
  // Abort the request if needed. This will destroy the NavigationRequest.
  if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
      result.action() == NavigationThrottle::CANCEL ||
      result.action() == NavigationThrottle::BLOCK_REQUEST ||
      result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) {
#if DCHECK_IS_ON()
    if (result.action() == NavigationThrottle::BLOCK_REQUEST) {
      DCHECK(net::IsRequestBlockedError(result.net_error_code()));
    } else if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE) {
      // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE.
      DCHECK_EQ(result.net_error_code(), net::ERR_ABORTED);
    }
#endif

    bool collapse_frame =
        result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE;

    // If the start checks completed synchronously, which could happen if there
    // is no onbeforeunload handler or if a NavigationThrottle cancelled it,
    // then this could cause reentrancy into NavigationController. So use a
    // PostTask to avoid that.
    auto completion_status =
        network::URLLoaderCompletionStatus(result.net_error_code());
    if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
        result.action() == NavigationThrottle::CANCEL) {
      error_navigation_trigger_ =
          ErrorNavigationTrigger::kNavigationThrottleCancel;
    } else {
      error_navigation_trigger_ =
          ErrorNavigationTrigger::kNavigationThrottleBlock;
    }
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&NavigationRequest::OnRequestFailedInternal,
                       weak_factory_.GetWeakPtr(), std::move(completion_status),
                       true /* skip_throttles */, result.error_page_content(),
                       collapse_frame));

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  StoragePartition* partition = GetStoragePartitionWithCurrentSiteInfo();
  CHECK(partition);

  // A WebContents that disallows service worker control (see
  // WebContents::PrivilegedParams) skips the service worker for the main
  // resource of every navigation it hosts -- across all frame trees, and for
  // both renderer-initiated and browser-initiated navigations -- so no document
  // it commits is ever controlled by a ServiceWorker. This is defense in depth
  // alongside the per-client service-worker ineligibility bit (see
  // ServiceWorkerClient::IsEligibleForServiceWorkerController).
  if (frame_tree_node_->frame_tree()
          .delegate()
          ->DoesWebContentsDisallowServiceWorkerControl()) {
    begin_params_->skip_service_worker = true;
  }

  // |loader_| should not exist if the service worker handle
  // will be destroyed, since it holds raw pointers to it. See the
  // comment in the header for |loader_|.
  CHECK(!loader_);

  // Only initialize the ServiceWorkerMainResourceHandle if it can be created
  // for this frame.
  bool can_create_service_worker =
      (frame_tree_node_->pending_frame_policy().sandbox_flags &
       network::mojom::WebSandboxFlags::kOrigin) !=
      network::mojom::WebSandboxFlags::kOrigin;
  if (can_create_service_worker) {
    ServiceWorkerContextWrapper* service_worker_context =
        static_cast<ServiceWorkerContextWrapper*>(
            partition->GetServiceWorkerContext());
    std::string fetch_event_client_id;
    // TODO(crbug.com/368087661): According to the spec this should be fetch
    // request's client but it is not here in the implementation.
    if (auto client = frame_tree_node_->current_frame_host()
                          ->GetLastCommittedServiceWorkerClient()) {
      fetch_event_client_id = client->client_uuid();
    }
    service_worker_handle_ = std::make_unique<ServiceWorkerMainResourceHandle>(
        service_worker_context,
        base::BindRepeating(&NavigationRequest::OnServiceWorkerAccessed,
                            weak_factory_.GetWeakPtr()),
        std::move(fetch_event_client_id));
  }

  // Mark the fetch_start (Navigation Timing API).
  commit_params_->navigation_timing->fetch_start = base::TimeTicks::Now();

  // Ensure that normal history navigations can dispatch the Navigation API's
  // navigate event as the navigation is starting. Cases without a UrlLoader
  // are handled in OnWillCommitWithoutUrlLoaderChecksComplete.
  MaybeDispatchNavigateEventForCrossDocumentTraversal();

  std::unique_ptr<NavigationUIData> navigation_ui_data;
  if (navigation_ui_data_)
    navigation_ui_data = navigation_ui_data_->Clone();

  // Give DevTools a chance to override begin params (headers, skip SW)
  // before actually loading resource.
  bool report_raw_headers = false;
  GURL devtools_referrer_override;
  devtools_instrumentation::ApplyNetworkRequestOverrides(
      frame_tree_node_, begin_params_.get(), &report_raw_headers,
      &devtools_user_agent_override_, &devtools_accept_language_override_,
      &devtools_referrer_override);

  if (!devtools_referrer_override.is_empty()) {
    // When the `Referer` header is overridden by DevTools (e.g. by CDP command
    // `Network.setExtraHTTPHeaders`), bypass referrer sanitization and set the
    // custom referrer of the navigation params to the overridden referrer
    // value. See crbug.com/437323961.
    common_params_->referrer->url = devtools_referrer_override;
  }

  devtools_instrumentation::OnNavigationRequestWillBeSent(*this);

  {
    // Merge headers with embedder's headers.
    net::HttpRequestHeaders headers;
    headers.AddHeadersFromString(begin_params_->headers);
    // `removed_headers` is only for redirects (see
    // `NavigationRequest::RemoveRequestHeader()`) and must be empty here.
    CHECK(headers_update_params_.removed_headers.empty());
    headers.MergeFrom(headers_update_params_.modified_headers);
    headers_update_params_.Clear();
    begin_params_->headers = headers.ToString();
    request_headers_.reset();
  }

  // TODO(clamy): Avoid cloning the navigation params and create the
  // ResourceRequest directly here.

  auto loader_type = NavigationURLLoader::LoaderType::kRegular;
  network::mojom::URLResponseHeadPtr cached_response_head = nullptr;
  if (IsServedFromBackForwardCache()) {
    loader_type = NavigationURLLoader::LoaderType::kNoopForBackForwardCache;
    cached_response_head = GetRenderFrameHostRestoredFromBackForwardCache()
                               ->last_response_head()
                               ->Clone();
  } else if (IsPrerenderedPageActivation()) {
    loader_type = NavigationURLLoader::LoaderType::kNoopForPrerender;
    CHECK(activating_prerender_host_id_.has_value());
    const network::mojom::URLResponseHeadPtr& last_response_head =
        GetPrerenderHostRegistry()
            .GetRenderFrameHostForReservedHost(*activating_prerender_host_id_)
            ->last_response_head();
    // As PrerenderCommitDeferringCondition makes sure to finish the prerender
    // initial navigation before activation, a valid last_response_head should
    // be always stored before reaching here.
    // TODO(522882268): CHECK-exclusion: Convert to a CHECK once we are
    // confident it won't be triggered.
    DCHECK(last_response_head);
    cached_response_head = last_response_head->Clone();
  } else if (IsInitialWebUINavigation()) {
    loader_type = NavigationURLLoader::LoaderType::kNoopForInitialWebUI;
  }

  // Sandbox flags inherited from the frame. In particular, this does not
  // include:
  // - Sandbox flags inherited from the creator via the PolicyContainer.
  // - Sandbox flags forced for MHTML documents.
  // - Sandbox flags from the future response via CSP.
  // It is used by the ExternalProtocolHandler to ensure sandboxed iframe won't
  // navigate the user toward a different application, which can be seen as a
  // main frame navigation somehow.
  network::mojom::WebSandboxFlags sandbox_flags =
      commit_params_->frame_policy.sandbox_flags;

  // Reset the compositor lock before starting the loader.
  compositor_lock_.reset();

  BrowserContext* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();

  // DevTools throttling profiles are only associated with local frame roots,
  // not each individual frame.
  RenderFrameHostImpl* local_root_rfh = frame_tree_node_->current_frame_host();
  while (!local_root_rfh->is_local_root()) {
    local_root_rfh = local_root_rfh->GetParent();
  }
  CHECK(local_root_rfh);

  loader_ = NavigationURLLoader::Create(
      browser_context, partition,
      std::make_unique<NavigationRequestInfo>(
          common_params_->Clone(), begin_params_.Clone(), sandbox_flags,
          GetIsolationInfo(),
          frame_tree_node_->current_frame_host()->IsInPrimaryMainFrame(),
          frame_tree_node_->IsOutermostMainFrame(),
          frame_tree_node_->IsMainFrame(),
          frame_tree_node_->AreAncestorsSecure(),
          frame_tree_node_->frame_tree_node_id(), report_raw_headers,
          upgrade_if_insecure_,
          blob_url_loader_factory_ ? blob_url_loader_factory_->Clone()
                                   : nullptr,
          devtools_navigation_token(), local_root_rfh->devtools_frame_token(),
          BuildClientSecurityStateForNavigationFetch(), IsPdf(),
          GetInitiatorProcessId(), initiator_document_token_,
          allow_cookies_from_browser_, navigation_id_, is_ad_tagged(),
          force_no_https_upgrade_),
      std::move(navigation_ui_data), service_worker_handle_.get(),
      std::move(prefetched_signed_exchange_cache_), this, loader_type,
      CreateCookieAccessObserver(), CreateTrustTokenAccessObserver(),
      CreateSharedDictionaryAccessObserver(),
      static_cast<StoragePartitionImpl*>(partition)
          ->CreateURLLoaderNetworkObserverForNavigationRequest(*this),
      NetworkServiceDevToolsObserver::MakeSelfOwned(frame_tree_node_),
      CreateDeviceBoundSessionObserver(), std::move(cached_response_head));
  CHECK(!HasRenderFrameHost());

  // If needed, perform an early RenderFrameHost swap after notifying observers
  // with DidStartNavigation, after processing WillStartRequest throttle events,
  // and after creating the NavigationURLLoader above (which needs the old
  // current_frame_host()). This (1) avoids performing the early swap in case
  // the navigation gets canceled prior to getting here, and (2) ensures that
  // DidStartNavigation and WillStartRequest implementations are not disrupted
  // by the early swap and don't see a RenderFrameHostChanged event prior to a
  // navigation actually starting.
  //
  // TODO(crbug.com/40276607): Remove the `is_called_after_did_start_navigation`
  // param once all early swaps are done from here.
  frame_tree_node_->render_manager()->PerformEarlyRenderFrameHostSwapIfNeeded(
      this, /*is_called_after_did_start_navigation=*/true);

  base::UmaHistogramTimes(
      base::StrCat({"Navigation.WillStartRequestToLoaderStart.",
                    IsInMainFrame() ? "MainFrame" : "Subframe"}),
      base::TimeTicks::Now() - will_start_request_time_);
  absl::Cleanup scoped_set_now = [navigation_request =
                                      weak_factory_.GetWeakPtr()] {
    if (navigation_request) {
      navigation_request->navigation_handle_timing_.loader_start_time =
          base::TimeTicks::Now();
      navigation_request->GetDelegate()->DidUpdateNavigationHandleTiming(
          navigation_request.get());
    }
  };

  base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
  loader_->Start();

  if (!this_ptr) {
    // `this` have been deleted by NavigationURLLoader::Start
    // DO NOT ADD CODE HERE.
    return;
  }

  // Try to create the speculative RFH after sending the network request
  // if DeferSpeculativeRFHCreation is enabled.
  // Only create the speculative RFH if it is a normal loading rather than
  // a BFCache restore or prerender activation or a sync initial WebUI
  // navigation. Otherwise, `OnResponseStarted` will have already been called
  // instantly and the navigation would have reached READY_TO_COMMIT already
  // with an appropriate RenderFrameHost already chosen, and the
  // creation of the speculative RFH is redundant.
  // TODO(crbug.com/394732486): All the speculative RFH creation will be skipped
  // if kDeferSpeculativeRFHWaitUntilFinalResponse is set. The behavior can
  // be more adaptive by limiting to sites that commonly use COOP.
  if (base::FeatureList::IsEnabled(features::kDeferSpeculativeRFHCreation) &&
      !kDeferSpeculativeRFHWaitUntilFinalResponse.Get() &&
      state_ < NavigationState::READY_TO_COMMIT &&
      GetAssociatedRFHType() == AssociatedRenderFrameHostType::NONE) {
    if (features::kCreateSpeculativeRFHFilterRestore.Get() &&
        loader_type != NavigationURLLoader::LoaderType::kRegular) {
      return;
    }
    auto create_speculative_rfh_task = base::BindOnce(
        [](base::WeakPtr<NavigationRequest> request, int64_t navigation_id) {
          if (!request || request->state_ >= WILL_PROCESS_RESPONSE ||
              request->HasRenderFrameHost()) {
            return;
          }
          auto rfh_creation_result =
              request->frame_tree_node_->render_manager()
                  ->GetFrameHostForNavigation(
                      request.get(), &request->browsing_context_group_swap_,
                      ProcessAllocationContext::CreateForNavigationRequest(
                          ProcessAllocationNavigationStage::
                              kAfterNetworkRequest,
                          navigation_id, request->IsInOutermostMainFrame()));
          if (rfh_creation_result.has_value()) {
            request->SetExpectedProcessIfAssociated();
          }
        },
        weak_factory_.GetWeakPtr(), navigation_id_);
    int delay_ms = features::kCreateSpeculativeRFHDelayMs.Get();
    if (delay_ms > 0) {
      GetUIThreadTaskRunner()->PostDelayedTask(
          FROM_HERE, std::move(create_speculative_rfh_task),
          base::Milliseconds(delay_ms));
    } else {
      std::move(create_speculative_rfh_task).Run();
    }
  }
}

void NavigationRequest::OnServiceWorkerAccessed(
    const GURL& scope,
    AllowServiceWorkerResult allowed) {
  GetDelegate()->OnServiceWorkerAccessed(this, scope, allowed);
}

network::mojom::WebSandboxFlags NavigationRequest::SandboxFlagsInitiator() {
  if (!initiator_navigation_state_) {
    return network::mojom::WebSandboxFlags::kNone;
  }
  return initiator_navigation_state_impl()
      ->policy_container_policies()
      .sandbox_flags;
}

network::mojom::WebSandboxFlags NavigationRequest::SandboxFlagsInherited() {
  return commit_params_->frame_policy.sandbox_flags;
}

network::mojom::WebSandboxFlags NavigationRequest::SandboxFlagsToCommit() {
  CHECK_GE(state_, WILL_PROCESS_RESPONSE);
  CHECK(!IsSameDocument());
  CHECK(!IsPageActivation());
  return policy_container_builder_->FinalPolicies().sandbox_flags;
}

void NavigationRequest::MaybeAddResourceTimingEntryForCancelledNavigation() {
  // Some navigations are cancelled even before requesting and receiving a
  // response. Those cases are not supported and the ResourceTiming is not
  // reported to the parent.
  if (!response()) {
    return;
  }

  blink::mojom::SubframeResourceLengthsPtr resource_lengths;
  // response() uses -1 to mean a missing response and 0 for a response with
  // headers but no body.
  if (response()->encoded_data_length >= 0) {
    resource_lengths = blink::mojom::SubframeResourceLengths::New();
    resource_lengths->encoded_data_length =
        base::ByteSize(base::as_unsigned(response()->encoded_data_length));
  }
  AddResourceTimingEntryForFailedSubframeNavigation(
      base::TimeTicks::Now(), std::move(resource_lengths));
}

void NavigationRequest::AddResourceTimingEntryForFailedSubframeNavigation(
    const network::URLLoaderCompletionStatus& status) {
  auto resource_lengths = blink::mojom::SubframeResourceLengths::New();
  resource_lengths->encoded_data_length = status.encoded_data_length;
  resource_lengths->encoded_body_length = status.encoded_body_length;
  resource_lengths->decoded_body_length = status.decoded_body_length;
  AddResourceTimingEntryForFailedSubframeNavigation(
      status.completion_time, std::move(resource_lengths));
}

void NavigationRequest::AddResourceTimingEntryForFailedSubframeNavigation(
    base::TimeTicks completion_time,
    blink::mojom::SubframeResourceLengthsPtr resource_lengths) {
  // For TAO-fail navigations, we would resort to fallback timing.
  // See HTMLFrameOwnerElement::ReportFallbackResourceTimingIfNeeded().
  CHECK(response());
  if (commit_params().navigation_timing->parent_resource_timing_access ==
      blink::mojom::ParentResourceTimingAccess::kDoNotReport) {
    return;
  }

  network::mojom::URLResponseHeadPtr response_head = response()->Clone();

  bool allow_response_details =
      commit_params().navigation_timing->parent_resource_timing_access ==
      blink::mojom::ParentResourceTimingAccess::kReportWithResponseDetails;

  GetParentFrame()->AddResourceTimingEntryForFailedSubframeNavigation(
      frame_tree_node(), common_params().navigation_start,
      commit_params().navigation_timing->redirect_end, completion_time,
      original_url_, common_params().url, std::move(response_head),
      allow_response_details, std::move(resource_lengths));
}

void NavigationRequest::OnRedirectChecksComplete(
    NavigationThrottle::ThrottleCheckResult result) {
  TRACE_EVENT("navigation", "NavigationRequest::OnRedirectChecksComplete",
              perfetto::Flow::FromPointer(this));
  CHECK(result.action() != NavigationThrottle::DEFER);
  CHECK(result.action() != NavigationThrottle::BLOCK_RESPONSE);
  CHECK(!IsPageActivation());

  bool collapse_frame =
      result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE;

  // Abort the request if needed. This will destroy the NavigationRequest.
  if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
      result.action() == NavigationThrottle::CANCEL) {
    // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE if needed.
    CHECK(result.action() == NavigationThrottle::CANCEL ||
          result.net_error_code() == net::ERR_ABORTED);
    auto completion_status =
        network::URLLoaderCompletionStatus(result.net_error_code());
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kNavigationThrottleCancel;
    OnRequestFailedInternal(std::move(completion_status),
                            true /* skip_throttles */,
                            result.error_page_content(), collapse_frame);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (result.action() == NavigationThrottle::BLOCK_REQUEST ||
      result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE) {
    CHECK(net::IsRequestBlockedError(result.net_error_code()));
    auto completion_status =
        network::URLLoaderCompletionStatus(result.net_error_code());
    if (result.net_error_code() == net::ERR_ABORTED) {
      error_navigation_trigger_ =
          ErrorNavigationTrigger::kNavigationThrottleBlock;
    }
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(result.net_error_code()),
        true /* skip_throttles */, result.error_page_content(), collapse_frame);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  devtools_instrumentation::OnNavigationRequestWillBeSent(*this);

  network::HttpRequestHeadersUpdateParams headers_update_params =
      std::move(headers_update_params_);
  headers_update_params_.Clear();

  if (remove_extra_headers_on_cross_origin_redirect_ &&
      !url::Origin::Create(commit_params_->redirects.back())
           .IsSameOriginWith(common_params_->url)) {
    CHECK(commit_params_->is_browser_initiated);

    // Browser-initiated navigations should always have a NavigationEntry.
    NavigationEntryImpl* nav_entry =
        GetNavigationController()->GetEntryWithUniqueIDIncludingPending(
            nav_entry_id_);
    CHECK(nav_entry);

    net::HttpRequestHeaders headers;
    headers.AddHeadersFromString(nav_entry->extra_headers());
    for (const auto& header : headers.GetHeaderVector()) {
      headers_update_params.removed_headers.push_back(header.key);
    }

    // Update the NavigationEntry so that history navigations don't include the
    // extra headers.
    nav_entry->set_extra_headers(std::string());
    nav_entry->SetRemoveExtraHeadersOnCrossOriginRedirect(false);
  }


  // Removes all Client Hints from the request, that were passed on from the
  // previous one.
  for (const auto& elem : network::GetClientHintToNameMap()) {
    const auto& header = elem.second;
    headers_update_params.removed_headers.push_back(header);
  }

  // Add any required Client Hints to the current request.
  BrowserContext* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();
  ClientHintsControllerDelegate* client_hints_delegate =
      browser_context->GetClientHintsControllerDelegate();
  if (client_hints_delegate) {
    net::HttpRequestHeaders client_hints_extra_headers;
    const url::Origin& source_origin =
        url::Origin::Create(commit_params_->redirects.back());
    const network::mojom::URLResponseHead* response_head =
        commit_params_->redirect_params.back()->response_head.get();
    ParseAndPersistAcceptCHForNavigation(
        source_origin, response_head->ssl_info, response_head->parsed_headers,
        response_head->headers.get(), browser_context, client_hints_delegate,
        frame_tree_node_);

    AddNavigationRequestClientHintsHeaders(
        GetTentativeOriginAtRequestTime(), &client_hints_extra_headers,
        browser_context, client_hints_delegate, is_overriding_user_agent(),
        frame_tree_node_, commit_params_->frame_policy.container_policy,
        common_params_->url);
    headers_update_params.modified_headers.MergeFrom(
        client_hints_extra_headers);
    // On a redirect, unless devtools has overridden the User-Agent header, we
    // should send the User-Agent string based on policy.
    if (!devtools_user_agent_override_) {
      headers_update_params.modified_headers.SetHeader(
          net::HttpRequestHeaders::kUserAgent,
          ComputeUserAgentValue(GetUserAgentOverride()));
    }
  }

  // Add reduced accept language header to the current request.
  // If devtools has overridden the Accept-Language header, skip reduce
  // Accept-Language header.
  if (auto reduce_accept_lang_utils =
          ReduceAcceptLanguageUtils::Create(browser_context);
      reduce_accept_lang_utils && !devtools_accept_language_override_) {
    if (!ReduceAcceptLanguageUtils::CheckDisableReduceAcceptLanguageOriginTrial(
            common_params_->url, frame_tree_node_,
            browser_context->GetOriginTrialsControllerDelegate())) {
      net::HttpRequestHeaders accept_language_headers;
      std::optional<std::string> reduced_accept_language =
          reduce_accept_lang_utils.value()
              .AddNavigationRequestAcceptLanguageHeaders(
                  url::Origin::Create(common_params_->url), frame_tree_node_,
                  &accept_language_headers);
      commit_params_->reduced_accept_language =
          reduced_accept_language.value_or("");
      headers_update_params.modified_headers.MergeFrom(accept_language_headers);
    } else {
      // Remove the Accept-Language header passed from previous request, if any.
      headers_update_params.removed_headers.push_back(
          net::HttpRequestHeaders::kAcceptLanguage);
      commit_params_->reduced_accept_language = "";
    }
  }

  loader_->FollowRedirect(std::move(headers_update_params));
}

void NavigationRequest::OnFailureChecksComplete(
    NavigationThrottle::ThrottleCheckResult result) {
  TRACE_EVENT("navigation", "NavigationRequest::OnFailureChecksComplete",
              perfetto::Flow::FromPointer(this));
  // This method is called as a result of getting to the end of
  // OnRequestFailedInternal(), which calls WillFailRequest(), which
  // runs the throttles, which eventually call back to this method.
  CHECK(result.action() != NavigationThrottle::DEFER);

  // The throttle may have changed the net_error_code, so we set the
  // `net_error_` again, overriding what OnRequestFailedInternal() set.
  net::Error old_net_error = net_error_;
  ErrorPageProcess old_error_page_process = ComputeErrorPageProcess();
  net_error_ = result.net_error_code();

  // The new `net_error_` value may mean we want to cancel the navigation.
  if (MaybeCancelFailedNavigation()) {
    return;
  }

  // FIXME: Should we clear out |extended_error_code_| here?

  // Ensure that WillFailRequest() isn't changing the error code in a way that
  // switches the destination process for the error page - see
  // https://crbug.com/817881.
  // NOTE: The throttle may change the error code to cancel a navigation which
  // could lead to a failure here, and therefore this must be done after
  // `MaybeCancelFailedNavigation`.
  CHECK_EQ(old_error_page_process, ComputeErrorPageProcess())
      << " Unsupported error code change in WillFailRequest(): from "
      << old_net_error << " to " << net_error_;

  // The OnRequestFailedInternal() did not commit the error page as it
  // deferred to WillFailRequest(), which has called through to here, and
  // now we are finally ready to commit the error page. This will be committed
  // to the RenderFrameHost previously chosen in OnRequestFailedInternal().
  PrepareToCommitErrorPage(result.error_page_content());
  // DO NOT ADD CODE after this. The previous call may have caused the
  // destruction of the NavigationRequest.
}

void NavigationRequest::OnWillProcessResponseChecksComplete(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK(result.action() != NavigationThrottle::DEFER);

  // If the NavigationThrottles allowed the navigation to continue, have the
  // processing of the response resume in the network stack.
  if (result.action() == NavigationThrottle::PROCEED) {
    // If this is a download and NavigationThrottles allowed it, intercept the
    // navigation response, pass it to DownloadManager, and cancel the
    // navigation.
    if (is_download_) {
      // TODO(arthursonzogni): Pass the real ResourceRequest. For the moment
      // only these parameters will be used, but it may evolve quickly.
      auto resource_request = std::make_unique<network::ResourceRequest>();
      resource_request->url = common_params_->url;
      resource_request->method = common_params_->method;
      resource_request->request_initiator = common_params_->initiator_origin;
      resource_request->referrer = common_params_->referrer->url;
      resource_request->has_user_gesture =
          common_params_->has_possibly_filtered_user_gesture;
      resource_request->mode = network::mojom::RequestMode::kNavigate;
      resource_request->transition_type = common_params_->transition;
      resource_request->is_reload_navigation =
          NavigationTypeUtils::IsReload(common_params_->navigation_type);
      resource_request->trusted_params =
          network::ResourceRequest::TrustedParams();
      resource_request->trusted_params->isolation_info = GetIsolationInfo();

      BrowserContext* browser_context =
          frame_tree_node_->navigator().controller().GetBrowserContext();
      DownloadManagerImpl* download_manager = static_cast<DownloadManagerImpl*>(
          browser_context->GetDownloadManager());
      if (!response_head_->client_side_content_decoding_types.empty()) {
        CHECK(base::FeatureList::IsEnabled(
            network::features::kRendererSideContentDecoding));
        // If content decoding is required, perform the decoding in the network
        // service.

        // Attempt to create the data pipe needed for content decoding.
        auto data_pipe_pair =
            network::ContentDecodingInterceptor::CreateDataPipePair(
                network::ContentDecodingInterceptor::ClientType::kDownload);
        if (!data_pipe_pair) {
          // Handle data pipe creation failure. This is rare but can happen if
          // shared memory is exhausted. In such a situation, the page load will
          // likely fail anyway as resources cannot be properly loaded. However,
          // we should avoid crashing the browser process or attempting to
          // download the raw encoded body. Instead, just abort the navigation
          // request.
          auto completion_status =
              network::URLLoaderCompletionStatus(net::ERR_ABORTED);
          error_navigation_trigger_ =
              ErrorNavigationTrigger::kContentDecoderDataPipeCreationFailed;
          OnRequestFailedInternal(completion_status,
                                  /*skip_throttles=*/false,
                                  /*error_page_content=*/std::nullopt,
                                  /*collapse_frame=*/false);
          // DO NOT ADD CODE after this. The previous call to
          // OnRequestFailedInternal has destroyed the NavigationRequest.
          return;
        }
        network::ContentDecodingInterceptor::InterceptOnNetworkService(
            *GetNetworkService(),
            response_head_->client_side_content_decoding_types,
            url_loader_client_endpoints_, response_body_,
            std::move(*data_pipe_pair));
      }
      base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
      download_manager->InterceptNavigation(
          std::move(resource_request), redirect_chain_, response_head_.Clone(),
          std::move(response_body_), std::move(url_loader_client_endpoints_),
          ssl_info_.has_value() ? ssl_info_->cert_status : 0,
          frame_tree_node_->frame_tree_node_id(),
          from_download_cross_origin_redirect_);
      CHECK(this_ptr);

      auto completion_status =
          network::URLLoaderCompletionStatus(net::ERR_ABORTED);
      error_navigation_trigger_ =
          ErrorNavigationTrigger::kShouldNotRenderResponse;
      OnRequestFailedInternal(completion_status, false /*skip_throttles*/,
                              std::nullopt /*error_page_content*/,
                              false /*collapse_frame*/);
      // DO NOT ADD CODE after this. The previous call to OnRequestFailed has
      // destroyed the NavigationRequest.
      return;
    }

    // Per https://whatwg.org/C/iframe-embed-object.html#the-object-element,
    // this implements step 4.7 from "determine what the object element
    // represents": "If the load failed (e.g. there was an HTTP 404 error, there
    // was a DNS error), fire an event named error at the element, then jump to
    // the step below labeled fallback."
    //
    // This case handles HTTP errors, which are otherwise considered a
    // "successful" navigation.
    //
    // TODO(dcheng): According to the standard, an <object> element shouldn't
    // even have a browsing context associated with it unless a successful
    // navigation would commit in it.
    if (response()->headers &&
        ShouldRenderFallbackContentForResponse(*response()->headers)) {
      // Spin up a helper to drain the response body for this navigation. The
      // helper will request fallback content (triggering completion) and report
      // the resource timing info once the entire response body is drained.
      //
      // The response body fetcher takes advantage of base::SupportsUserData to
      // ensure that the fetcher does not outlive `this`. This ensures that the
      // fallback / resource timing are only reported if the navigation request
      // is logically still pending.
      auto completion_status =
          network::URLLoaderCompletionStatus(net::ERR_ABORTED);
      error_navigation_trigger_ =
          ErrorNavigationTrigger::kShouldRenderFallbackContent;
      ObjectNavigationFallbackBodyLoader::CreateAndStart(
          *this, std::move(response_body_),
          std::move(url_loader_client_endpoints_),
          base::BindOnce(&NavigationRequest::OnRequestFailedInternal,
                         weak_factory_.GetWeakPtr(), completion_status,
                         false /* skip_throttles */,
                         std::nullopt /* error_page_content */,
                         false /* collapse_frame */));
      // Unlike the other early returns, intentionally skip calling
      // `OnRequestFailedInternal()`. This allows the response body drainer to
      // track the lifetime of `this` and skip the remaining work if `this` is
      // deleted before the response body is completely loaded.
      return;
    }
  }

  // Abort the request if needed. This includes requests that were blocked by
  // NavigationThrottles and requests that should not commit (e.g. downloads,
  // 204/205s). This will destroy the NavigationRequest.
  if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
      result.action() == NavigationThrottle::CANCEL ||
      !response_should_be_rendered_) {
    MaybeAddResourceTimingEntryForCancelledNavigation();

    // TODO(clamy): distinguish between CANCEL and CANCEL_AND_IGNORE.
    if (!response_should_be_rendered_) {
      auto completion_status =
          network::URLLoaderCompletionStatus(net::ERR_ABORTED);
      error_navigation_trigger_ =
          ErrorNavigationTrigger::kShouldNotRenderResponse;
      OnRequestFailedInternal(completion_status, true /* skip_throttles */,
                              std::nullopt /* error_page_content */,
                              false /* collapse_frame */);

      // DO NOT ADD CODE after this. The previous call to
      // OnRequestFailedInternal has destroyed the NavigationRequest.
      return;
    }

    CHECK(result.action() == NavigationThrottle::CANCEL ||
          result.net_error_code() == net::ERR_ABORTED);
    auto completion_status =
        network::URLLoaderCompletionStatus(result.net_error_code());
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kNavigationThrottleCancel;
    OnRequestFailedInternal(completion_status, true /* skip_throttles */,
                            result.error_page_content(),
                            false /* collapse_frame */);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  if (result.action() == NavigationThrottle::BLOCK_RESPONSE) {
    CHECK_EQ(net::ERR_BLOCKED_BY_RESPONSE, result.net_error_code());
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(result.net_error_code()),
        true /* skip_throttles */, result.error_page_content(),
        false /* collapse_frame */);
    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  CHECK_EQ(result.action(), NavigationThrottle::PROCEED);

  // When this request is for prerender activation, `commit_deferrer_` has
  // already been processed. If it's an initial WebUI navigation, commit
  // deferring conditions are skipped.
  if (IsPrerenderedPageActivation() || IsInitialWebUINavigation()) {
    CHECK(!commit_deferrer_);
    CommitNavigation();
    // DO NOT ADD CODE after this. The previous call to CommitNavigation
    // destroyed the NavigationRequest.
    return;
  }

  RunCommitDeferringConditions(base::BindOnce(
      &NavigationRequest::CommitNavigation, weak_factory_.GetWeakPtr()));
  // DO NOT ADD CODE after this. The previous call to
  // RunCommitDeferringConditions may have caused the destruction of the
  // NavigationRequest.
}

void NavigationRequest::OnWillCommitWithoutUrlLoaderChecksComplete(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK(result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
        result.action() == NavigationThrottle::PROCEED);
  if (result.action() == NavigationThrottle::CANCEL_AND_IGNORE) {
    OnRequestFailedInternal(
        network::URLLoaderCompletionStatus(result.net_error_code()),
        /*skip_throttles=*/true, result.error_page_content(),
        /*collapse_frame=*/false);

    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
    // has destroyed the NavigationRequest.
    return;
  }

  // Ensure that bfcache and other non-UrlLoader history navigations can
  // dispatch the Navigation API's navigate event as the navigation is starting.
  // Cases with a UrlLoader are handled in OnStartChecksComplete.
  MaybeDispatchNavigateEventForCrossDocumentTraversal();

  InheritServiceWorkerControllerFromParentIfNeeded();

  CommitNavigation();
}

void NavigationRequest::InheritServiceWorkerControllerFromParentIfNeeded() {
  CHECK(!loader_);
  CHECK(!service_worker_handle_);
  RenderFrameHostImpl* parent = frame_tree_node()->parent();
  if (!parent || !GetURL().IsAboutSrcdoc()) {
    return;
  }
  base::WeakPtr<ServiceWorkerClient> parent_service_worker_client =
      parent->GetLastCommittedServiceWorkerClient();
  if (!parent_service_worker_client) {
    return;
  }

  if ((frame_tree_node_->pending_frame_policy().sandbox_flags &
       network::mojom::WebSandboxFlags::kOrigin) ==
      network::mojom::WebSandboxFlags::kOrigin) {
    return;
  }
  GetContentClient()->browser()->LogWebFeatureForCurrentPage(
      frame_tree_node_->current_frame_host(),
      blink::mojom::WebFeature::kAboutSrcdocToBeControlledByServiceWorker);
  if (!base::FeatureList::IsEnabled(features::kServiceWorkerSrcdocSupport) ||
      !GetContentClient()->browser()->AllowServiceWorkerToControlSrcdocIframe(
          frame_tree_node_->navigator().controller().GetBrowserContext())) {
    return;
  }

  StoragePartition* partition = GetStoragePartitionWithCurrentSiteInfo();
  auto* service_worker_context = static_cast<ServiceWorkerContextWrapper*>(
      partition->GetServiceWorkerContext());
  // As ServiceWorkerMainResourceHandle is not used for intercepting the srcdoc
  // iframe main resource, the fetch event client id is not used. Use empty
  // string as fetch_event_client_id when creating it.
  service_worker_handle_ = std::make_unique<ServiceWorkerMainResourceHandle>(
      service_worker_context, base::DoNothing(),
      /*fetch_event_client_id=*/std::string(), parent_service_worker_client);
  service_worker_handle_->set_service_worker_client(
      service_worker_context->context()
          ->service_worker_client_owner()
          .CreateServiceWorkerClientForWindow(
              frame_tree_node_->AreAncestorsSecure(),
              frame_tree_node_->frame_tree_node_id()),
      GetIsolationInfo());
  service_worker_handle_->service_worker_client()->InheritControllerFrom(
      *parent_service_worker_client, net::SimplifyUrlForRequest(GetURL()));
}

void NavigationRequest::RunCommitDeferringConditions(
    base::OnceClosure on_commit_deferring_conditions_complete_callback) {
  CHECK(!commit_deferring_conditions_complete_closure_);
  commit_deferring_conditions_complete_closure_ =
      std::move(on_commit_deferring_conditions_complete_callback);
  commit_deferrer_->RegisterDeferringConditions(*this);
  commit_deferrer_->ProcessChecks();
  // DO NOT ADD CODE after this. The previous call to ProcessChecks may have
  // caused the destruction of the NavigationRequest.
}

void NavigationRequest::OnCommitDeferringConditionChecksComplete(
    CommitDeferringCondition::NavigationType navigation_type,
    std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id) {
  // CommitDeferringConditions could have been triggered for prerender
  // activation, a normal navigation commit or an error page commit.
  // Running this closure will continue the commit on the appropriate path.
  CHECK(commit_deferring_conditions_complete_closure_);
  std::move(commit_deferring_conditions_complete_closure_).Run();
  // DO NOT ADD CODE after this. The previous call could cause the
  // destruction of the NavigationRequest.
}

void NavigationRequest::PrepareToCommitErrorPage(
    const std::optional<std::string>& error_page_content) {
  RunCommitDeferringConditions(
      base::BindOnce(&NavigationRequest::CommitErrorPage,
                     weak_factory_.GetWeakPtr(), error_page_content));
  //  DO NOT ADD CODE after this. The previous call may have caused the
  //  destruction of the NavigationRequest.
}

void NavigationRequest::CommitErrorPage(
    const std::optional<std::string>& error_page_content) {
  CHECK(!IsSameDocument());

  DetermineAgentClusterKeyForCommit();

  UpdateHistoryParamsInCommitNavigationParams();

  common_params_->should_replace_current_entry =
      ShouldReplaceCurrentEntryForFailedNavigation();

  // Don't pass the base url in a failed navigation.
  common_params_->initiator_base_url = std::nullopt;

  ReuseRequestNavigationClientForCommitIfNeeded();

  base::WeakPtr<NavigationRequest> weak_self(weak_factory_.GetWeakPtr());
  ReadyToCommitNavigation(true /* is_error */);
  // The caller above might result in the deletion of `this`. Return immediately
  // if so.
  if (!weak_self) {
    return;
  }

  // Ensure the renderer does not reuse the document sequence number for
  // cross-origin navigations (which can lead to later bugs with same-document
  // navigations appearing to be cross-origin). All error pages have unique
  // opaque origins and are considered cross-origin.
  //
  // The one exception is for transitioning to or from a compatible error page,
  // which preserves state in case a temporary failure later succeeds. Here, we
  // must check for any cases where the committing error page has a valid
  // precursor that agrees with the current document, whether the current
  // document is already an error page or not. (The renderer process will narrow
  // DSN reuse further, to cases the URL is a closer match, ignoring fragments.)
  // See also CommitNavigation for the other direction.
  // TODO(crbug.com/396645697): Use a different technique for preserving history
  // item state on error pages, separate from the error page's own state.
  RenderFrameHostImpl* previous_rfh = frame_tree_node()->current_frame_host();
  const url::Origin& previous_origin = previous_rfh->GetLastCommittedOrigin();
  bool is_error_page_with_same_precursor =
      previous_origin.GetTupleOrPrecursorTupleIfOpaque().IsValid() &&
      commit_params_->origin_to_commit.GetTupleOrPrecursorTupleIfOpaque() ==
          previous_origin.GetTupleOrPrecursorTupleIfOpaque();
  if (!is_error_page_with_same_precursor ||
      base::FeatureList::IsEnabled(kReplaceEntryWhenRecoveringFromError)) {
    commit_params_->force_new_document_sequence_number = true;
  } else {
    // We only preserve the document sequence number for temporary errors that
    // could later be reloaded and succeed, which don't stay in the current
    // process. Fatal errors routed to kCurrentProcess have a pure opaque origin
    // and will not share the precursor, so they will always force a new
    // document sequence number.
    CHECK_NE(ComputeErrorPageProcess(), ErrorPageProcess::kCurrentProcess);
  }

  PopulateDocumentTokenForCrossDocumentNavigation();
  // Use a separate cache shard, and no cookies, for error pages.
  isolation_info_for_subresources_ =
      net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt);

  // Before sending the commit parameters to the renderer process, sanitize
  // the redirect URLs to avoid leaking potentially sensitive data into
  // processes which are cross-site. There is no dependency on the
  // cross-site-ness, therefore just sanitize unilaterally.
  SanitizeRedirectsForCommit(common_params_, commit_params_);

  GetRenderFrameHost()->FailedNavigation(
      this, *common_params_, *commit_params_, has_stale_copy_in_cache_,
      net_error_, extended_error_code_, error_page_content, *document_token_);
  UpdateNavigationHandleTimingsOnCommitSent();

  SendDeferredConsoleMessages();
}

void NavigationRequest::AddOldPageInfoToCommitParamsIfNeeded() {
  // Add the routing ID and the updated lifecycle state of the old page if we
  // need to run pagehide and visibilitychange handlers of the old page
  // when we commit the new page.
  auto* old_frame_host =
      frame_tree_node_->render_manager()->current_frame_host();
  if (!GetRenderFrameHost()
           ->ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
               old_frame_host, GetUrlInfo())) {
    return;
  }
  CHECK(!IsSameDocument());
  // The pagehide event's "persisted" property depends on whether the old page
  // will be put into the back-forward cache or not. As we won't freeze the
  // page until after the commit finished, there is no way to know for sure
  // whether the page will actually be persisted or not after commit, but we
  // will send our best guess by checking if the page can be persisted at this
  // point.
  bool can_store_old_page_in_bfcache =
      frame_tree_node_->frame_tree().is_primary() &&
      frame_tree_node_->frame_tree()
          .controller()
          .GetBackForwardCache()
          .GetFutureBackForwardCacheEligibilityPotential(old_frame_host)
          .CanStore();
  commit_params_->old_page_info = blink::mojom::OldPageInfo::New();
  commit_params_->old_page_info->frame_token_for_old_main_frame =
      old_frame_host->GetFrameToken();
  auto* page_lifecycle_state_manager =
      old_frame_host->render_view_host()->GetPageLifecycleStateManager();
  commit_params_->old_page_info->new_lifecycle_state_for_old_page =
      page_lifecycle_state_manager->SetPagehideDispatchDuringNewPageCommit(
          can_store_old_page_in_bfcache /* persisted */);
}

bool NavigationRequest::ShouldDispatchPageSwapEvent() const {
  if (early_render_frame_host_swap_type_ !=
      EarlyRenderFrameHostSwapType::kNone) {
    return false;
  }

  if (IsSameDocument()) {
    return false;
  }

  return !did_fire_page_swap_;
}

void NavigationRequest::CommitNavigation() {
  TRACE_EVENT("navigation", "NavigationRequest::CommitNavigation",
              perfetto::Flow::FromPointer(this));

  if (fast_fetch_manager_) {
    fast_fetch_manager_->OnCommitNavigation(*this);
  }

  // A navigation request should only commit once the response has been
  // processed.
  CHECK_GE(state_, WILL_PROCESS_RESPONSE);
  // If a WebUI was created for this navigation, it must have been moved to the
  // RenderFrameHost we're about to commit in already.
  CHECK(!HasWebUI());

  CheckSoftNavigationHeuristicsInvariants();

  CoopCoepSanityCheck();

  DetermineAgentClusterKeyForCommit();

  UpdateHistoryParamsInCommitNavigationParams();
  CHECK(NeedsUrlLoader() == !!response_head_ ||
        (was_redirected_ && common_params_->url.IsAboutBlank()));
  CHECK(!common_params_->url.SchemeIs(url::kJavaScriptScheme));
  CHECK(!blink::IsRendererDebugURL(common_params_->url));

  AddOldPageInfoToCommitParamsIfNeeded();
  if (ShouldDispatchPageSwapEvent()) {
    frame_tree_node_->current_frame_host()
        ->GetAssociatedLocalFrame()
        ->DispatchPageSwap(WillDispatchPageSwap());
  }

  url::Origin origin_to_commit = GetOriginToCommit().value();
  if (base::FeatureList::IsEnabled(features::kValidateCommitOriginAtCommit)) {
    ValidateCommitOrigin(origin_to_commit);
  }
  isolation_info_for_subresources_ =
      GetRenderFrameHost()->ComputeIsolationInfoForSubresourcesForPendingCommit(
          origin_to_commit, is_credentialless(), ComputeFencedFrameNonce());
  CHECK(!isolation_info_for_subresources_.IsEmpty());

  // If this is a srcdoc document, the content comes from the parent frame, so
  // the origin must be the parent and not the initiator. In this case, do not
  // inherit the base URI from the initiator if the origins do not agree
  // (accounting for the case that the chosen origin might be opaque with a
  // precursor of the parent's origin, in a sandboxed case). There should also
  // not be an initiator base URL if there is no initiator origin, such as in a
  // browser-initiated navigation.
  if (GetURL().IsAboutSrcdoc() &&
      (!common_params().initiator_origin ||
       origin_to_commit.GetTupleOrPrecursorTupleIfOpaque() !=
           common_params()
               .initiator_origin->GetTupleOrPrecursorTupleIfOpaque())) {
    // TODO(crbug.com/40165505): Make this unreachable by blocking
    // cross-origin about:srcdoc navigations. Then enforce that the chosen
    // origin for srcdoc cases agrees with the parent frame's origin.
    common_params_->initiator_base_url = std::nullopt;
  }

  // TODO(crbug.com/40092527): The storage key's origin is ignored at the
  // moment. We will be able to use it once the browser can compute the origin
  // to commit.
  std::optional<base::UnguessableToken> nonce =
      GetRenderFrameHost()->ComputeNonce(is_credentialless(),
                                         ComputeFencedFrameNonce());

  commit_params_->storage_key = GetRenderFrameHost()->CalculateStorageKey(
      origin_to_commit, base::OptionalToPtr(nonce));

  RenderFrameHostImpl* old_frame_host =
      frame_tree_node_->render_manager()->current_frame_host();
  if (!NavigationTypeUtils::IsSameDocument(common_params_->navigation_type)) {
    // We want to record this for the frame that we are navigating away from.
    old_frame_host->RecordNavigationSuddenTerminationHandlers();
  }
  if (IsServedFromBackForwardCache() || IsPrerenderedPageActivation()) {
    CommitPageActivation();
    return;
  }

  // For consistency, prerendering activation *should* go through this as well.
  // However, the prerender implementation based on swapping WebContents
  // introduces a number of edge cases that navigation code wouldn't normally
  // have to handle, so it's easier to simply skip this in the hopes that the
  // non-mparch implementation will be removed soon.
  base::WeakPtr<NavigationRequest> weak_self(weak_factory_.GetWeakPtr());
  ReadyToCommitNavigation(false /* is_error */);
  // The call above might block on showing a user dialog. The interaction of
  // the user with this dialog might result in the WebContents owning this
  // NavigationRequest to be destroyed. Return if this is the case.
  if (!weak_self)
    return;

  CHECK(GetRenderFrameHost() == old_frame_host ||
        GetRenderFrameHost() ==
            frame_tree_node_->render_manager()->speculative_frame_host());

  ReuseRequestNavigationClientForCommitIfNeeded();

  CreateCoepReporter(GetRenderFrameHost()->GetProcess()->GetStoragePartition());
  coop_status_.UpdateReporterStoragePartition(
      GetRenderFrameHost()->GetProcess()->GetStoragePartition());
  CreateDipReporter(GetRenderFrameHost()->GetProcess()->GetStoragePartition());

  BrowserContext* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();
  ClientHintsControllerDelegate* client_hints_delegate =
      browser_context->GetClientHintsControllerDelegate();
  if (client_hints_delegate) {
    std::optional<std::vector<network::mojom::WebClientHintsType>>
        opt_in_hints_from_response;
    if (response()) {
      opt_in_hints_from_response = ParseAndPersistAcceptCHForNavigation(
          url::Origin::Create(common_params_->url), response()->ssl_info,
          response()->parsed_headers, response()->headers.get(),
          browser_context, client_hints_delegate, frame_tree_node_);
    }
    commit_params_->enabled_client_hints =
        LookupAcceptCHForCommit(origin_to_commit, client_hints_delegate,
                                frame_tree_node_, common_params_->url);
  }
  // Navigation requests should use the new origin as the partition origin
  // except if embedded in an outer frame.
  url::Origin partition_origin = origin_to_commit;
  bool is_top_level = frame_tree_node()->GetParentOrOuterDocument() == nullptr;
  if (!is_top_level) {
    partition_origin = frame_tree_node()
                           ->GetParentOrOuterDocument()
                           ->GetOutermostMainFrame()
                           ->GetLastCommittedOrigin();
  }

  PersistOriginTrialsFromHeaders(origin_to_commit, partition_origin, response(),
                                 browser_context, GetNextPageUkmSourceId());

  // Clean the reduced accept-language to commit if the final response have a
  // valid deprecation origin trial token.
  if (auto reduce_accept_lang_utils =
          ReduceAcceptLanguageUtils::Create(browser_context);
      reduce_accept_lang_utils && !devtools_accept_language_override_ &&
      ReduceAcceptLanguageUtils::CheckDisableReduceAcceptLanguageOriginTrial(
          common_params_->url, frame_tree_node_,
          browser_context->GetOriginTrialsControllerDelegate()) &&
      !commit_params_->reduced_accept_language.empty()) {
    reduce_accept_lang_utils.value().RemoveReducedAcceptLanguage(
        origin_to_commit, frame_tree_node_);
    commit_params_->reduced_accept_language = "";
  }

  // Sticky user activation should only be preserved for same-site subframe
  // navigations, and same-origin top-frame navigations behind the feature flag
  // StickyUserActivationAcrossSameOriginNavigation. These checks limit newly
  // navigated documents from reusing the sticky user activation state from the
  // previously navigated document in the frame.
  //
  // - We persist user activation across same-site navigations for compatibility
  //   reasons, and this does not need to match the same-site checks used in the
  //   process model. See https://crbug.com/40527366.
  //
  //   TODO(crbug.com/40228985): Remove this once we find a way to reset
  //   activation unconditionally without breaking sites in practice.
  //
  // - The feature flag StickyUserActivationAcrossSameOriginNavigation relaxes
  //   the preservation of sticky activation to include same-origin navigations
  //   to ease multi-page app development which currently face problem with, for
  //   example, virtual keyboards.  See https://crbug.com/433729626.
  commit_params_->should_have_sticky_user_activation =
      old_frame_host->HasStickyUserActivation() &&
      ((!frame_tree_node_->IsMainFrame() &&
        net::SchemefulSite::IsSameSite(old_frame_host->GetLastCommittedOrigin(),
                                       origin_to_commit)) ||
       (base::FeatureList::IsEnabled(
            blink::features::kStickyUserActivationAcrossSameOriginNavigation) &&
        !commit_params_->is_browser_initiated &&
        frame_tree_node_->IsMainFrame() &&
        old_frame_host->GetLastCommittedOrigin() == origin_to_commit));

  // Generate a UKM source and track it on NavigationRequest. This will be
  // passed down to the blink::Document to be created, if any, and used for UKM
  // source creation when navigation has successfully committed.
  commit_params_->document_ukm_source_id = ukm::UkmRecorder::GetNewSourceID();

  blink::mojom::ServiceWorkerContainerInfoForClientPtr
      service_worker_container_info;
  blink::mojom::ControllerServiceWorkerInfoPtr controller;

  // The decision to create `service_worker_handle_` was made before the
  // request was sent, based only on the sandbox flags inherited from the frame
  // owner element. If the response delivered a `Content-Security-Policy:
  // sandbox` header without `allow-same-origin`, the document will commit with
  // an opaque origin and is not eligible to use service workers, so drop the
  // handle now instead of creating a container host for the committed document.
  if (service_worker_handle_ &&
      (policy_container_builder_->FinalPolicies().sandbox_flags &
       network::mojom::WebSandboxFlags::kOrigin) ==
          network::mojom::WebSandboxFlags::kOrigin) {
    service_worker_handle_.reset();
  }

  // Notify the service worker navigation handle that navigation commit is
  // about to go.
  if (service_worker_handle_ &&
      service_worker_handle_->service_worker_client()) {
    CHECK(coep_reporter());
    CHECK(dip_reporter());
    mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
        coep_reporter_remote;
    coep_reporter()->Clone(
        coep_reporter_remote.InitWithNewPipeAndPassReceiver());

    mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
        dip_reporter_remote;
    dip_reporter()->Clone(dip_reporter_remote.InitWithNewPipeAndPassReceiver());

    std::tie(service_worker_container_info, controller) =
        service_worker_handle_->scoped_service_worker_client()
            ->CommitResponseAndRelease(
                GetRenderFrameHost()->GetGlobalId(),
                policy_container_builder_->FinalPolicies(),
                std::move(coep_reporter_remote), std::move(dip_reporter_remote),
                commit_params_->document_ukm_source_id);
  }

  // Determine if top-level navigation is allowed without sticky user
  // activation. This is used to fix the exploit in https://crbug.com/1251790.
  // If a child document is cross-origin with its parent, it loses its ability
  // to navigate top without user gesture. One notable exception is made if its
  // parent embeds it using sandbox="allow-top-navigation". Please note this is
  // quite unusual, because it means using sandbox brings new capabilities, as
  // opposed to new restrictions.
  using WebSandboxFlags = network::mojom::WebSandboxFlags;
  const bool embedder_allows_top_navigation_explicitly =
      ((commit_params_->frame_policy.sandbox_flags != WebSandboxFlags::kNone) &&
       (commit_params_->frame_policy.sandbox_flags &
        WebSandboxFlags::kTopNavigation) == WebSandboxFlags::kNone);
  const bool is_same_origin_to_top =
      origin_to_commit ==
      GetRenderFrameHost()->GetMainFrame()->GetLastCommittedOrigin();
  if (is_same_origin_to_top) {
    policy_container_builder_->SetAllowTopNavigationWithoutUserGesture(true);
  } else if (!IsInMainFrame() && !embedder_allows_top_navigation_explicitly) {
    policy_container_builder_->SetAllowTopNavigationWithoutUserGesture(false);
  }

  if (!IsSameDocument()) {
    commit_params_->navigation_api_history_entry_arrays =
        GetNavigationController()->GetNavigationApiHistoryEntryVectors(
            frame_tree_node_, this);
    PopulateDocumentTokenForCrossDocumentNavigation();

    // Ensure the renderer does not reuse the document sequence number for
    // cross-origin navigations (which can lead to later bugs with same-document
    // navigations appearing to be cross-origin).
    //
    // The one exception is for transitioning to or from a compatible error
    // page, which preserves state in case a temporary failure later succeeds.
    // Here, we must check for the case that the user navigates from an error
    // page to a non-error page that matches the (valid) precursor origin. See
    // also CommitErrorPage for the other direction.
    // TODO(crbug.com/396645697): Use a different technique for preserving
    // history item state on error pages, separate from the error page's own
    // state.
    RenderFrameHostImpl* previous_rfh = frame_tree_node()->current_frame_host();
    const url::Origin& previous_origin = previous_rfh->GetLastCommittedOrigin();
    bool is_cross_origin_navigation =
        !commit_params_->origin_to_commit.IsSameOriginWith(previous_origin);
    bool compatible_with_error_page =
        previous_rfh->IsErrorDocument() &&
        previous_origin.GetTupleOrPrecursorTupleIfOpaque().IsValid() &&
        commit_params_->origin_to_commit.GetTupleOrPrecursorTupleIfOpaque() ==
            previous_origin.GetTupleOrPrecursorTupleIfOpaque();
    if (is_cross_origin_navigation &&
        (!compatible_with_error_page ||
         base::FeatureList::IsEnabled(kReplaceEntryWhenRecoveringFromError))) {
      commit_params_->force_new_document_sequence_number = true;
    }
  }

  if (early_hints_manager_) {
    commit_params_->early_hints_preloaded_resources =
        early_hints_manager_->TakePreloadedResources();
    commit_params_->early_hints_preconnects =
        early_hints_manager_->TakePreconnectedResources();
  }

  if (response_head_ && response_head_->parsed_headers) {
    // Collect `Link: rel=preconnect` headers from the final navigation response
    // (not 103 Early Hints) for the SpeculationMeasurement API. The renderer
    // issues these preconnects; here we only forward them so they can be
    // recorded at commit.
    for (const auto& link : response_head_->parsed_headers->link_headers) {
      if (link->rel == network::mojom::LinkRelAttribute::kPreconnect) {
        commit_params_->navigation_preconnects.push_back(link.Clone());
      }
    }
  }

  if (response_head_) {
    commit_params_->navigation_delivery_type =
        response_head_->navigation_delivery_type;
  }

  // Add our map of modified blink runtime-enabled features to
  // the commit params so they can be communicated to the renderer process.
  commit_params_->modified_runtime_features =
      runtime_feature_state_context_.GetFeatureOverrides();

  // Documents loaded from fenced frame configs can opt into allowing
  // cross-origin subframes to use their reporting metadata to send
  // `reportEvent()` beacons. The cross-origin subframes still require a
  // separate per-report opt-in.
  if (fenced_frame_properties_.has_value() && response_head_ &&
      response_head_->parsed_headers->allow_cross_origin_event_reporting) {
    fenced_frame_properties_->SetAllowCrossOriginEventReporting();
  }

  if (base::FeatureList::IsEnabled(
          blink::features::kFencedFramesSrcPermissionsPolicy)) {
    std::optional<url::Origin> mapped_origin;
    if (fenced_frame_properties_.has_value()) {
      mapped_origin = url::Origin::Create(
          fenced_frame_properties_->mapped_url()->GetValueIgnoringVisibility());
    } else if (frame_tree_node_->HasFencedFrameProperties() &&
               frame_tree_node_->GetFencedFrameProperties()->mapped_url()) {
      mapped_origin =
          url::Origin::Create(frame_tree_node_->GetFencedFrameProperties()
                                  ->mapped_url()
                                  ->GetValueIgnoringVisibility());
    }

    // Container policy allowlists are first calculated by the embedder where
    // origin of the fenced frame is opaque. Now that the mapped URL is known,
    // update the container policy allowlists so that any allowlist with
    // "matches_opaque_src=true" points to the final mapped origin. This will be
    // the container policy that is sent to the inner root to construct the
    // final permissions policy.
    if (mapped_origin.has_value()) {
      for (auto& declaration : commit_params_->frame_policy.container_policy) {
        if (declaration.matches_opaque_src) {
          CHECK(!declaration.self_if_matches.has_value());
          declaration.matches_opaque_src = false;
          declaration.self_if_matches = mapped_origin.value();
        }
      }
    }
  }

  // Let the embedder fully replace the inherited container policy at
  // commit (e.g., to grant all permissions-policy features to MIME
  // handler extensions that previously ran as top-level frames).
  // Runs after the fenced-frame fixup above so the override wins by
  // construction; no current frame is both a fenced frame and a
  // consumer of this hook.
  if (auto container_policy_override =
          GetContentClient()->browser()->GetContainerPolicyOverrideForCommit(
              *this)) {
    commit_params_->frame_policy.container_policy =
        std::move(*container_policy_override);
  }

  // Create a view of the fenced frame properties from the perspective of the
  // fenced frame content, which will be sent to its renderer.
  // On each navigation commit within the fenced frame tree:
  // * If the properties have no mapped url, the browser will send the renderer
  //   the `RedactedFencedFrameProperties` unconditionally.
  // * If the properties do have a mapped url, the browser will send the
  //   renderer the `RedactedFencedFrameProperties`, redacting extra information
  //   based on whether the origin is same-origin to the urn's mapped_url (after
  //   redirects).
  // This is because we want to make fenced frame APIs available only
  // in same-origin contexts, when "same-origin" has a coherent definition.
  const auto& computed_fenced_frame_properties = ComputeFencedFrameProperties();
  if (computed_fenced_frame_properties.has_value()) {
    content::FencedFrameEntity entity =
        content::FencedFrameEntity::kSameOriginContent;
    if (computed_fenced_frame_properties->mapped_url().has_value() &&
        !origin_to_commit.IsSameOriginWith(
            computed_fenced_frame_properties->mapped_url()
                ->GetValueIgnoringVisibility())) {
      entity = content::FencedFrameEntity::kCrossOriginContent;
    }
    commit_params_->fenced_frame_properties =
        computed_fenced_frame_properties->RedactFor(entity);
  }

  commit_params_->load_with_storage_access = ShouldLoadWithStorageAccess(
      begin_params(), common_params(), frame_tree_node()->current_frame_host(),
      did_encounter_cross_origin_redirect(), GetURL(), response());

  auto common_params = common_params_->Clone();
  auto commit_params = commit_params_.Clone();
  auto response_head = response_head_.Clone();
  if (!subresource_loader_params_.prefetched_signed_exchanges.empty()) {
    commit_params->prefetched_signed_exchanges =
        std::move(subresource_loader_params_.prefetched_signed_exchanges);
  }

  // Before sending the commit parameters to the renderer process, sanitize
  // the redirect URLs to avoid leaking potentially sensitive data into
  // processes which are cross-site. There is no dependency on the
  // cross-site-ness, therefore just sanitize unilaterally.
  SanitizeRedirectsForCommit(common_params, commit_params);

  GetRenderFrameHost()->CommitNavigation(
      this, std::move(common_params), std::move(commit_params),
      std::move(response_head), std::move(response_body_),
      std::move(url_loader_client_endpoints_), std::move(controller),
      std::move(subresource_overrides_),
      std::move(service_worker_container_info), document_token_,
      devtools_navigation_token_);
  if (service_worker_handle_ &&
      service_worker_handle_->service_worker_client()) {
    service_worker_handle_->service_worker_client()->SetContainerReady();
  }

  if (base::FeatureList::IsEnabled(
          net::features::kUpdateIsMainFrameOriginRecentlyAccessed) &&
      IsInMainFrame()) {
    URLLoaderFactoryParamsHelper::OnMainFrameNavigation(origin_to_commit);
  }
  UpdateNavigationHandleTimingsOnCommitSent();

  // Give SpareRenderProcessHostManager a heads-up about the most recently used
  // BrowserContext.  This is mostly needed to make sure the spare is warmed-up
  // if it wasn't done in RenderProcessHostImpl::GetProcessHostForSiteInstance.
  RenderProcessHostImpl::NotifySpareManagerAboutRecentlyUsedSiteInstance(
      GetRenderFrameHost()->GetSiteInstance());

  SendDeferredConsoleMessages();
}

void NavigationRequest::UpdateViewTransitionStateForDestinationOrigin(
    const url::Origin& origin) {
  if (!commit_params().view_transition_state && !view_transition_resources_) {
    return;
  }
  // Disallow cross origin view transitions.
  if (!view_transition_source_origin_.IsSameOriginWith(origin)) {
    commit_params_->view_transition_state.reset();
    view_transition_resources_.reset();
  }
}

void NavigationRequest::CommitPageActivation() {
  TRACE_EVENT("navigation", "NavigationRequest::CommitPageActivation",
              perfetto::Flow::FromPointer(this));
  // An activation is either for the back-forward cache or prerendering. They
  // are mutually exclusive.
  CHECK_NE(IsServedFromBackForwardCache(), IsPrerenderedPageActivation());

  NavigationControllerImpl* controller = GetNavigationController();

  if (IsServedFromBackForwardCache()) {
    std::unique_ptr<BackForwardCacheImpl::Entry> activated_entry;
    // Navigations served from the back-forward cache must be a history
    // navigation, and thus should have a valid |pending_history_list_index|
    // value. We will pass that value and the |current_history_list_length|
    // value to update the history index and length information saved in the
    // renderer, which might be stale.
    CHECK_GE(commit_params_->pending_history_list_index, 0);

    auto page_restore_params = blink::mojom::PageRestoreParams::New();
    page_restore_params->navigation_start = NavigationStart();
    page_restore_params->pending_history_list_index =
        commit_params_->pending_history_list_index;
    page_restore_params->current_history_list_length =
        commit_params_->current_history_list_length;
    activated_entry = controller->GetBackForwardCache().RestoreEntry(
        nav_entry_id_, std::move(page_restore_params));
    CHECK(activated_entry);

    // Restore navigation API entries, since they will probably have changed
    // since the page entered bfcache. We must update all frames, not just the
    // top frame, because it is possible (though unlikely) that an iframe's
    // entries have changed, too.
    activated_entry->render_frame_host()->ForEachRenderFrameHostImplWithAction(
        [this, &activated_entry](RenderFrameHostImpl* rfh) {
          // |this| is given as a parameter to
          // GetNavigationApiHistoryEntryVectors() only for the frame being
          // committed (i.e., the top frame).
          auto entry_arrays =
              rfh->frame_tree()
                  ->controller()
                  .GetNavigationApiHistoryEntryVectors(
                      rfh->frame_tree_node(),
                      activated_entry->render_frame_host() == rfh ? this
                                                                  : nullptr);
          rfh->GetAssociatedLocalFrame()
              ->SetNavigationApiHistoryEntriesForRestore(
                  std::move(entry_arrays),
                  blink::mojom::NavigationApiEntryRestoreReason::kBFCache);
          return RenderFrameHost::FrameIterationAction::kContinue;
        });

    // When activating from BackForwardCache, properly set the
    // BrowsingContextGroupSwap information. This is required because we
    // otherwise do it in the RenderFrameHost selection, in
    // GetFrameHostForNavigation, which does not happen for BFCache restores.
    // TODO(crbug.com/40922919): This code assumes that pages can only be
    // stored in the BFCache if they live in a different BrowsingInstance in
    // another CoopRelatedGroup, so we enforce that invariant via a CHECK. If
    // this is not the case anymore, `browsing_context_group_swap_` should be
    // set to BrowsingContextGroupSwap::CreateRelatedCoopSwap() if the two
    // SiteInstances live in the same CoopRelatedGroup.
    SiteInstanceImpl* current_site_instance =
        frame_tree_node_->current_frame_host()->GetSiteInstance();
    SiteInstanceImpl* target_site_instance =
        activated_entry->render_frame_host()->GetSiteInstance();
    CHECK(!target_site_instance->IsRelatedSiteInstance(current_site_instance));
    browsing_context_group_swap_ =
        BrowsingContextGroupSwap::CreateSecuritySwap();

    base::WeakPtr<NavigationRequest> weak_self(weak_factory_.GetWeakPtr());
    ReadyToCommitNavigation(false /* is_error */);
    // The call above might block on showing a user dialog. The interaction of
    // the user with this dialog might result in the WebContents owning this
    // NavigationRequest to be destroyed. Return if this is the case.
    if (!weak_self)
      return;

    // Treat this as the commit start time for the activation (i.e., after the
    // ReadyToCommitNavigation call).
    page_activation_commit_time_ = base::TimeTicks::Now();

    // Make sure to update the view transition state before passing the state to
    // `activated_entry`. This may need to clear the state if the origin
    // changed.
    UpdateViewTransitionStateForDestinationOrigin(GetOriginToCommit().value());

    // Use std::exchange instead of move, so that we clear out the optional on
    // the commit_params.
    activated_entry->SetViewTransitionState(
        std::exchange(commit_params_->view_transition_state, {}));

    // Move the BackForwardCacheImpl::Entry into RenderFrameHostManager, in
    // preparation for committing. This entry may be either restored from the
    // backforward cache.
    CHECK(activated_entry);
    frame_tree_node_->render_manager()->RestorePage(
        activated_entry->TakeStoredPage());
  } else {
    std::unique_ptr<StoredPage> stored_page =
        GetPrerenderHostRegistry().ActivateReservedHost(
            activating_prerender_host_id_.value(), *this);
    CHECK(stored_page);

    RenderFrameHostImpl* rfh = stored_page->render_frame_host();

    // The prerender page might have navigated. Update the URL and the redirect
    // chain, as the prerendered page might have been redirected or performed
    // a same-document navigation.
    // TODO(crbug.com/40170496): Ensure that the tests that navigate
    // MPArch activation flow do not crash. This is a hack to unblock the basic
    // MPArch activation flow for now. There are probably other parameters which
    // are out of sync, and we need to carefully think through how we can
    // activate a RenderFrameHost whose URL doesn't match the one that was
    // initially passed to NavigationRequest (or disallow subsequent navigations
    // in the main frame of the prerender frame tree).
    common_params_->url = rfh->GetLastCommittedURL();
    // TODO(crbug.com/40170496): We may have to add the entire redirect
    // chain.
    redirect_chain_.clear();
    redirect_chain_.push_back(rfh->GetLastCommittedURL());

    base::WeakPtr<NavigationRequest> weak_self(weak_factory_.GetWeakPtr());
    ReadyToCommitNavigation(false /* is_error */);
    // The call above might block on showing a user dialog. The interaction of
    // the user with this dialog might result in the WebContents owning this
    // NavigationRequest to be destroyed. Return if this is the case.
    if (!weak_self)
      return;

    // Treat this as the commit start time for the activation (i.e., after the
    // ReadyToCommitNavigation call).
    page_activation_commit_time_ = base::TimeTicks::Now();

    // Make sure to update the view transition state before passing the state to
    // `stored_page`. This may need to clear the state if the origin
    // changed.
    UpdateViewTransitionStateForDestinationOrigin(GetOriginToCommit().value());

    // Use std::exchange instead of move, so that we clear out the optional on
    // the commit_params.
    stored_page->SetViewTransitionState(
        std::exchange(commit_params_->view_transition_state, {}));

    // Update navigation API entries. A prerendered page has only a single
    // history entry, but now it has access to a full back/forward list.
    rfh->ForEachRenderFrameHostImplWithAction([this, &stored_page](
                                                  RenderFrameHostImpl* rfh) {
      // Currently, prerender activation only happens for DIFFERENT_DOCUMENT
      // navigations. If that ever changes, `reason` calculation will need to be
      // updated (and new NavigationApiEntryRestoreReason values added).
      CHECK_EQ(common_params_->navigation_type,
               blink::mojom::NavigationType::DIFFERENT_DOCUMENT);
      blink::mojom::NavigationApiEntryRestoreReason reason =
          common_params_->should_replace_current_entry
              ? blink::mojom::NavigationApiEntryRestoreReason::
                    kPrerenderActivationReplace
              : blink::mojom::NavigationApiEntryRestoreReason::
                    kPrerenderActivationPush;
      // |this| is given as a parameter to
      // GetNavigationApiHistoryEntryVectors() only for the frame being
      // committed (i.e., the top frame).
      auto entry_arrays =
          rfh->frame_tree()->controller().GetNavigationApiHistoryEntryVectors(
              rfh->frame_tree_node(),
              stored_page->render_frame_host() == rfh ? this : nullptr);
      rfh->GetAssociatedLocalFrame()->SetNavigationApiHistoryEntriesForRestore(
          std::move(entry_arrays), reason);
      return RenderFrameHost::FrameIterationAction::kContinue;
    });

    // Move the StoredPage into RenderFrameHostManager, in
    // preparation for committing. This entry may be used for prerendering.
    frame_tree_node_->render_manager()->ActivatePrerender(
        std::move(stored_page));
  }

  // Commit the page activation. This includes committing the RenderFrameHost
  // and restoring extra state, such as proxies, etc.
  // Note that this will delete the NavigationRequest.
  GetRenderFrameHost()->DidCommitPageActivation(
      this, IsPrerenderedPageActivation()
                ? MakeDidCommitProvisionalLoadParamsForPrerenderActivation()
                : MakeDidCommitProvisionalLoadParamsForBFCacheRestore());

  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous call.
}

void NavigationRequest::SetExpectedProcess(
    RenderProcessHost* expected_process) {
  if (expected_process &&
      expected_process->GetID() == expected_render_process_host_id_) {
    // This |expected_process| has already been informed of the navigation,
    // no need to update it again.
    return;
  }

  ResetExpectedProcess();

  if (expected_process == nullptr)
    return;

  // Keep track of the speculative RenderProcessHost and tell it to expect a
  // navigation to |site_info_|.
  expected_render_process_host_id_ = expected_process->GetID();
  expected_process->AddObserver(this);
  RenderProcessHostImpl::AddExpectedNavigationToSite(
      frame_tree_node()->navigator().controller().GetBrowserContext(),
      expected_process, site_info_);
}

void NavigationRequest::SetExpectedProcessIfAssociated() {
  if (associated_rfh_type_ != AssociatedRenderFrameHostType::NONE) {
    RenderFrameHostImpl* navigating_frame_host =
        associated_rfh_type_ == AssociatedRenderFrameHostType::SPECULATIVE
            ? frame_tree_node_->render_manager()->speculative_frame_host()
            : frame_tree_node_->current_frame_host();
    SetExpectedProcess(navigating_frame_host->GetProcess());
  }
}

void NavigationRequest::ResetExpectedProcess() {
  if (expected_render_process_host_id_.is_null()) {
    // No expected process is set, nothing to update.
    return;
  }
  RenderProcessHost* process =
      RenderProcessHost::FromID(expected_render_process_host_id_);
  if (process) {
    RenderProcessHostImpl::RemoveExpectedNavigationToSite(
        frame_tree_node()->navigator().controller().GetBrowserContext(),
        process, site_info_);
    process->RemoveObserver(this);
  }
  expected_render_process_host_id_ = ChildProcessId();
}

void NavigationRequest::RenderProcessHostDestroyed(RenderProcessHost* host) {
  CHECK_EQ(host->GetID(), expected_render_process_host_id_);
  ResetExpectedProcess();
}

void NavigationRequest::RenderProcessExited(
    RenderProcessHost* host,
    const ChildProcessTerminationInfo& info) {}

void NavigationRequest::UpdateNavigationHandleTimingsOnResponseReceived(
    bool is_redirect,
    bool is_first_response) {
  base::TimeTicks loader_callback_time = base::TimeTicks::Now();

  const base::TimeDelta domain_lookup_delay =
      response_head_->load_timing.connect_timing.domain_lookup_end -
      response_head_->load_timing.connect_timing.domain_lookup_start;
  const base::TimeDelta connect_delay =
      response_head_->load_timing.connect_timing.connect_end -
      response_head_->load_timing.connect_timing.connect_start;
  const base::TimeDelta ssl_delay =
      response_head_->load_timing.connect_timing.ssl_end -
      response_head_->load_timing.connect_timing.ssl_start;

  if (is_first_response) {
    CHECK(!navigation_handle_timing_.first_fetch_start_time.has_value());
    CHECK(navigation_handle_timing_.first_request_start_time.is_null());
    CHECK(navigation_handle_timing_.first_response_start_time.is_null());
    CHECK(navigation_handle_timing_.first_loader_callback_time.is_null());

    if (!response_head_->request_start.is_null()) {
      navigation_handle_timing_.first_fetch_start_time =
          response_head_->request_start;
    }
    navigation_handle_timing_.first_request_start_time =
        response_head_->load_timing.send_start;
    navigation_handle_timing_.first_response_start_time =
        response_head_->load_timing.receive_headers_start;
    navigation_handle_timing_.first_loader_callback_time = loader_callback_time;

    navigation_handle_timing_.first_request_domain_lookup_delay =
        domain_lookup_delay;
    navigation_handle_timing_.first_request_connect_delay = connect_delay;
    navigation_handle_timing_.first_request_ssl_delay = ssl_delay;

    const net::LoadTimingInfo::ConnectTiming& connect_timing =
        response_head_->load_timing.connect_timing;
    navigation_handle_timing_.first_request_domain_lookup_start_time =
        connect_timing.domain_lookup_start;
    navigation_handle_timing_.first_request_domain_lookup_end_time =
        connect_timing.domain_lookup_end;
    navigation_handle_timing_.first_request_connect_start_time =
        connect_timing.connect_start;
    navigation_handle_timing_.first_request_connect_end_time =
        connect_timing.connect_end;
    navigation_handle_timing_.first_request_ssl_start_time =
        connect_timing.ssl_start;

    first_fetch_start_time_ = response_head_->request_start;
  }

  if (!is_redirect) {
    navigation_handle_timing_.non_redirected_request_start_time =
        response_head_->load_timing.send_start;
    navigation_handle_timing_.non_redirect_response_start_time =
        response_head_->load_timing.receive_headers_start;
    navigation_handle_timing_.non_redirect_response_loader_callback_time =
        loader_callback_time;
  }

  navigation_handle_timing_.final_request_start_time =
      response_head_->load_timing.send_start;
  navigation_handle_timing_.final_response_start_time =
      response_head_->load_timing.receive_headers_start;
  navigation_handle_timing_.final_non_informational_response_start_time =
      response_head_->load_timing.receive_non_informational_headers_start;
  navigation_handle_timing_.final_loader_callback_time = loader_callback_time;

  navigation_handle_timing_.final_request_domain_lookup_delay =
      domain_lookup_delay;
  navigation_handle_timing_.final_request_connect_delay = connect_delay;
  navigation_handle_timing_.final_request_ssl_delay = ssl_delay;

  const net::LoadTimingInfo::ConnectTiming& final_connect_timing =
      response_head_->load_timing.connect_timing;
  navigation_handle_timing_.final_request_domain_lookup_start_time =
      final_connect_timing.domain_lookup_start;
  navigation_handle_timing_.final_request_domain_lookup_end_time =
      final_connect_timing.domain_lookup_end;
  navigation_handle_timing_.final_request_connect_start_time =
      final_connect_timing.connect_start;
  navigation_handle_timing_.final_request_connect_end_time =
      final_connect_timing.connect_end;
  navigation_handle_timing_.final_request_ssl_start_time =
      final_connect_timing.ssl_start;

  if (response_head_->load_timing_internal_info) {
    navigation_handle_timing_.create_stream_delay =
        response_head_->load_timing_internal_info->create_stream_delay;
    navigation_handle_timing_.connected_callback_delay =
        response_head_->load_timing_internal_info->connected_callback_delay;
    navigation_handle_timing_.accept_ch_frame_received =
        response_head_->load_timing_internal_info->accept_ch_frame_received;
    navigation_handle_timing_.initialize_stream_delay =
        response_head_->load_timing_internal_info->initialize_stream_delay;
    navigation_handle_timing_.session_details = {
        .session_source =
            response_head_->load_timing_internal_info->session_source,
        .advertised_alt_svc_state =
            response_head_->load_timing_internal_info->advertised_alt_svc_state,
        .http_network_session_quic_enabled =
            response_head_->load_timing_internal_info
                ->http_network_session_quic_enabled,
        .max_stream_limit_pending_delay =
            response_head_->load_timing_internal_info
                ->max_stream_limit_pending_delay,
        .resolution_details =
            response_head_->load_timing_internal_info->resolution_details,
    };
  }

  final_receive_headers_end_time_ =
      response_head_->load_timing.receive_headers_end;

  // |navigation_commit_sent_time| will be updated by
  // UpdateNavigationHandleTimingsOnCommitSent() later.
  CHECK(navigation_handle_timing_.navigation_commit_sent_time.is_null());

  GetDelegate()->DidUpdateNavigationHandleTiming(this);
}

void NavigationRequest::UpdateNavigationHandleTimingsOnCommitSent() {
  CHECK(navigation_handle_timing_.navigation_commit_sent_time.is_null());
  navigation_handle_timing_.navigation_commit_sent_time =
      base::TimeTicks::Now();

  GetDelegate()->DidUpdateNavigationHandleTiming(this);
}

void NavigationRequest::UpdateNavigationHandleTimingsOnCreated() {
  if (!common_params_->input_start.is_null()) {
    navigation_handle_timing_.user_interaction = common_params_->input_start;
  }

  // Use `common_params_->actual_navigation_start` if provided by the renderer.
  // Otherwise, fallback to navigation_start. When called from constructor,
  // `common_params_->navigation_start` contains the time before any adjustments
  // by beforeunload handlers.
  navigation_handle_timing_.actual_navigation_start =
      !common_params_->actual_navigation_start.is_null()
          ? common_params_->actual_navigation_start
          : common_params_->navigation_start;
}

void NavigationRequest::UpdateNavigationHandleTimingsOnBeginNavigation() {
  base::TimeDelta phase1_duration;
  if (!begin_params_->before_unload_dialog_opened.is_null() &&
      !begin_params_->before_unload_dialog_closed.is_null()) {
    phase1_duration = begin_params_->before_unload_dialog_closed -
                      begin_params_->before_unload_dialog_opened;
  }

  base::TimeDelta phase2_duration;
  if (!beforeunload_phase2_dialog_opened_time_.is_null() &&
      !beforeunload_phase2_dialog_closed_time_.is_null()) {
    phase2_duration = beforeunload_phase2_dialog_closed_time_ -
                      beforeunload_phase2_dialog_opened_time_;
  }

  navigation_handle_timing_.before_unload_dialog_duration =
      std::max(base::TimeDelta(), phase1_duration) +
      std::max(base::TimeDelta(), phase2_duration);
}

void NavigationRequest::UpdateSiteInfo(
    RenderProcessHost* post_redirect_process) {
  ChildProcessId post_redirect_process_id;
  if (post_redirect_process) {
    post_redirect_process_id = post_redirect_process->GetID();
  }

  SiteInfo new_site_info = GetSiteInfoForCommonParamsURL();
  if (new_site_info == site_info_ &&
      post_redirect_process_id == expected_render_process_host_id_) {
    return;
  }

  // Stop expecting a navigation to the current SiteInfo in the current expected
  // process.
  ResetExpectedProcess();

  // Update the SiteInfo and the expected process.
  site_info_ = new_site_info;
  SetExpectedProcess(post_redirect_process);
}

bool NavigationRequest::IsAllowedByConnectionAllowlist(bool is_redirect) {
  // The connection allowlist base feature is the kill switch for the feature.
  // It is checked first. Then connection allowlist also requires origin trial
  // enabled. In order to check the origin trial status, the initiator policy
  // container policies need to be retrieved.
  if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
    return true;
  }

  // `initiator_frame_token_` not being set implies this is not a
  // renderer-initiated navigation, which is out of the scope of connection
  // allowlist.
  if (!initiator_frame_token_) {
    return true;
  }

  // Here we are using the initiator's policies, regardless of whether they can
  // be inherited by navigation. This is because this call is used to enforce
  // 'connection-allowlist' in the initiator, and so the initiator's policies
  // should always be respected lest we create a bypass.
  const PolicyContainerPolicies* policies =
      GetInitiatorPolicyContainerPolicies();

  if (!policies) {
    return true;
  }

  if (!HasActiveConnectionAllowlists(*policies)) {
    return true;
  }

  network::mojom::NetworkContext* network_context = nullptr;
  net::NetworkAnonymizationKey network_anonymization_key;
  std::optional<base::UnguessableToken> reporting_source;

  RenderFrameHostImpl* initiator_rfh = GetInitiatorDocumentRenderFrameHost();
  if (initiator_rfh) {
    network_context =
        initiator_rfh->GetProcess()->GetStoragePartition()->GetNetworkContext();
    network_anonymization_key = initiator_rfh->GetIsolationInfoForSubresources()
                                    .network_anonymization_key();
    reporting_source = initiator_rfh->GetReportingSource();
  }

  // Perform functional checks (redirects, same-document, local URLs) only after
  // confirming the feature is active for this initiator. Note:
  // redirect_behavior defaults to kBlock if not explicitly set in the
  // Connection-Allowlist header.
  if (is_redirect) {
    return IsRedirectAllowedByConnectionAllowlist(
        *policies, GetOriginalRequestURL(), network_context,
        network_anonymization_key, reporting_source);
  }

  // For same-document navigation, the connection allowlist is not checked. For
  // local navigation, the connection allowlist will be inherited as part of the
  // PolicyContainerHost and applied in the network service in
  // NetworkRestrictionsNavigationThrottle::WillCommitWithoutUrlLoader().
  if (IsSameDocument() || !IsURLHandledByNetworkStack(common_params_->url)) {
    return true;
  }

  // If it is renderer-initiated and a history navigation, it should be
  // checked against connection allowlist unless it is served from the BFcache.
  // https://github.com/WICG/connection-allowlists/issues/4
  if (IsServedFromBackForwardCache()) {
    return true;
  }

  // It's possible that the initiator frame has been deleted by the time this is
  // reached. If so, we can't complete the fenced frame check below, and will
  // fail closed.
  // The feature currently does not impact fenced frames.
  // TODO(crbug.com/447954811): Revisit this if the feature needs to be
  // enabled and fenced frames need to be supported.
  if (initiator_rfh && initiator_rfh->IsNestedWithinFencedFrame()) {
    return true;
  }

  return ConnectionAllowlistAllowsUrlAndReportIfNeeded(
      *policies, common_params_->url, network_context,
      network_anonymization_key, reporting_source);
}

bool NavigationRequest::IsAllowedByCSPDirective(
    const std::vector<network::mojom::ContentSecurityPolicyPtr>& policies,
    network::CSPContext* context,
    network::mojom::CSPDirectiveName directive,
    bool has_followed_redirect,
    bool url_upgraded_after_redirect,
    bool is_opaque_fenced_frame,
    network::CSPContext::CheckCSPDisposition disposition) {
  GURL url;
  // If this request was upgraded in the net stack, downgrade the URL back to
  // HTTP before checking report only policies.
  if (url_upgraded_after_redirect &&
      disposition ==
          network::CSPContext::CheckCSPDisposition::CHECK_REPORT_ONLY_CSP &&
      common_params_->url.SchemeIs(url::kHttpsScheme)) {
    GURL::Replacements replacements;
    replacements.SetSchemeStr(url::kHttpScheme);
    url = common_params_->url.ReplaceComponents(replacements);
  } else {
    url = common_params_->url;
  }
  network::CSPCheckResult result = context->IsAllowedByCsp(
      policies, directive, url, original_url_, has_followed_redirect,
      common_params_->source_location, disposition, is_opaque_fenced_frame);
  if (result.WouldBlockIfWildcardDoesNotMatchWs()) {
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        GetParentFrame(),
        blink::mojom::WebFeature::kCspWouldBlockIfWildcardDoesNotMatchWs);
  }
  return result.IsAllowed();
}

net::Error NavigationRequest::CheckCSPDirectives(
    RenderFrameHostCSPContext parent_context,
    const PolicyContainerPolicies* parent_policies,
    RenderFrameHostCSPContext initiator_context,
    const PolicyContainerPolicies* initiator_policies,
    bool has_followed_redirect,
    bool url_upgraded_after_redirect,
    bool is_response_check,
    network::CSPContext::CheckCSPDisposition disposition) {
  // Following directive checks' order is important as the `error` code takes
  // only the result last set.
  net::Error error = net::OK;

  if (initiator_policies) {
    // [form-action]
    if (begin_params_->is_form_submission && !is_response_check &&
        !IsAllowedByCSPDirective(
            initiator_policies->content_security_policies, &initiator_context,
            network::mojom::CSPDirectiveName::FormAction, has_followed_redirect,
            url_upgraded_after_redirect,
            /*is_opaque_fenced_frame=*/false, disposition)) {
      // net::ERR_ABORTED is used instead of net::ERR_BLOCKED_BY_CSP. This is
      // a better user experience as the user is not presented with an error
      // page. However if other CSP directives like frame-src are violated, it
      // may be appropriate for them to use ERR_BLOCKED_BY_CSP so this can be
      // overridden by the checks below.
      error = net::ERR_ABORTED;
    }
  }

  // [frame-src] or [fenced-frame-src]
  if (parent_policies) {
    bool is_opaque_fenced_frame_root_navigation =
        frame_tree_node_->IsFencedFrameRoot() &&
        fenced_frame_properties_.has_value() &&
        fenced_frame_properties_->mapped_url().has_value() &&
        !fenced_frame_properties_->mapped_url()
             ->GetValueForEntity(FencedFrameEntity::kEmbedder)
             .has_value();
    if (!IsAllowedByCSPDirective(
            parent_policies->content_security_policies, &parent_context,
            frame_tree_node_->IsFencedFrameRoot()
                ? network::mojom::CSPDirectiveName::FencedFrameSrc
                : network::mojom::CSPDirectiveName::FrameSrc,
            has_followed_redirect, url_upgraded_after_redirect,
            is_opaque_fenced_frame_root_navigation, disposition)) {
      error = net::ERR_BLOCKED_BY_CSP;
    }
  }

  return error;
}

net::Error NavigationRequest::CheckContentSecurityPolicy(
    bool has_followed_redirect,
    bool url_upgraded_after_redirect,
    bool is_response_check) {
  CHECK(policy_container_builder_.has_value());
  if (common_params_->url.SchemeIs(url::kAboutScheme))
    return net::OK;

  if (IsSameDocument())
    return net::OK;

  if (common_params_->should_check_main_world_csp ==
      network::mojom::CSPDisposition::DO_NOT_CHECK) {
    return net::OK;
  }

  RenderFrameHostImpl* parent = frame_tree_node()->parent();
  const PolicyContainerPolicies* parent_policies =
      policy_container_builder_->ParentPolicies();
  CHECK(!parent == !parent_policies);
  bool set_parent_for_nested_frame_tree =
      !parent && frame_tree_node()->IsFencedFrameRoot() &&
      frame_tree_node()->render_manager()->GetOuterDelegateNode();
  if (set_parent_for_nested_frame_tree) {
    parent = frame_tree_node()
                 ->render_manager()
                 ->GetOuterDelegateNode()
                 ->current_frame_host()
                 ->GetParent();
    // TODO(antoniosartori): If we want to keep checking frame-src for fenced
    // frames, consider storing a snapshot of the parent policies in the
    // `policy_container_builder_` at the beginning of the navigation.
    parent_policies = &parent->policy_container_host()->policies();
  }

  // Here we are using the initiator policies regardless of whether they can be
  // inherited or not. The initiator policies will be used to enforce
  // 'form-action' in the initiator. This should be enforced even if the
  // policies are ultimately not inherited, as not doing so might allow to
  // bypass the policy.
  const PolicyContainerPolicies* initiator_policies =
      GetInitiatorPolicyContainerPolicies();

  // CSP checking happens in three phases, per steps 3-5 of
  // https://fetch.spec.whatwg.org/#main-fetch:
  //
  // (1) Check report-only policies and trigger reports for any violations.
  // (2) Upgrade the request to HTTPS if necessary.
  // (3) Check enforced policies (triggering reports for any violations of those
  //     policies) and block the request if necessary.
  //
  // This sequence of events allows site owners to learn about (via step 1) any
  // requests that are upgraded in step 2.

  RenderFrameHostCSPContext parent_context(parent);

  // Note: the initiator RenderFrameHost could have been deleted by
  // now. Then this RenderFrameHostCSPContext will do nothing and we won't
  // report violations for this check.
  //
  // If the initiator frame has navigated away in between, we also use a no-op
  // `initiator_csp_context`, in order not to trigger `securitypolicyviolation`
  // events in the wrong document.
  RenderFrameHostCSPContext initiator_context(
      GetInitiatorDocumentRenderFrameHost());

  net::Error report_only_csp_status = CheckCSPDirectives(
      parent_context, parent_policies, initiator_context, initiator_policies,
      has_followed_redirect, url_upgraded_after_redirect, is_response_check,
      network::CSPContext::CHECK_REPORT_ONLY_CSP);

  // upgrade-insecure-requests is handled in the network code for redirects,
  // only do the upgrade here if this is not a redirect.
  // Note that `FrameTreeNode::IsMainFrame()` returns true for fenced frames
  // based on MPArch, but it's fine to skip the logic below as
  // `network::UpgradeInsecureRequest()` does not apply to fenced frame
  // navigation requests. (See https://github.com/WICG/fenced-frame/issues/23)
  if (!has_followed_redirect && !frame_tree_node()->IsMainFrame()) {
    CHECK(parent_policies);
    if (parent_policies && network::ShouldUpgradeInsecureRequest(
                               parent_policies->content_security_policies)) {
      upgrade_if_insecure_ = true;
      network::UpgradeInsecureRequest(&common_params_->url);
      common_params_->referrer = Referrer::SanitizeForRequest(
          common_params_->url, *common_params_->referrer);
      if (base::FeatureList::IsEnabled(
              features::kSanitizeOriginalUrlDuringNavigation)) {
        // It is safe to convert GURL to an Origin and back in the code below
        // because we only want to discard the rest of the URL (e.g., path and
        // params). The actual underlying Origin is not needed, which could be
        // inherited or opaque in sandbox cases.
        commit_params_->original_url =
            common_params_->url.DeprecatedGetOriginAsURL();
      } else {
        commit_params_->original_url = common_params_->url;
      }
      original_url_ = common_params_->url;
    }
  }

  net::Error enforced_csp_status = CheckCSPDirectives(
      parent_context, parent_policies, initiator_context, initiator_policies,
      has_followed_redirect, url_upgraded_after_redirect, is_response_check,
      network::CSPContext::CHECK_ENFORCED_CSP);
  if (enforced_csp_status != net::OK)
    return enforced_csp_status;
  return report_only_csp_status;
}

NavigationRequest::CredentialedSubresourceCheckResult
NavigationRequest::CheckCredentialedSubresource() const {
  // It only applies to subframes.
  if (frame_tree_node_->IsOutermostMainFrame())
    return CredentialedSubresourceCheckResult::ALLOW_REQUEST;

  // URLs with no embedded credentials should load correctly.
  if (!common_params_->url.has_username() &&
      !common_params_->url.has_password())
    return CredentialedSubresourceCheckResult::ALLOW_REQUEST;

  // Relative URLs on top-level pages that were loaded with embedded credentials
  // should load correctly.
  RenderFrameHostImpl* parent = frame_tree_node_->GetParentOrOuterDocument();
  CHECK(parent);
  const GURL& parent_url = parent->GetLastCommittedURL();
  if (url::IsSameOriginWith(parent_url, common_params_->url) &&
      parent_url.GetUsername() == common_params_->url.GetUsername() &&
      parent_url.GetPassword() == common_params_->url.GetPassword()) {
    return CredentialedSubresourceCheckResult::ALLOW_REQUEST;
  }

  // Warn the user about the request being blocked.
  const char* console_message =
      "Subresource requests whose URLs contain embedded credentials (e.g. "
      "`https://user:pass@host/`) are blocked. See "
      "https://www.chromestatus.com/feature/5669008342777856 for more "
      "details.";
  parent->AddMessageToConsole(blink::mojom::ConsoleMessageLevel::kWarning,
                              console_message);
  return CredentialedSubresourceCheckResult::BLOCK_REQUEST;
}

NavigationRequest::AboutSrcDocCheckResult NavigationRequest::CheckAboutSrcDoc()
    const {
  if (!common_params_->url.IsAboutSrcdoc())
    return AboutSrcDocCheckResult::ALLOW_REQUEST;

  // Loading about:srcdoc in the main frame can't have any reasonable meaning.
  // There might be a malicious website trying to exploit a bug from this. As a
  // defensive measure, do not proceed. They would have failed anyway later.
  if (frame_tree_node_->IsMainFrame())
    return AboutSrcDocCheckResult::BLOCK_REQUEST;

  // There are 4 cases where we allow a navigation to about:srcdoc:

  // 1) We allow same-document navigations from any frame.
  if (IsSameDocument()) {
    return AboutSrcDocCheckResult::ALLOW_REQUEST;
  }

  const std::optional<url::Origin>& initiator_origin =
      common_params().initiator_origin;
  // 2) Browser-initiated navigations are (temporarily) allowed for
  // about:srcdoc.
  if (!initiator_origin) {
    // TODO(https://crbug.com/40165505): for now, allow this, and land the
    // change to block it in a separate CL in case it breaks things beyond our
    // local test suites.
    return AboutSrcDocCheckResult::ALLOW_REQUEST;
  }

  // 3) An about:srcdoc frame can reload itself (even if it is cross-origin from
  // its parent due to being sandboxed).
  if (frame_tree_node()
          ->current_frame_host()
          ->GetLastCommittedURL()
          .IsAboutSrcdoc() &&
      initiator_origin->IsSameOriginWith(
          frame_tree_node()->current_frame_host()->GetLastCommittedOrigin())) {
    return AboutSrcDocCheckResult::ALLOW_REQUEST;
  }

  // 4) Setting src = 'about:srcdoc' is allowed for now as long as the
  // initiator's origin matches the origin of the srcdoc's parent. It is
  // important not to allow initiators that are cross-origin with the parent,
  // because the content comes from the parent and many places in the code
  // assume the origin comes from the initiator.
  // TODO(https://crbug.com/40165505): navigations to 'about:srcdoc' aren't
  // supposed to ever be allowed according to spec.
  if (*initiator_origin ==
      frame_tree_node()->parent()->GetLastCommittedOrigin()) {
    return AboutSrcDocCheckResult::ALLOW_REQUEST;
  }

  // Navigations with an initiator that is cross-origin to the about:srcdoc
  // parent are not allowed.
  return AboutSrcDocCheckResult::BLOCK_REQUEST;
}

void NavigationRequest::SetupCSPEmbeddedEnforcement() {
  if (IsInMainFrame())
    return;
  // TODO(https://crbug.com/11129645): MHTML iframe not supported yet.
  if (IsForMhtmlSubframe())
    return;

  // TODO(antoniosartori): Probably we should have taken a snapshot of the 'csp'
  // attribute at the beginning of the navigation and not now, since the
  // beforeunload handlers might have modified it in the meantime.
  // See pull request about the spec:
  // https://github.com/w3c/webappsec-cspee/pull/11
  network::mojom::ContentSecurityPolicyPtr frame_csp_attribute =
      frame_tree_node()->csp_attribute()
          ? frame_tree_node()->csp_attribute()->Clone()
          : nullptr;
  if (frame_csp_attribute) {
    // TODO(antoniosartori): Maybe we should revisit what 'self' means in the
    // 'csp' attribute.
    const GURL& url = GetURL();
    frame_csp_attribute->self_origin = network::mojom::CSPSource::New(
        url.GetScheme(), url.GetHost(), url.EffectiveIntPort(), "", false,
        false);
  }

  const network::mojom::ContentSecurityPolicy* parent_required_csp =
      frame_tree_node()->parent()->required_csp();

  std::vector<network::mojom::ContentSecurityPolicyPtr> frame_csp;
  frame_csp.push_back(std::move(frame_csp_attribute));
  std::string error_message;
  if (network::IsValidRequiredCSPAttr(frame_csp, parent_required_csp,
                                      error_message)) {
    // If |frame_csp| is valid then it is not null.
    SetRequiredCSP(std::move(frame_csp[0]));
    return;
  }

  if (frame_csp[0]) {
    GetParentFrame()->AddMessageToConsole(
        blink::mojom::ConsoleMessageLevel::kError,
        base::StringPrintf("The frame 'csp' attribute ('%s') is invalid and "
                           "will be discarded: %s",
                           frame_csp[0]->header->header_value.c_str(),
                           error_message.c_str()));
  }

  if (parent_required_csp) {
    SetRequiredCSP(parent_required_csp->Clone());
  }
  // TODO(antoniosartori): Consider instead blocking the navigation here,
  // since this seems to be insecure
  // (cf. https://github.com/w3c/webappsec-cspee/pull/11).
}

NavigationRequest::CSPEmbeddedEnforcementResult
NavigationRequest::CheckCSPEmbeddedEnforcement() {
  // We enforce CSPEE only for subframes.
  if (IsInMainFrame())
    return CSPEmbeddedEnforcementResult::ALLOW_RESPONSE;

  if (IsSameDocument())
    return CSPEmbeddedEnforcementResult::ALLOW_RESPONSE;

  if (!required_csp_)
    return CSPEmbeddedEnforcementResult::ALLOW_RESPONSE;

  // The |response()| can be null for navigations that do not require a
  // URLLoader (about:blank, about:srcdoc, ...)
  const network::mojom::OriginOrWildcardHeaderValue* allow_csp_from =
      response() ? response()->parsed_headers->allow_csp_from.get() : nullptr;

  if (network::AllowsBlanketEnforcementOfRequiredCSP(
          GetParentFrame()->GetLastCommittedOrigin(), GetURL(), allow_csp_from,
          required_csp_)) {
    if (GetURL().SchemeIsLocal() || GetURL().SchemeIsFile()) {
      base::UmaHistogramEnumeration(
          "Navigation.CSPEmbeddedEnforcement.Outcome",
          NavigationCSPEmbeddedEnforcementOutcome::kAllowLocalScheme);
    } else {
      base::UmaHistogramEnumeration(
          "Navigation.CSPEmbeddedEnforcement.Outcome",
          NavigationCSPEmbeddedEnforcementOutcome::kAllowAllowCSPFromHeader);
    }
    // Enforce the required CSPs on the frame by passing them down to blink.
    policy_container_builder_->AddContentSecurityPolicy(required_csp_->Clone());
    return CSPEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  // All the URLs that do not |NeedsUrlLoader()| allows blanket enforcement of
  // CSP, Except for MHTML iframe.
  // TODO(arthursonzogni): Make MHTML response to use the normal loading path,
  // by introducing their own MHTML UrlLoader. Then CSPEE can be supported.
  if (!response()) {
    // TODO(https://crbug.com/11129645): Remove MHTML edge case, once MHTML
    // documents are handled through the standard code path with its own
    // URLLoaderFactory.
    CHECK(IsForMhtmlSubframe());
    base::UmaHistogramEnumeration(
        "Navigation.CSPEmbeddedEnforcement.Outcome",
        NavigationCSPEmbeddedEnforcementOutcome::kAllowMHTML);
    return CSPEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  std::string sanitized_blocked_url =
      GetRedirectChain().front().DeprecatedGetOriginAsURL().spec();
  if (allow_csp_from && allow_csp_from->is_error_message()) {
    AddDeferredConsoleMessage(
        blink::mojom::ConsoleMessageLevel::kError,
        base::StringPrintf("The value of the 'Allow-CSP-From' response header "
                           "returned by %s is invalid: %s",
                           sanitized_blocked_url.c_str(),
                           allow_csp_from->get_error_message().c_str()));
  }

  if (network::Subsumes(*required_csp_,
                        response()->parsed_headers->content_security_policy)) {
    base::UmaHistogramEnumeration(
        "Navigation.CSPEmbeddedEnforcement.Outcome",
        NavigationCSPEmbeddedEnforcementOutcome::kAllowSubsumes);
    return CSPEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  base::UmaHistogramEnumeration(
      "Navigation.CSPEmbeddedEnforcement.Outcome",
      NavigationCSPEmbeddedEnforcementOutcome::kBlock);
  AddDeferredConsoleMessage(
      blink::mojom::ConsoleMessageLevel::kError,
      base::StringPrintf(
          "Refused to display '%s' in a frame. The embedder requires it to "
          "enforce the following Content Security Policy: '%s'. However, the "
          "frame neither accepts that policy using the Allow-CSP-From header "
          "nor delivers a Content Security Policy which is at least as strong "
          "as that one.",
          sanitized_blocked_url.c_str(),
          required_csp_->header->header_value.c_str()));

  return CSPEmbeddedEnforcementResult::BLOCK_RESPONSE;
}

void NavigationRequest::SetupConnectionAllowlistEmbeddedEnforcement() {
  if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
    return;
  }
  if (IsInMainFrame()) {
    return;
  }
  // TODO(https://crbug.com/1129645): MHTML iframe not supported yet.
  if (IsForMhtmlSubframe()) {
    return;
  }
  // The feature currently does not impact fenced frames, matching
  // NetworkRestrictionsNavigationThrottle.
  if (frame_tree_node()->IsInFencedFrameTree()) {
    return;
  }

  std::optional<network::ConnectionAllowlist> frame_attribute =
      frame_tree_node()->connection_allowlist_attribute();
  const std::optional<network::ConnectionAllowlist>& parent_required =
      frame_tree_node()->parent()->required_connection_allowlist();

  if (frame_attribute) {
    // The frame's attribute may carry an unresolved `response-origin` token
    // (resolved against the framed response, which isn't known yet), so the
    // never-loosen check against the parent's requirement is deferred to
    // CheckConnectionAllowlistEmbeddedEnforcement(), after resolution.
    required_connection_allowlist_ = std::move(frame_attribute);
    required_connection_allowlist_from_attribute_ = true;
    // Advertise the requirement to the framed document via the request header,
    // mirroring CSP embedded enforcement's `Sec-Required-CSP` (see the spec's
    // "Advertising the requirement" section).
    SetRequestHeader("Sec-Required-Connection-Allowlist",
                     required_connection_allowlist_->serialized_value);
    return;
  }

  if (parent_required) {
    // The parent's requirement cascades into this frame, so it must be
    // advertised on this navigation's request as well.
    required_connection_allowlist_ = parent_required;
    SetRequestHeader("Sec-Required-Connection-Allowlist",
                     required_connection_allowlist_->serialized_value);
  }
}

NavigationRequest::ConnectionAllowlistEmbeddedEnforcementResult
NavigationRequest::CheckConnectionAllowlistEmbeddedEnforcement() {
  if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }
  // We enforce embedded enforcement only for subframes.
  if (IsInMainFrame()) {
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }
  if (IsSameDocument()) {
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }
  if (!required_connection_allowlist_) {
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  // Resolve the deferred `response-origin` token (which could not be resolved
  // in the renderer) against the framed document's origin. Local-scheme
  // documents (about:blank, about:srcdoc) inherit their origin from their
  // initiator instead of deriving one from their URL, so
  // `url::Origin::Create(GetURL())` would resolve the token to an opaque origin
  // and propagate it to this frame's descendants. `GetOriginToCommit()` cannot
  // be used because this also runs before the response is processed, for
  // navigations that need no URLLoader. Ignoring sandbox flags matches how the
  // network service resolves the token for a delivered `Connection-Allowlist`.
  if (required_connection_allowlist_->match_response_origin) {
    required_connection_allowlist_->allowlist.push_back(
        GetOriginForURLLoaderFactoryUnchecked().Serialize());
    required_connection_allowlist_->match_response_origin = false;
  }

  // Never loosen an inherited requirement: if the requirement came from this
  // frame's `connectionallowlist` attribute, it is honored only if -- once its
  // `response-origin` token is resolved -- it is at least as strict as
  // (subsumes) the parent's required allowlist. Otherwise it is discarded in
  // favor of the parent's requirement. This is checked here rather than in
  // Setup because an unresolved `response-origin` attribute has an empty
  // allowlist that would trivially (and incorrectly) subsume any requirement.
  if (required_connection_allowlist_from_attribute_) {
    const std::optional<network::ConnectionAllowlist>& parent_required =
        frame_tree_node()->parent()->required_connection_allowlist();
    if (parent_required &&
        !network::ConnectionAllowlistSubsumes(
            *parent_required, *required_connection_allowlist_)) {
      devtools_instrumentation::OnConnectionAllowlistEmbeddedEnforcementIssue(
          *this, devtools_instrumentation::
                     ConnectionAllowlistEmbeddedEnforcementIssue::
                         kIFrameAttributeLoosensEmbeddingRequirement);
      required_connection_allowlist_ = parent_required;
    }
  }

  // The `response()` can be null for navigations that do not require a
  // URLLoader (about:blank, about:srcdoc, ...).
  const network::mojom::OriginOrWildcardHeaderValue*
      allow_connection_allowlist_from =
          response()
              ? response()
                    ->parsed_headers->allow_connection_allowlist_from.get()
              : nullptr;

  if (network::AllowsBlanketEnforcementOfRequiredConnectionAllowlist(
          GetParentFrame()->GetLastCommittedOrigin(), GetURL(),
          allow_connection_allowlist_from)) {
    // The framed document opted in (via `Allow-Connection-Allowlist-From` or a
    // local scheme). Install the required allowlist as the frame's enforced
    // allowlist at commit.
    //
    // TODO(crbug.com/447954811): For local-scheme frames (about:blank,
    // about:srcdoc, data:, ...), policies are inherited rather than taken from
    // the delivered response, so installing the requirement needs separate
    // handling in IncorporateDeliveredPoliciesForLocalURL(). Until then, only
    // frames whose policies come from the delivered response enforce the
    // requirement (the navigation is still allowed for local schemes).
    if (!GetURL().SchemeIsLocal()) {
      enforce_required_connection_allowlist_ = true;
    }
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  // Navigations that do not need a URLLoader allow blanket enforcement above,
  // except for MHTML iframes (not yet supported).
  if (!response()) {
    CHECK(IsForMhtmlSubframe());
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  if (allow_connection_allowlist_from &&
      allow_connection_allowlist_from->is_error_message()) {
    devtools_instrumentation::OnConnectionAllowlistEmbeddedEnforcementIssue(
        *this,
        devtools_instrumentation::ConnectionAllowlistEmbeddedEnforcementIssue::
            kInvalidAllowConnectionAllowlistFrom);
  }

  // The framed document may instead satisfy the requirement by delivering its
  // own Connection-Allowlist that is at least as strict (subsumes the required
  // one). In that case its own allowlist remains enforced.
  const std::optional<network::ConnectionAllowlist>& delivered =
      response()->parsed_headers->connection_allowlists.enforced;
  if (delivered && network::ConnectionAllowlistSubsumes(
                       *required_connection_allowlist_, *delivered)) {
    return ConnectionAllowlistEmbeddedEnforcementResult::ALLOW_RESPONSE;
  }

  devtools_instrumentation::OnConnectionAllowlistEmbeddedEnforcementIssue(
      *this,
      devtools_instrumentation::ConnectionAllowlistEmbeddedEnforcementIssue::
          kEmbeddingRequirementNotSatisfied);
  return ConnectionAllowlistEmbeddedEnforcementResult::BLOCK_RESPONSE;
}

void NavigationRequest::UpdateHistoryParamsInCommitNavigationParams() {
  NavigationController& navigation_controller =
      frame_tree_node_->navigator().controller();
  commit_params_->current_history_list_index =
      navigation_controller.GetCurrentEntryIndex();
  commit_params_->current_history_list_length =
      navigation_controller.GetEntryCount();
}

void NavigationRequest::SanitizeRedirectsForCommit(
    blink::mojom::CommonNavigationParamsPtr& common_params,
    blink::mojom::CommitNavigationParamsPtr& commit_params) {
  if (!base::FeatureList::IsEnabled(kSanitizeRedirectUrlsDuringNavigation)) {
    return;
  }

  // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error
  // Pages are isolated.
  const bool should_sanitize_final_url_for_error_page =
      base::FeatureList::IsEnabled(
          features::kSanitizeFailedSubframeNavigationUrls) &&
      ComputeErrorPageProcess() == ErrorPageProcess::kCurrentProcess &&
      !commit_params->redirect_params.empty() &&
      !url::Origin::Create(common_params->url)
           .IsSameOriginWith(GetRenderFrameHost()->GetLastCommittedOrigin());

  if (should_sanitize_final_url_for_error_page) {
    common_params->url = common_params->url.DeprecatedGetOriginAsURL();
  }

  // It is safe to convert GURL to an Origin and back in the code below because
  // we only want to discard the rest of the URL (e.g., path and params). The
  // actual underlying Origin is not needed, which could be inherited or opaque
  // in sandbox cases.
  for (GURL& redirect : commit_params->redirects) {
    redirect = redirect.DeprecatedGetOriginAsURL();
  }

  // In the redirect_params vector, the last entry contains the URL we are going
  // to commit after following all redirects. We should not be sanitizing it, as
  // we need to commit the real URL as part of the navigation. Make an exception
  // if the error page commits in the initiator's process and the navigation was
  // redirected before failing, in which case that final URL may be cross-origin
  // to the receiving process and must be reduced to origin as well.
  if (!commit_params->redirect_params.empty()) {
    base::span<blink::mojom::NavigationRedirectParamsPtr> redirect_params_span(
        commit_params->redirect_params);
    if (!should_sanitize_final_url_for_error_page) {
      redirect_params_span =
          redirect_params_span.first(redirect_params_span.size() - 1);
    }
    for (auto& redirect_param : redirect_params_span) {
      redirect_param->redirect_info.new_url =
          redirect_param->redirect_info.new_url.DeprecatedGetOriginAsURL();
    }
  }

  if (base::FeatureList::IsEnabled(
          features::kSanitizeLocationHeadersDuringNavigation)) {
    // The expected origin of the process that will host the committed document.
    // We use this to determine if a redirect is cross-origin to the committing
    // process. For successful navigations, we assume the committing process
    // will match the origin of the destination URL. For error pages that are
    // allowed to commit in the current process, we use the current origin of
    // the RenderFrameHost.
    const url::Origin expected_commit_process_origin =
        should_sanitize_final_url_for_error_page
            ? GetRenderFrameHost()->GetLastCommittedOrigin()
            : url::Origin::Create(common_params->url);

    // Sanitize the "Location" headers for redirects that are cross-origin to
    // the final committed URL (or receiving process for error pages).
    // TODO(crbug.com/495463654): Consider if we need to handle cases that
    // inherit an origin (e.g. about:blank), or if we cross a CSP sandbox
    // boundary where the origin becomes unique/opaque.
    for (auto& redirect_param : commit_params->redirect_params) {
      auto& response_head = redirect_param->response_head;
      if (!response_head || !response_head->headers ||
          !response_head->headers->HasHeader("Location")) {
        continue;
      }

      const GURL& target_url = redirect_param->redirect_info.new_url;
      const url::Origin target_origin = url::Origin::Create(target_url);
      if (!target_origin.IsSameOriginWith(expected_commit_process_origin)) {
        response_head->headers->SetHeader("Location",
                                          target_origin.GetURL().spec());
      }
    }
  }
}

void NavigationRequest::RendererRequestedNavigationCancellationForTesting() {
  OnNavigationClientDisconnected(0, "");
}

void NavigationRequest::OnNavigationClientDisconnected(
    uint32_t reason,
    const std::string& description) {
  // Renderer-initiated navigation cancellations can only happen before the
  // navigation gets into the READY_TO_COMMIT state, because
  // RendererCancellationThrottle will prevent renderer-initiated navigations
  // from entering that state before the JS task that started the navigation
  // finishes. After navigation reaches READY_TO_COMMIT stage, we should
  // ignore these navigation cancellations, except for these two cases:
  // 1. It reuses the current RenderFrame(Host), because the RenderFrame expects
  // the navigation to be cancelled successfully (as the state in the renderer
  // is already updated to cancel the navigation).
  // TODO(crbug.com/40615943): This case will eventually go away with
  // RenderDocument as cross-document navigations won't reuse RenderFrameHosts
  // anymore. Fix tests that expect this behavior.
  // 2. The target renderer had crashed, so the speculative RenderFrame is not
  // live anymore, because the navigation can't commit in a crashed renderer.
  std::optional<NavigationDiscardReason> discard_reason;
  if (HasRenderFrameHost() && !GetRenderFrameHost()->IsRenderFrameLive()) {
    discard_reason = NavigationDiscardReason::kRenderProcessGone;
  } else {
    switch (static_cast<mojom::NavigationClientDisconnectReason>(reason)) {
      case mojom::NavigationClientDisconnectReason::kResetForSwap:
        // If the RenderFrame that initiated this navigation request is swapped
        // out (disconnecting its NavigationClient for this request), do not
        // treat it as a cancellation. Otherwise, if a previous navigation
        // before `this` is slow to commit, it would unexpectedly cancel `this`
        // subsequent attempt to navigate elsewhere.
        return;
      case mojom::NavigationClientDisconnectReason::kNoExplicitReason:
        error_navigation_trigger_ =
            ErrorNavigationTrigger::kNavigationClientDisconnected;
        discard_reason = NavigationDiscardReason::kInternalCancellation;
        break;
      case mojom::NavigationClientDisconnectReason::kResetForAbort:
        discard_reason = NavigationDiscardReason::kExplicitCancellation;
        break;
      case mojom::NavigationClientDisconnectReason::kResetForNewNavigation:
        discard_reason =
            NavigationDiscardReason::kNewOtherNavigationRendererInitiated;
        break;
      case mojom::NavigationClientDisconnectReason::
          kResetForDuplicateNavigation:
        discard_reason = NavigationDiscardReason::kNewDuplicateNavigation;
        break;
    }
    if (!discard_reason.has_value()) {
      // TODO(https://crbug.com/366060351): An invalid value was used. Kill
      // either the requesting or committing client's process.
      return;
    }
  }

  if (!IsWaitingToCommit()) {
    // The cancellation happens before READY_TO_COMMIT.
    frame_tree_node_->navigator().CancelNavigation(frame_tree_node_,
                                                   discard_reason.value());
  } else if (GetRenderFrameHost() ==
                 frame_tree_node_->render_manager()->current_frame_host() ||
             !GetRenderFrameHost()->IsRenderFrameLive() ||
             (base::FeatureList::IsEnabled(
                  features::kSkipRendererCancellationThrottle) &&
              commit_params_->commit_target_frame_token.has_value())) {
    // If the NavigationRequest has already reached READY_TO_COMMIT,
    // `render_frame_host_` owns `this`. Cache any needed state in stack
    // variables to avoid a use-after-free.
    FrameTreeNode* frame_tree_node = frame_tree_node_;
    GetRenderFrameHost()->NavigationRequestCancelled(this,
                                                     discard_reason.value());
    // Ensure that the speculative RFH, if any, is also cleaned up.
    frame_tree_node->render_manager()->DiscardSpeculativeRFHIfUnused(
        discard_reason.value());
  }

  // Do not add code after this, NavigationRequest might have been destroyed.
}

void NavigationRequest::ReuseRequestNavigationClientForCommitIfNeeded() {
  if (!request_navigation_client_.is_bound()) {
    return;
  }
  auto* rfh_at_construction = RenderFrameHostImpl::FromID(
      current_render_frame_host_id_at_construction_);
  if (GetRenderFrameHost() == rfh_at_construction) {
    // Reuse the request NavigationClient for commit.
    commit_navigation_client_ = std::move(request_navigation_client_);
  } else if (base::FeatureList::IsEnabled(
                 features::kSkipRendererCancellationThrottle) &&
             rfh_at_construction &&
             (rfh_at_construction == frame_tree_node_->current_frame_host()) &&
             rfh_at_construction->GetSiteInstance()->group() ==
                 GetRenderFrameHost()->GetSiteInstance()->group()) {
    // When doing a local RenderFrame swap, to ensure navigation cancellation
    // behavior is the same with what it used to be if we didn't do a
    // RenderFrame swap, reuse `request_navigation_client_` for commit.
    // This allows last-minute navigation cancellations from the requester
    // to still take effect, even if the commit is in-flight to the renderer.
    // If the navigation is not cancelled, when doing the commit in the
    // renderer, the NavigationClient that is currently owned by the old
    // RenderFrame will later be moved to be owned by the new RenderFrame.
    commit_params_->commit_target_frame_token =
        GetRenderFrameHost()->GetFrameToken();
    commit_navigation_client_ = std::move(request_navigation_client_);
    HandleInterfaceDisconnection(commit_navigation_client_);
  } else {
    // This navigation is cross-RenderFrameHost: the original document should
    // no longer be able to cancel it.
    IgnoreInterfaceDisconnection();
  }
}

void NavigationRequest::HandleInterfaceDisconnection(
    mojo::AssociatedRemote<mojom::NavigationClient>& navigation_client) {
  // `Unretained()` is safe because the `mojo::AssociatedRemote` reference only
  // refers to fields owned by `this`.
  navigation_client.set_disconnect_with_reason_handler(
      base::BindOnce(&NavigationRequest::OnNavigationClientDisconnected,
                     base::Unretained(this)));
}

void NavigationRequest::IgnoreInterfaceDisconnection() {
  return request_navigation_client_.set_disconnect_handler(base::DoNothing());
}

void NavigationRequest::IgnoreCommitInterfaceDisconnection() {
  return commit_navigation_client_.set_disconnect_handler(base::DoNothing());
}

bool NavigationRequest::IsSameDocument() const {
  return NavigationTypeUtils::IsSameDocument(common_params_->navigation_type);
}

std::optional<base::UnguessableToken>
NavigationRequest::GetSameDocumentMetricsToken() const {
  return same_document_metrics_token_;
}

bool NavigationRequest::IsHistory() const {
  return NavigationTypeUtils::IsHistory(common_params_->navigation_type);
}

bool NavigationRequest::IsRestore() const {
  return NavigationTypeUtils::IsRestore(common_params_->navigation_type);
}

bool NavigationRequest::IsReload() const {
  return NavigationTypeUtils::IsReload(common_params_->navigation_type);
}

RenderFrameHost* NavigationRequest::GetRenderFrameHostForDownloadLogging() {
  RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host();

  // Attribute download logging to the initiator's RenderFrameHost for a
  // newly opened popup that turns into a download. This allows for actionable
  // telemetry (UKM) and better debugging. Note: This is a best-effort approach,
  // as the initiator document might have already navigated away.
  if (IsInOutermostMainFrame() && rfh->is_initial_empty_document()) {
    if (RenderFrameHostImpl* initiator_rfh =
            GetInitiatorDocumentRenderFrameHost()) {
      rfh = initiator_rfh;
    }
  }

  return rfh;
}

void NavigationRequest::RecordDownloadUseCountersPrePolicyCheck() {
  RenderFrameHost* rfh = GetRenderFrameHostForDownloadLogging();

  GetContentClient()->browser()->LogWebFeatureForCurrentPage(
      rfh, blink::mojom::WebFeature::kDownloadPrePolicyCheck);

  // Log UseCounters for opener navigations.
  if (download_policy().IsType(
          blink::NavigationDownloadType::kOpenerCrossOrigin)) {
    rfh->AddMessageToConsole(
        blink::mojom::ConsoleMessageLevel::kError,
        base::StringPrintf(
            "Navigating a cross-origin opener to a download (%s) is "
            "deprecated, see "
            "https://www.chromestatus.com/feature/5742188281462784.",
            common_params_->url.DeprecatedGetOriginAsURL().spec().c_str()));
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        rfh, blink::mojom::WebFeature::kOpenerNavigationDownloadCrossOrigin);
  }

  // Log UseCounters for download in sandbox.
  if (download_policy().IsType(blink::NavigationDownloadType::kSandbox)) {
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        rfh, blink::mojom::WebFeature::kDownloadInSandbox);
  }

  // Log UseCounters for download without user activation.
  if (download_policy().IsType(blink::NavigationDownloadType::kNoGesture)) {
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        rfh, blink::mojom::WebFeature::kDownloadWithoutUserGesture);
  }

  // Log UseCounters for download in ad frame without user activation.
  if (download_policy().IsType(
          blink::NavigationDownloadType::kAdFrameNoGesture)) {
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        rfh, blink::mojom::WebFeature::kDownloadInAdFrameWithoutUserGesture);
  }

  // Log UseCounters for download in ad frame.
  if (download_policy().IsType(blink::NavigationDownloadType::kAdFrame)) {
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        rfh, blink::mojom::WebFeature::kDownloadInAdFrame);
  } else if (download_policy().IsType(
                 blink::NavigationDownloadType::kAdScript)) {
    // We only record kDownloadFromAdScript if kDownloadInAdFrame is not set.
    // This avoids double counting and makes it easier to isolate the impact
    // of ad scripts.
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        rfh, blink::mojom::WebFeature::kDownloadFromAdScript);
  }
}

void NavigationRequest::RecordDownloadUseCountersPostPolicyCheck() {
  CHECK(is_download_);
  RenderFrameHost* rfh = GetRenderFrameHostForDownloadLogging();

  GetContentClient()->browser()->LogWebFeatureForCurrentPage(
      rfh, blink::mojom::WebFeature::kDownloadPostPolicyCheck);
}

void NavigationRequest::OnNavigationEventProcessed(
    NavigationThrottleEvent event,
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK_NE(NavigationThrottle::DEFER, result.action());
  switch (event) {
    case NavigationThrottleEvent::kNoEvent:
      DUMP_WILL_BE_NOTREACHED();
      return;
    case NavigationThrottleEvent::kWillStartRequest:
      OnWillStartRequestProcessed(result);
      return;
    case NavigationThrottleEvent::kWillRedirectRequest:
      OnWillRedirectRequestProcessed(result);
      return;
    case NavigationThrottleEvent::kWillFailRequest:
      OnWillFailRequestProcessed(result);
      return;
    case NavigationThrottleEvent::kWillProcessResponse:
      OnWillProcessResponseProcessed(result);
      return;
    case NavigationThrottleEvent::kWillCommitWithoutUrlLoader:
      OnWillCommitWithoutUrlLoaderProcessed(result);
      return;
  }
  NOTREACHED();
}

void NavigationRequest::OnWillStartRequestProcessed(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK_EQ(WILL_START_REQUEST, state_);
  CHECK_NE(NavigationThrottle::BLOCK_RESPONSE, result.action());
  CHECK(processing_navigation_throttle_);
  processing_navigation_throttle_ = false;
  if (result.action() != NavigationThrottle::PROCEED)
    SetState(CANCELING);

  if (complete_callback_for_testing_ &&
      std::move(complete_callback_for_testing_).Run(result)) {
    return;
  }

  if (MaybeEvictFromBackForwardCacheBySubframeNavigation(
          frame_tree_node_->current_frame_host())) {
    // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
    // deleted by the previous calls.
    return;
  }

  OnStartChecksComplete(result);

  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

void NavigationRequest::OnWillRedirectRequestProcessed(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK_EQ(WILL_REDIRECT_REQUEST, state_);
  CHECK_NE(NavigationThrottle::BLOCK_RESPONSE, result.action());
  CHECK(processing_navigation_throttle_);
  processing_navigation_throttle_ = false;
  if (result.action() == NavigationThrottle::PROCEED) {
    // Notify the delegate that a redirect was encountered and will be followed.
    if (GetDelegate()) {
      // TODO(crbug.com/504574017): Remove the WeakPtr check once Android
      // WebView can avoid the synchronous deletion case.
#if BUILDFLAG(IS_ANDROID)
      base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
#else
      CHECK(is_safe_to_delete_);
      base::AutoReset<bool> resetter(&is_safe_to_delete_, false);
#endif
      GetDelegate()->DidRedirectNavigation(this);
#if BUILDFLAG(IS_ANDROID)
      if (!this_ptr) {
        // The NavigationRequest was deleted during the above call.
        return;
      }
#endif
    }
  } else {
    SetState(CANCELING);
  }

  if (complete_callback_for_testing_ &&
      std::move(complete_callback_for_testing_).Run(result)) {
    return;
  }
  OnRedirectChecksComplete(result);

  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

void NavigationRequest::OnWillFailRequestProcessed(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK_EQ(WILL_FAIL_REQUEST, state_);
  CHECK_NE(NavigationThrottle::BLOCK_RESPONSE, result.action());
  CHECK(processing_navigation_throttle_);
  processing_navigation_throttle_ = false;
  if (result.action() == NavigationThrottle::PROCEED) {
    result = NavigationThrottle::ThrottleCheckResult(
        NavigationThrottle::PROCEED, net_error_);
  } else {
    SetState(CANCELING);
  }

  if (complete_callback_for_testing_ &&
      std::move(complete_callback_for_testing_).Run(result)) {
    return;
  }
  OnFailureChecksComplete(result);

  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

void NavigationRequest::OnWillProcessResponseProcessed(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK_EQ(WILL_PROCESS_RESPONSE, state_);
  CHECK_NE(NavigationThrottle::BLOCK_REQUEST, result.action());
  CHECK_NE(NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE, result.action());
  CHECK(processing_navigation_throttle_);
  processing_navigation_throttle_ = false;
  if (result.action() != NavigationThrottle::PROCEED) {
    SetState(CANCELING);
  }

  base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
  if (complete_callback_for_testing_ &&
      std::move(complete_callback_for_testing_).Run(result)) {
    return;
  }
  CHECK(this_ptr);
  OnWillProcessResponseChecksComplete(result);
  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

void NavigationRequest::OnWillCommitWithoutUrlLoaderProcessed(
    NavigationThrottle::ThrottleCheckResult result) {
  CHECK_EQ(WILL_COMMIT_WITHOUT_URL_LOADER, state_);
  // TODO(522891290): CHECK-exclusion: Convert to a CHECK once we are confident
  // it won't be triggered.
  DCHECK(result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
         result.action() == NavigationThrottle::PROCEED);
  CHECK(processing_navigation_throttle_);
  processing_navigation_throttle_ = false;
  if (complete_callback_for_testing_ &&
      std::move(complete_callback_for_testing_).Run(result)) {
    return;
  }

  if (MaybeEvictFromBackForwardCacheBySubframeNavigation(
          frame_tree_node_->current_frame_host())) {
    // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
    // deleted by the previous calls.
    return;
  }

  OnWillCommitWithoutUrlLoaderChecksComplete(result);

  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

RenderFrameHostImpl*
NavigationRequest::GetRenderFrameHostRestoredFromBackForwardCache() const {
  if (IsServedFromBackForwardCache()) {
    return &*rfh_restored_from_back_forward_cache_.value();
  }
  return nullptr;
}

NavigatorDelegate* NavigationRequest::GetDelegate() const {
  return frame_tree_node()->navigator().GetDelegate();
}

void NavigationRequest::Resume(NavigationThrottle* resuming_throttle) {
  CHECK(resuming_throttle);
  CHECK(!is_resuming_) << "This call does not support re-entrancy.";
  // We cannot resume the navigation during the destruction of the
  // NavigationRequest. Otherwise the construction of the URL loader will crash
  // because FrameTreeNode::navigation_request() returns nullptr.
  if (is_destructing_) {
    return;
  }
  EnterChildTraceEvent("Resume", this);

  if (1u == throttle_registry_->GetDeferringThrottles().size()) {
    is_resuming_ = true;

    // Stop watching for response body changes to ensure that the response body
    // callback isn't called later in the throttle's lifetime with a response
    // body that is not relevant to the throttle.
    if (response_body_watcher_) {
      CHECK(response_body_callback_);
      response_body_watcher_.reset();
      base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
      std::move(response_body_callback_).Run(std::string());
      CHECK(this_ptr) << "Synchronous cancellation is forbidden.";
    }

    is_resuming_ = false;
  }
  throttle_registry_->ResumeProcessingNavigationEvent(resuming_throttle);
  // DO NOT ADD CODE AFTER THIS, as the NavigationHandle might have been deleted
  // by the previous call.
}

void NavigationRequest::CancelDeferredNavigation(
    NavigationThrottle* cancelling_throttle,
    NavigationThrottle::ThrottleCheckResult result) {
  // TODO(https://crbug.com/526542189): CHECK-exclusion: Convert to CHECK once
  // we are sure this isn't hit.
  DCHECK(cancelling_throttle);
  // TODO(https://crbug.com/526541751): CHECK-exclusion: Convert to CHECK once
  // we are sure this isn't hit.
  DCHECK(throttle_registry_->GetDeferringThrottles().contains(
      cancelling_throttle));
  CancelDeferredNavigationInternal(result);
}

void NavigationRequest::RegisterThrottleForTesting(
    std::unique_ptr<NavigationThrottle> navigation_throttle) {
  // Throttles will already have run the first time the page was navigated, we
  // won't run them again on activation. See instead CommitDeferringCondition.
  CHECK(!IsPageActivation())
      << "Attempted to register a NavigationThrottle for an activating "
         "navigation which will not work.";
  throttle_registry_->AddThrottle(std::move(navigation_throttle));
}
bool NavigationRequest::IsDeferredForTesting() {
  return IsDeferred();
}

bool NavigationRequest::IsMhtmlOrSubframe() {
  CHECK(state_ >= WILL_PROCESS_RESPONSE ||
        state_ == WILL_START_REQUEST && !NeedsUrlLoader());

  return is_mhtml_or_subframe_;
}

bool NavigationRequest::IsForMhtmlSubframe() const {
  return frame_tree_node_->parent() && frame_tree_node_->frame_tree()
                                           .root()
                                           ->current_frame_host()
                                           ->is_mhtml_document();
}

void NavigationRequest::CancelDeferredNavigationInternal(
    NavigationThrottle::ThrottleCheckResult result) {
  // TODO(522889716): CHECK-exclusion: Convert to a CHECK once we are confident
  // it won't be triggered.
  DCHECK(processing_navigation_throttle_);
  CHECK(result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
        result.action() == NavigationThrottle::CANCEL ||
        result.action() == NavigationThrottle::BLOCK_RESPONSE ||
        result.action() == NavigationThrottle::BLOCK_REQUEST ||
        result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE);
  CHECK((result.action() != NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE &&
         result.action() != NavigationThrottle::BLOCK_REQUEST) ||
        state_ == WILL_START_REQUEST || state_ == WILL_REDIRECT_REQUEST);

  EnterChildTraceEvent("CancelDeferredNavigation", this);
  NavigationState old_state = state_;
  SetState(CANCELING);
  if (complete_callback_for_testing_ &&
      std::move(complete_callback_for_testing_).Run(result)) {
    return;
  }

  switch (old_state) {
    case WILL_START_REQUEST:
      OnStartChecksComplete(result);
      return;
    case WILL_REDIRECT_REQUEST:
      OnRedirectChecksComplete(result);
      return;
    case WILL_FAIL_REQUEST:
      OnFailureChecksComplete(result);
      return;
    case WILL_PROCESS_RESPONSE:
      OnWillProcessResponseChecksComplete(result);
      return;
    case WILL_COMMIT_WITHOUT_URL_LOADER:
      OnWillCommitWithoutUrlLoaderChecksComplete(result);
      return;
    default:
      NOTREACHED();
  }
  // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
  // deleted by the previous calls.
}

void NavigationRequest::WillStartRequest() {
  TRACE_EVENT("navigation", "NavigationRequest::WillStartRequest",
              perfetto::Flow::FromPointer(this));
  EnterChildTraceEvent("WillStartRequest", this);
  CHECK_EQ(state_, WILL_START_REQUEST);
  will_start_request_time_ = base::TimeTicks::Now();

  if (IsSelfReferentialURL()) {
    SetState(CANCELING);
    DVLOG(1) << "Cancelling self-referential request for " << GetURL();
    if (complete_callback_for_testing_ &&
        std::move(complete_callback_for_testing_)
            .Run(NavigationThrottle::CANCEL)) {
      return;
    }
    OnWillProcessResponseChecksComplete(NavigationThrottle::CANCEL);

    // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
    // deleted by the previous calls.
    return;
  }

  // Throttles will already have run the first time the page was navigated, we
  // won't run them again on activation.
  if (!IsPageActivation()) {
    base::ElapsedTimer duration;
    throttle_registry_->RegisterNavigationThrottles();
    base::UmaHistogramTimes(
        base::StrCat({"Navigation.RegisterNavigationThrottlesTime.",
                      IsInMainFrame() ? "MainFrame" : "Subframe"}),
        duration.Elapsed());
  }

  // If the content/ embedder did not pass the NavigationUIData at the beginning
  // of the navigation, ask for it now.
  if (!navigation_ui_data_) {
    navigation_ui_data_ = GetDelegate()->GetNavigationUIData(this);
  }

  processing_navigation_throttle_ = true;

  base::ScopedUmaHistogramTimer timer(base::StrCat(
      {"Navigation.ProcessNavigationThrottlesTime.WillStartRequest.",
       IsInMainFrame() ? "MainFrame" : "SubFrame"}));
  // Notify each throttle of the request.
  throttle_registry_->ProcessNavigationEvent(
      NavigationThrottleEvent::kWillStartRequest);
  // DO NOT ADD CODE AFTER THIS, as the NavigationHandle might have been deleted
  // by the previous call.
}

void NavigationRequest::WillRedirectRequest(
    const GURL& new_referrer_url,
    RenderProcessHost* post_redirect_process) {
  TRACE_EVENT("navigation", "NavigationRequest::WillRedirectRequest",
              perfetto::Flow::FromPointer(this));
  EnterChildTraceEvent("WillRedirectRequest", this, "url",
                       common_params_->url.possibly_invalid_spec());
  UpdateStateFollowingRedirect(new_referrer_url);
  UpdateSiteInfo(post_redirect_process);

  if (IsSelfReferentialURL()) {
    SetState(CANCELING);
    if (complete_callback_for_testing_ &&
        std::move(complete_callback_for_testing_)
            .Run(NavigationThrottle::CANCEL)) {
      return;
    }
    OnWillProcessResponseChecksComplete(NavigationThrottle::CANCEL);

    // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been
    // deleted by the previous calls.
    return;
  }

  // Notify each throttle of the request.
  throttle_registry_->ProcessNavigationEvent(
      NavigationThrottleEvent::kWillRedirectRequest);
  // DO NOT ADD CODE AFTER THIS, as the NavigationHandle might have been deleted
  // by the previous call.
}

void NavigationRequest::WillFailRequest() {
  EnterChildTraceEvent("WillFailRequest", this);

  SetState(WILL_FAIL_REQUEST);
  processing_navigation_throttle_ = true;

  // Notify each throttle of the request.
  throttle_registry_->ProcessNavigationEvent(
      NavigationThrottleEvent::kWillFailRequest);
  // DO NOT ADD CODE AFTER THIS, as the NavigationHandle might have been deleted
  // by the previous call.
}

void NavigationRequest::WillProcessResponse() {
  TRACE_EVENT("navigation", "NavigationRequest::WillProcessResponse",
              perfetto::Flow::FromPointer(this));
  EnterChildTraceEvent("WillProcessResponse", this);
  CHECK_EQ(state_, WILL_PROCESS_RESPONSE);

  processing_navigation_throttle_ = true;
  was_get_response_body_called_ = false;
  base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());

  // Notify each throttle of the response.
  throttle_registry_->ProcessNavigationEvent(
      NavigationThrottleEvent::kWillProcessResponse);

  // `this` may have been deleted by the previous call.
  if (!this_ptr) {
    // DO NOT ADD CODE HERE.
    return;
  }

  CHECK(!was_get_response_body_called_ || IsDeferred());
}

void NavigationRequest::WillCommitWithoutUrlLoader() {
  TRACE_EVENT("navigation", "NavigationRequest::WillCommitWithoutUrlLoader",
              perfetto::Flow::FromPointer(this));
  EnterChildTraceEvent("WillCommitWithoutUrlLoader", this);

  throttle_registry_->RegisterNavigationThrottlesForCommitWithoutUrlLoader();

  // `CommitNavigation()` expects to be called once the request has reached
  // at least `WILL_PROCESS_RESPONSE`. `WILL_COMMIT_WITHOUT_URL_LOADER` meets
  // that requirement, and is useful to clarify which throttles we are waiting
  // for.
  SetState(WILL_COMMIT_WITHOUT_URL_LOADER);
  processing_navigation_throttle_ = true;

  throttle_registry_->ProcessNavigationEvent(
      NavigationThrottleEvent::kWillCommitWithoutUrlLoader);
}

bool NavigationRequest::IsSelfReferentialURL() {
  // about: URLs should be exempted since they are reserved for other purposes
  // and cannot be the source of infinite recursion.
  // See https://crbug.com/341858 .
  if (common_params_->url.SchemeIs(url::kAboutScheme))
    return false;

  // Browser-triggered navigations should be exempted.
  if (browser_initiated())
    return false;

  // Some sites rely on constructing frame hierarchies where frames are loaded
  // via POSTs with the same URLs, so exempt POST requests.
  // See https://crbug.com/710008.
  if (common_params_->method == "POST")
    return false;

  // We allow one level of self-reference because some sites depend on that,
  // but we don't allow more than one.
  bool found_self_reference = false;
  for (RenderFrameHost* rfh = frame_tree_node()->parent(); rfh;
       rfh = rfh->GetParent()) {
    if (rfh->GetLastCommittedURL().EqualsIgnoringRef(common_params_->url)) {
      if (found_self_reference)
        return true;
      found_self_reference = true;
    }
  }
  return false;
}

void NavigationRequest::DidCommitNavigation(
    const mojom::DidCommitProvisionalLoadParams& params,
    bool navigation_entry_committed,
    bool did_replace_entry,
    const GURL& previous_main_frame_url) {
  TRACE_EVENT("navigation", "NavigationRequest::DidCommitNavigation",
              perfetto::Flow::FromPointer(this));
  common_params_->url = params.url;
  did_replace_entry_ = did_replace_entry;
  should_update_history_ = params.should_update_history;
  navigation_handle_timing_.navigation_commit_received_time =
      params.commit_navigation_start;
  navigation_handle_timing_.navigation_commit_reply_sent_time =
      params.commit_reply_sent;
  navigation_handle_timing_.navigation_did_commit_time = base::TimeTicks::Now();
  // A same document navigation with the same url, and no user-gesture is
  // typically the result of 'history.replaceState().' As the page is
  // controlling this, the user doesn't really think of this as a navigation
  // and it doesn't make sense to log this in history. Logging this in history
  // would lead to lots of visits to a particular page, which impacts the
  // visit count.
  // Navigations in non-primary frame trees don't appear in history.
  if ((should_update_history_ && IsSameDocument() && !HasUserGesture() &&
       params.url == previous_main_frame_url) ||
      !GetRenderFrameHost()->GetPage().IsPrimary()) {
    should_update_history_ = false;
  }
  previous_main_frame_url_ = previous_main_frame_url;

  // It should be kept in sync with the check in
  // RenderFrameHostImpl::TakeNewDocumentPropertiesFromNavigation.
  if (DidEncounterError()) {
    EnterChildTraceEvent("DidCommitNavigation: error page", this);
    SetState(DID_COMMIT_ERROR_PAGE);
  } else {
    EnterChildTraceEvent("DidCommitNavigation", this);
    SetState(DID_COMMIT);
  }
  navigation_or_document_handle_->OnNavigationCommitted(*this);

  // If the navigation committed successfully, pass ownership of ViewTransition
  // resources to the new view. This ensures that the resources are cleaned up
  // if the new renderer process terminates before taking ownership of them.
  if (view_transition_resources_ && state_ == DID_COMMIT) {
    GetRenderFrameHost()
        ->GetRenderWidgetHost()
        ->GetRenderWidgetHostViewBase()
        ->SetViewTransitionResources(std::move(view_transition_resources_));
  }

  StopCommitTimeout();

  // Switching BrowsingInstance because of COOP or top-level cross browsing
  // instance navigation resets the name of the frame. The renderer already
  // knows locally about it because we sent an empty name at frame creation
  // time. The renderer has now committed the page and we can safely enforce the
  // empty name on the browser side.
  BrowserContext* context =
      frame_tree_node_->navigator().controller().GetBrowserContext();
  bool should_clear_browsing_instance_name =
      browsing_context_group_swap().ShouldClearWindowName() ||
      (commit_params().is_cross_site_cross_browsing_context_group &&
       base::FeatureList::IsEnabled(
           features::kClearCrossSiteCrossBrowsingContextGroupWindowName) &&
       GetContentClient()
           ->browser()
           ->IsClearWindowNameForNewBrowsingContextGroupAllowed(context));

  if (should_clear_browsing_instance_name) {
    std::string name, unique_name;
    // The "swap" only affect main frames, that have an empty unique name.
    if (features::GetBrowsingContextMode() ==
        features::BrowsingContextStateImplementationType::
            kLegacyOneToOneWithFrameTreeNode) {
      CHECK(frame_tree_node_->unique_name().empty());
      GetRenderFrameHost()->browsing_context_state()->SetFrameName(name,
                                                                   unique_name);
    }
  }

  // Record metrics for the time it took to commit the navigation if it was to
  // another document without error.
  if (!IsSameDocument() && state_ != DID_COMMIT_ERROR_PAGE) {
    ui::PageTransition transition =
        ui::PageTransitionFromInt(common_params_->transition);
    base::Process::Priority priority =
        GetRenderFrameHost()->GetProcess()->GetPriority();

    RecordStartToCommitMetrics(
        common_params_->navigation_start, transition, ready_to_commit_time_,
        priority, is_same_process_, frame_tree_node_->IsMainFrame());
  }

  CHECK(!frame_tree_node_->IsMainFrame() || navigation_entry_committed)
      << "Only subframe navigations can get here without changing the "
      << "NavigationEntry";
  subframe_entry_committed_ = navigation_entry_committed;

  // For successful navigations, ensure the frame owner element is no longer
  // collapsed as a result of a prior navigation.
  if (state_ != DID_COMMIT_ERROR_PAGE &&
      (!frame_tree_node()->IsMainFrame() ||
       frame_tree_node()->IsFencedFrameRoot())) {
    // The last committed load in collapsed frames will be an error page with
    // |kUnreachableWebDataURL|. Same-document navigation should not be
    // possible.
    // TODO(523045758): CHECK-exclusion: Convert to a CHECK once we are
    // confident it won't be triggered.
    DCHECK(!IsSameDocument() || !frame_tree_node()->is_collapsed());
    frame_tree_node()->SetCollapsed(false);
  }

  if (service_worker_handle_ &&
      service_worker_handle_->service_worker_client()) {
    // Notify the service worker navigation handle that the navigation finished
    // committing.
    service_worker_handle_->service_worker_client()->OnEndNavigationCommit();
  }

  // TODO(crbug.com/40249865): consider using NavigationOrDocumentHandle
  // instead once we can get a WeakDocumentPtr from NavigationOrDocumentHandle.
  if (subresource_proxying_url_loader_service_bind_context_) {
    CHECK(!IsSameDocument());

    subresource_proxying_url_loader_service_bind_context_
        ->OnDidCommitNavigation(GetRenderFrameHost()->GetWeakDocumentPtr());
  }
  if (keep_alive_url_loader_factory_context_) {
    CHECK(!IsSameDocument());

    keep_alive_url_loader_factory_context_->OnDidCommitNavigation(this);
  }
  if (fetch_later_loader_factory_context_) {
    CHECK(!IsSameDocument());

    fetch_later_loader_factory_context_->OnDidCommitNavigation(this);
  }

  if (!pending_commit_metrics_.start_time.is_null()) {
    const bool is_for_mhtml = IsMhtmlMimeType(GetMimeType());
    base::UmaHistogramTimes(
        is_for_mhtml ? "Navigation.PendingCommit.Duration.MHTML"
                     : "Navigation.PendingCommit.Duration.Regular",
        base::TimeTicks::Now() - pending_commit_metrics_.start_time);
    const bool did_block_get_frame_host_for_navigation =
        pending_commit_metrics_.blocked_count > 0;
    base::UmaHistogramBoolean(
        is_for_mhtml
            ? "Navigation.PendingCommit.DidBlockGetFrameHostForNavigation.MHTML"
            : "Navigation.PendingCommit.DidBlockGetFrameHostForNavigation."
              "Regular",
        did_block_get_frame_host_for_navigation);
    if (did_block_get_frame_host_for_navigation) {
      base::UmaHistogramCounts100(
          is_for_mhtml ? "Navigation.PendingCommit.BlockedCount.MHTML"
                       : "Navigation.PendingCommit.BlockedCount.Regular",
          pending_commit_metrics_.blocked_count);
      base::UmaHistogramCounts100(
          is_for_mhtml ? "Navigation.PendingCommit.BlockedCommitCount.MHTML"
                       : "Navigation.PendingCommit.BlockedCommitCount.Regular",
          pending_commit_metrics_.blocked_commit_count);
    }
  }
  if (ignored_duplicate_navigation_count_ > 0) {
    base::UmaHistogramCounts100(
        "Navigation.DuplicateNavigationsIgnoredCountPerNavigation",
        ignored_duplicate_navigation_count_);
    ukm::builders::Navigation_DuplicateNavigationsIgnored builder(
        GetNextPageUkmSourceId());
    builder.SetIgnoredDuplicateNavigationCount(
        ukm::GetExponentialBucketMinForCounts1000(
            ignored_duplicate_navigation_count_));
    builder.Record(ukm::UkmRecorder::Get());
  }

  if (!IsSameDocument() && IsInOutermostMainFrame() &&
      params.url.SchemeIsHTTPOrHTTPS()) {
    const auto mode = GetBeforeUnloadExecutionMode();
    const char kBaseName[] =
        "Navigation.BeforeUnloadExecutionMode.IsInOutermostMainFrame";
    base::UmaHistogramEnumeration(kBaseName, mode);
    base::UmaHistogramEnumeration(
        base::StrCat(
            {kBaseName, IsSameOrigin() ? ".SameOrigin" : ".CrossOrigin"}),
        mode);
  }
  TRACE_EVENT_INSTANT(
      "navigation", "BeforeUnloadExecutionMode", "BeforeUnloadExecutionMode",
      BeforeUnloadExecutionModeToString(GetBeforeUnloadExecutionMode()));

  if (!activation_beacon_url_.is_empty()) {
    auto* manager =
        PreloadActivationReportManager::GetOrCreateForBrowserContext(
            GetWebContents()->GetBrowserContext());
    manager->ReportActivation(activation_beacon_url_, GetRenderFrameHost());
    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
        GetRenderFrameHost(),
        blink::mojom::WebFeature::kPrefetchAndPrerenderActivationBeacon);
  }

  // DO NOT ADD CODE after this.
  // UnblockPendingSubframeNavigationRequestsIfNeeded() resumes throttles, which
  // may cause the destruction of this NavigationRequest.
  UnblockPendingSubframeNavigationRequestsIfNeeded();
}

SiteInfo NavigationRequest::GetSiteInfoForCommonParamsURL() {
  UrlInfo url_info = GetUrlInfo();

  // TODO(alexmos): Using |starting_site_instance_|'s IsolationContext may not
  // be correct for cross-BrowsingInstance redirects.
  return SiteInfo::Create(starting_site_instance_->GetIsolationContext(),
                          url_info);
}

// TODO(zetamoo): Try to merge this function inside its callers.
void NavigationRequest::UpdateStateFollowingRedirect(
    const GURL& new_referrer_url) {
  // The navigation should not redirect to a "renderer debug" url. It should be
  // blocked in NavigationRequest::OnRequestRedirected or in
  // ResourceLoader::OnReceivedRedirect.
  // Note: the |common_params_->url| below is the post-redirect URL.
  // See https://crbug.com/728398.
  CHECK(!blink::IsRendererDebugURL(common_params_->url));

  // Re-generate the feature context to ensure that the runtime-enabled features
  // have the correct state values.
  runtime_feature_state_context_ = blink::RuntimeFeatureStateContext();

  // Update the navigation parameters.
  if (!(common_params_->transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT)) {
    sanitized_referrer_->url = new_referrer_url;
    sanitized_referrer_ =
        Referrer::SanitizeForRequest(common_params_->url, *sanitized_referrer_);
  }

  common_params_->referrer = sanitized_referrer_.Clone();

  was_redirected_ = true;
  redirect_chain_.push_back(common_params_->url);

  SetState(WILL_REDIRECT_REQUEST);
  processing_navigation_throttle_ = true;

#if BUILDFLAG(IS_ANDROID)
  navigation_handle_proxy_->DidRedirect();
#endif
}

void NavigationRequest::SetNavigationClient(
    mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client) {
  CHECK(from_begin_navigation_ ||
        common_params_->is_history_navigation_in_new_child_frame);
  CHECK(!request_navigation_client_);
  if (!navigation_client.is_valid())
    return;

  request_navigation_client_.reset();
  request_navigation_client_.Bind(std::move(navigation_client));

  // Binds the OnAbort callback
  HandleInterfaceDisconnection(request_navigation_client_);
}

bool NavigationRequest::NeedsUrlLoader() {
#if BUILDFLAG(IS_ANDROID)
  // If the navigation is for a PDF file, Chrome on Android will render it with
  // a Java NativePage object and the navigation will always be main frame. The
  // NativePage is responsible for reading the file and thus no URLLoader is
  // needed. If NativePage is not enabled for PDF, `IsPdf()` should never be
  // true.
  if (IsPdf()) {
    return false;
  }
#endif  // BUILDFLAG(IS_ANDROID)

  bool is_mhtml_subframe_loaded_from_archive =
      IsForMhtmlSubframe() &&
      // Unlike all other MHTML subframe URLs, data-url are loaded via the
      // URL, not from the MHTML archive. See https://crbug.com/969696.
      !common_params_->url.SchemeIs(url::kDataScheme);

  return IsURLHandledByNetworkStack(common_params_->url) && !IsSameDocument() &&
         !is_mhtml_subframe_loaded_from_archive;
}

void NavigationRequest::UpdateLocalNetworkAccessRequestPolicy() {
  // It is useless to update this state for same-document navigations as well
  // as pages served from the back-forward cache or prerendered pages.
  CHECK(!IsSameDocument());
  CHECK(!IsPageActivation());
  if (GetSocketAddress().address().IsValid() &&
      GetSocketAddress().address().IsZero()) {
    web_features_to_log_.push_back(
        blink::mojom::WebFeature::kPrivateNetworkAccessNullIpAddress);
  }

  ContentBrowserClient* client = GetContentClient()->browser();
  BrowserContext* context =
      frame_tree_node_->navigator().controller().GetBrowserContext();

  url::Origin origin = GetOriginToCommit().value();
  // TODO(crbug.com/452389539): Centralize these policy overrides.
  ContentBrowserClient::LocalNetworkAccessRequestPolicyOverride
      policy_override = client->ShouldOverrideLocalNetworkAccessRequestPolicy(
          context, origin);

  if (policy_override ==
      ContentBrowserClient::LocalNetworkAccessRequestPolicyOverride::
          kForceAllow) {
    local_network_access_request_policy_ =
        network::mojom::LocalNetworkAccessRequestPolicy::kAllow;
    return;
  }

  const PolicyContainerPolicies& policies =
      policy_container_builder_->FinalPolicies();

  // TODO(crbug.com/433300380): The lna_secure_context_overide check needs to be
  // done in all other policy derivation points. This boolean should probably be
  // put into PolicyContainerPolicies.
  local_network_access_request_policy_ = DeriveLocalNetworkAccessRequestPolicy(
      policies, LocalNetworkAccessRequestContext::kSubresource);

  if (policy_override ==
      ContentBrowserClient::LocalNetworkAccessRequestPolicyOverride::
          kBlockInsteadOfWarn) {
    local_network_access_request_policy_ =
        OverrideToBlockInsteadOfWarn(local_network_access_request_policy_);
  }

  if (policy_override ==
      ContentBrowserClient::LocalNetworkAccessRequestPolicyOverride::
          kWarnInsteadOfBlock) {
    local_network_access_request_policy_ =
        OverrideToWarnInsteadOfBlock(local_network_access_request_policy_);
  }
}

std::vector<blink::mojom::WebFeature>
NavigationRequest::TakeWebFeaturesToLog() {
  std::vector<blink::mojom::WebFeature> result;
  result.swap(web_features_to_log_);
  return result;
}

void NavigationRequest::set_keep_alive_url_loader_factory_context(
    base::WeakPtr<KeepAliveURLLoaderService::FactoryContext> factory_context) {
  if (did_set_keep_alive_url_loader_factory_context_for_testing_) {
    // A unit test set a fake context already. Use that instead of the passed in
    // context.
    CHECK_IS_TEST();
    CHECK(keep_alive_url_loader_factory_context_);
    CHECK_NE(keep_alive_url_loader_factory_context_.get(),
             factory_context.get());
    return;
  }
  CHECK(!keep_alive_url_loader_factory_context_);
  keep_alive_url_loader_factory_context_ = factory_context;
}

void NavigationRequest::set_fetch_later_loader_factory_context(
    base::WeakPtr<KeepAliveURLLoaderService::FactoryContext> factory_context) {
  if (did_set_fetch_later_url_loader_factory_context_for_testing_) {
    // A unit test set a fake context already. Use that instead of the passed in
    // context.
    CHECK_IS_TEST();
    CHECK(fetch_later_loader_factory_context_);
    CHECK_NE(fetch_later_loader_factory_context_.get(), factory_context.get());
    return;
  }
  fetch_later_loader_factory_context_ = factory_context;
}

void NavigationRequest::SetKeepAliveURLLoaderFactoryContextForTesting(
    base::WeakPtr<KeepAliveURLLoaderService::FactoryContext> factory_context) {
  CHECK(!keep_alive_url_loader_factory_context_);
  did_set_keep_alive_url_loader_factory_context_for_testing_ = true;
  keep_alive_url_loader_factory_context_ = factory_context;
  CHECK(keep_alive_url_loader_factory_context_);
}

void NavigationRequest::SetFetchLaterLoaderFactoryContextForTesting(
    base::WeakPtr<KeepAliveURLLoaderService::FactoryContext> factory_context) {
  CHECK(!fetch_later_loader_factory_context_);
  did_set_fetch_later_url_loader_factory_context_for_testing_ = true;
  fetch_later_loader_factory_context_ = factory_context;
  CHECK(fetch_later_loader_factory_context_);
}

void NavigationRequest::ReadyToCommitNavigation(bool is_error) {
  TRACE_EVENT("navigation", "NavigationRequest::ReadyToCommitNavigation",
              perfetto::Flow::FromPointer(this));
  EnterChildTraceEvent("ReadyToCommitNavigation", this);

  // We may come back to here asynchronously, and the renderer may be destroyed
  // in the meantime. Renderer-initiated navigations listen to mojo
  // disconnection from the renderer NavigationClient; but browser-initiated
  // navigations do not, so we must look explicitly. We should not proceed and
  // claim "ReadyToCommitNavigation" to the delegate if the renderer is gone.
  if (!GetRenderFrameHost()->IsRenderFrameLive()) {
    OnNavigationClientDisconnected(0, "");
    // DO NOT ADD CODE AFTER THIS, as the NavigationHandle has been deleted
    // by the previous call.
    return;
  }

  // Note: This marks the RenderFrameHost as loading. This is important to
  // ensure that FrameTreeNode::IsLoading() still returns correct result for
  // delegate and observer callbacks. Otherwise, there would be a period of time
  // where the FrameTreeNode has no NavigationRequest, yet the
  // RenderFrameHostImpl is not marked as loading yet, causing
  // FrameTreeNode::IsLoading() to incorrectly return false.
  frame_tree_node_->TransferNavigationRequestOwnership(GetRenderFrameHost());

  // When a speculative RenderFrameHost reaches ReadyToCommitNavigation, the
  // browser process has asked the renderer to commit the navigation and is
  // waiting for confirmation of the commit. Update the LifecycleStateImpl to
  // kPendingCommit as RenderFrameHost isn't considered speculative anymore and
  // was chosen to commit as this navigation's final RenderFrameHost.
  if (GetRenderFrameHost()->lifecycle_state() ==
      RenderFrameHostImpl::LifecycleStateImpl::kSpeculative) {
    // Only cross-RenderFrameHost navigations create speculative
    // RenderFrameHosts whereas SameDocument, BackForwardCache and
    // PrerenderedActivation navigations don't.
    CHECK(!IsSameDocument() && !IsPageActivation());
    GetRenderFrameHost()->SetLifecycleState(
        RenderFrameHostImpl::LifecycleStateImpl::kPendingCommit);
    pending_commit_metrics_.start_time = base::TimeTicks::Now();
  }

  // Reset the source location information, which is not needed anymore. This
  // avoids leaking cross-origin data to another process in case the navigation
  // doesn't commit in the same process as the document that initiated it.
  common_params_->source_location = network::mojom::SourceLocation::New();

  SetState(READY_TO_COMMIT);
  ready_to_commit_time_ = base::TimeTicks::Now();
  RestartCommitTimeout();

  if (!IsSameDocument() && !IsPageActivation())
    UpdateLocalNetworkAccessRequestPolicy();

  RenderFrameHostImpl* previous_render_frame_host =
      frame_tree_node_->current_frame_host();

  // Record metrics for the time it takes to get to this state from the
  // beginning of the navigation.
  if (!IsSameDocument() && !is_error) {
    is_same_process_ =
        GetRenderFrameHost()->GetProcess()->GetDeprecatedID() ==
        previous_render_frame_host->GetProcess()->GetDeprecatedID();

    RecordReadyToCommitMetrics(
        previous_render_frame_host, GetRenderFrameHost(), *common_params_.get(),
        ready_to_commit_time_, origin_agent_cluster_end_result_,
        did_receive_early_hints_before_cross_origin_redirect_);
  }

  std::optional<url::Origin> origin_to_commit = GetOriginToCommit();
  same_origin_ = (previous_render_frame_host->GetLastCommittedOrigin() ==
                  origin_to_commit);

  SetExpectedProcess(GetRenderFrameHost()->GetProcess());

  commit_params_->is_load_data_with_base_url = IsLoadDataWithBaseURL();
  commit_params_->origin_to_commit = origin_to_commit.value();

  if (!IsSameDocument()) {
    // TODO(crbug.com/504574017): Remove the WeakPtr check once Android
    // WebView can avoid the synchronous deletion case.
#if BUILDFLAG(IS_ANDROID)
    base::WeakPtr<NavigationRequest> this_ptr(weak_factory_.GetWeakPtr());
#else
    CHECK(is_safe_to_delete_);
    base::AutoReset<bool> resetter(&is_safe_to_delete_, false);
#endif
    GetDelegate()->ReadyToCommitNavigation(this);
#if BUILDFLAG(IS_ANDROID)
    if (!this_ptr) {
      // The NavigationRequest was deleted during the above call.
      return;
    }
#endif
  }

#if !BUILDFLAG(IS_ANDROID)
  if (IsIsolatedContext(GetRenderFrameHost()->GetProcess()) ||
      (origin_to_commit &&
       GetContentClient()->browser()->GetDirectSocketsDelegate() &&
       GetContentClient()
           ->browser()
           ->GetDirectSocketsDelegate()
           ->AreDirectSocketsAllowed(GetRenderFrameHost()->GetBrowserContext(),
                                     *origin_to_commit))) {
    GetMutableRuntimeFeatureStateContext().SetDirectSocketsEnabled(
        base::FeatureList::IsEnabled(blink::features::kDirectSockets));
  }
#endif

  // View-source URLs can't be prerendered or loaded in a fenced frame.
  if (IsInPrimaryMainFrame()) {
    NavigationEntry* entry = GetNavigationEntry();
    if (entry && entry->IsViewSourceMode()) {
      // Put the renderer in view source mode.
      GetRenderFrameHost()->GetAssociatedLocalFrame()->EnableViewSourceMode();
    }
  }

  // For fenced frames, update the mapped URL to be the URL from navigation
  // commit (after redirects), because we want future same-origin checks to be
  // performed with respect to the first origin committed in the fenced frame.
  if (is_embedder_initiated_fenced_frame_navigation_) {
    // In certain circumstances, the FencedFrameProperties will not have a
    // mapped url.
    // * The initial about:blank navigation in a fenced frame.
    // In those cases, we skip this step.
    if (fenced_frame_properties_.has_value() &&
        fenced_frame_properties_->mapped_url().has_value()) {
      fenced_frame_properties_->UpdateMappedURL(GetURL());
    }

    // For fenced frames with flexible permissions, pass in information needed
    // to build a replica of the embedder's permissions policies. This does not
    // happen for URN iframes as they can get their embedder's permissions
    // policies directly in the renderer.
    if (base::FeatureList::IsEnabled(
            blink::features::kFencedFramesLocalUnpartitionedDataAccess) &&
        GetNavigatingFrameType() == FrameType::kFencedFrameRoot &&
        fenced_frame_properties_->effective_enabled_permissions().size() ==
            0u) {
      fenced_frame_properties_->UpdateParentParsedPermissionsPolicy(
          GetParentFrameOrOuterDocument()->GetPermissionsPolicy(),
          GetParentFrameOrOuterDocument()->GetLastCommittedOrigin());
    }
  }

  if (ready_to_commit_callback_for_testing_)
    std::move(ready_to_commit_callback_for_testing_).Run();
}

bool NavigationRequest::IsWaitingToCommit() {
  return state_ == READY_TO_COMMIT;
}

bool NavigationRequest::WasResourceHintsReceived() {
  CHECK_GE(state_, WILL_PROCESS_RESPONSE)
      << "Should only be called after the response started";
  return was_resource_hints_received_;
}

bool NavigationRequest::IsPdf() {
  return embedder_isolation_info_.is_pdf();
}

bool NavigationRequest::IsLoadDataWithBaseURL() const {
  // A navigation is a loadDataWithBaseURL navigation if it's a successful
  // primary main frame navigation to a data: URL, and its base URL is valid.
  return IsInPrimaryMainFrame() && !DidEncounterError() &&
         common_params_->url.SchemeIs(url::kDataScheme) &&
         common_params_->base_url_for_data_url.is_valid();
}

url::Origin NavigationRequest::GetTentativeOriginAtRequestTime() {
  CHECK_LT(state_, WILL_PROCESS_RESPONSE);
  return GetOriginForURLLoaderFactoryBeforeResponse(
      commit_params_->frame_policy.sandbox_flags);
}

std::optional<url::Origin> NavigationRequest::GetOriginToCommit() {
  return GetOriginForURLLoaderFactoryAfterResponse();
}

url::Origin NavigationRequest::GetOriginForURLLoaderFactoryBeforeResponse(
    network::mojom::WebSandboxFlags sandbox_flags) {
  // Calculate an approximation of the origin. The sandbox/csp are ignored.
  url::Origin origin = GetOriginForURLLoaderFactoryUnchecked();

  // Apply sandbox flags.
  // See https://html.spec.whatwg.org/#sandboxed-origin-browsing-context-flag
  // ```
  // The 'sandboxed origin browsing context flag' forces content into a unique
  // origin, thus preventing it from accessing other content from the same
  // origin.
  //
  // This flag also prevents script from reading from or writing to the
  // document.cookie IDL attribute, and blocks access to localStorage.
  // ```
  bool use_opaque_origin =
      (sandbox_flags & network::mojom::WebSandboxFlags::kOrigin) ==
      network::mojom::WebSandboxFlags::kOrigin;
  if (use_opaque_origin) {
    origin = origin.DeriveNewOpaqueOrigin();
  }

  return origin;
}

// TODO(crbug.com/40065692): Remove.
// Determine the relationship between the initiator and the current frame.
// `same`: they are the same frame.
// `ancestor`: the initiator is the ancestor of the navigating frame.
// `descendant`: the initiator is the descendant of the navigating frame.
// `other`: any other scenarios.
std::string_view DetermineInitiatorRelationship(
    RenderFrameHost* initiator_frame,
    RenderFrameHost* current_frame) {
  if (!current_frame || !initiator_frame) {
    return "other";
  }

  if (current_frame == initiator_frame) {
    return "same";
  }

  RenderFrameHost* rfh = current_frame;
  while (rfh) {
    rfh = rfh->GetParent();
    if (rfh == initiator_frame) {
      return "ancestor";
    }
  }

  rfh = initiator_frame;
  while (rfh) {
    rfh = rfh->GetParent();
    if (rfh == current_frame) {
      return "descendant";
    }
  }

  return "other";
}

std::optional<url::Origin>
NavigationRequest::GetOriginForURLLoaderFactoryAfterResponse() {
  // The origin to commit is not known until we get the final network response.
  CHECK_GE(state_, WILL_PROCESS_RESPONSE);

  // Downloads and/or 204 responses don't commit anything - there is no frame to
  // commit in (and therefore there is no origin that will get committed and we
  // indicate this by returning `nullopt`).
  if (!response_should_be_rendered_) {
    return std::nullopt;
  }

  if (IsSameDocument() || IsPageActivation()) {
    CHECK(HasRenderFrameHost());
    return GetRenderFrameHost()->GetLastCommittedOrigin();
  }

  url::Origin origin =
      GetOriginForURLLoaderFactoryBeforeResponse(SandboxFlagsToCommit());

  SCOPED_CRASH_KEY_BOOL("Bug1454273", "is_in_main_frame", IsInMainFrame());
  SCOPED_CRASH_KEY_STRING256(
      "Bug1454273", "current_origin",
      frame_tree_node_->current_origin().GetDebugString());
  RenderFrameHostImpl* parent = frame_tree_node_->parent();
  SCOPED_CRASH_KEY_STRING256(
      "Bug1454273", "parent_origin",
      parent ? parent->GetLastCommittedOrigin().GetDebugString() : "");
  // `outer_doc` is only set when `parent` is null.
  RenderFrameHostImpl* outer_doc =
      parent ? nullptr : GetParentFrameOrOuterDocument();
  SCOPED_CRASH_KEY_STRING256(
      "Bug1454273", "outer_doc_origin",
      outer_doc ? outer_doc->GetLastCommittedOrigin().GetDebugString() : "");
  // `embedder` is only set when both `parent` and `outer_doc` are null.
  RenderFrameHostImpl* embedder =
      (parent || outer_doc)
          ? nullptr
          : frame_tree_node()->GetParentOrOuterDocumentOrEmbedder();
  SCOPED_CRASH_KEY_STRING256(
      "Bug1454273", "embedder_origin",
      embedder ? embedder->GetLastCommittedOrigin().GetDebugString() : "");

  RenderFrameHost* initiator_rfh = GetInitiatorDocumentRenderFrameHost();
  SCOPED_CRASH_KEY_STRING256(
      "Bug1454273", "initiator_origin",
      initiator_rfh ? initiator_rfh->GetLastCommittedOrigin().GetDebugString()
                    : "");
  SCOPED_CRASH_KEY_STRING32(
      "Bug1454273", "initiator_relationship",
      DetermineInitiatorRelationship(initiator_rfh,
                                     frame_tree_node_->current_frame_host()));

  // MHTML documents should commit as an opaque origin. They should not be able
  // to make network request on behalf of the real origin.
  // TODO(crbug.com/370979008): Migrate to CHECK.
  DUMP_WILL_BE_CHECK(!IsMhtmlOrSubframe() || origin.opaque());

  // If the target of this navigation will be rendered in a RenderFrameHost,
  // then verify that the chosen origin is allowed to be accessed from that
  // process.
  //
  // Note that GetRenderFrameHost() only allows retrieving the RenderFrameHost
  // once it has been set for this navigation. This happens either at
  // WillProcessResponse time for regular navigations or at WillFailRequest time
  // for error pages.
  //
  // There are some exceptions where this check must be skipped:
  // * Some error pages may commit in an error process that allows many origins.
  // * MHTML iframes can load documents from any origin, no matter the current
  //   policy of the process being used. This is because the content is loaded
  //   from the MHTML archive within the process. There are no data loaded from
  //   the network.
  //
  // Note: This is a security-critical check that needs to be a CHECK rather
  // than a DCHECK, and it should never be reverted, even if there are crashes
  // in the wild.
  if (HasRenderFrameHost() &&
      !GetRenderFrameHost()->ShouldBypassSecurityChecksForErrorPage(this) &&
      !IsForMhtmlSubframe()) {
    int process_id = GetRenderFrameHost()->GetProcess()->GetDeprecatedID();
    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
    CHECK(policy->CanAccessOrigin(
        process_id, origin,
        ChildProcessSecurityPolicyImpl::AccessType::kCanCommitNewOrigin));
  }

  return origin;
}

void NavigationRequest::WriteIntoTrace(
    perfetto::TracedProto<TraceProto> ctx) const {
  ctx->set_navigation_id(navigation_id_);
  ctx->set_has_committed(HasCommitted());
  ctx->set_is_error_page(IsErrorPage());
  ctx.Set(TraceProto::kFrameTreeNode, frame_tree_node());
  if (state_ >= WILL_PROCESS_RESPONSE)
    ctx.Set(TraceProto::kRenderFrameHost, GetRenderFrameHost());

  perfetto::TracedDictionary dict = std::move(ctx).AddDebugAnnotations();
  dict.Add("url", common_params_->url);
  dict.Add("net_error", net_error_);
  dict.Add("browser_initiated", commit_params_->is_browser_initiated);
  dict.Add("from_begin_navigation", from_begin_navigation_);
  dict.Add("is_synchronous_renderer_commit", is_synchronous_renderer_commit_);
  dict.Add("reload_type", reload_type_);
  dict.Add("state", state_);
  dict.Add("navigation_type", common_params_->navigation_type);

  if (IsServedFromBackForwardCache()) {
    dict.Add("served_from_bfcache", true);
    dict.Add("rfh_restored_from_bfcache",
             GetRenderFrameHostRestoredFromBackForwardCache());
  }

  if (activating_prerender_host_id_.has_value()) {
    dict.Add("prerender_host_id", activating_prerender_host_id_.value());
  }
}

bool NavigationRequest::SetNavigationTimeout(base::TimeDelta timeout) {
  // Setting a navigation-level timeout isn't implemented yet for other states,
  // as the `loader_` must already be created. This means that callers like
  // NavigationThrottles should only call SetNavigationTimeout from
  // WillRedirectRequest(). If other use cases come up that need to set this
  // timeout at other points in the NavigationRequest lifecycle, one possible
  // solution could be to have the NavigationRequest hold the timeout value
  // temporarily until it creates the `loader_` in OnStartChecksCompleted().
  CHECK_EQ(state_, WILL_REDIRECT_REQUEST);
  if (loader_)
    return loader_->SetNavigationTimeout(timeout);
  return false;
}

void NavigationRequest::CancelNavigationTimeout() {
  if (loader_) {
    loader_->CancelNavigationTimeout();
  }
}

void NavigationRequest::SetAllowCookiesFromBrowser(
    bool allow_cookies_from_browser) {
  allow_cookies_from_browser_ = allow_cookies_from_browser;
}

void NavigationRequest::DidIgnoreDuplicateNavigation() {
  ignored_duplicate_navigation_count_++;
}

size_t NavigationRequest::GetIgnoredDuplicateNavigationCount() const {
  return ignored_duplicate_navigation_count_;
}

void NavigationRequest::GetResponseBody(ResponseBodyCallback callback) {
  CHECK_GE(state_, WILL_PROCESS_RESPONSE)
      << "The response body should only be requested after the response body "
         "data pipe is received from the network stack.";
  CHECK(processing_navigation_throttle_ || IsDeferred());
  CHECK(response_body_callback_.is_null());
  CHECK(callback);
  response_body_callback_ = std::move(callback);
  was_get_response_body_called_ = true;

  CHECK(!response_body_watcher_);
  response_body_watcher_ = std::make_unique<mojo::SimpleWatcher>(
      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL,
      base::SequencedTaskRunner::GetCurrentDefault());
  response_body_watcher_->Watch(
      response_body(),
      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
      base::BindRepeating(&NavigationRequest::OnResponseBodyReady,
                          weak_factory_.GetWeakPtr()));
  response_body_watcher_->ArmOrNotify();
}

void NavigationRequest::RenderProcessBlockedStateChanged(bool blocked) {
  if (blocked)
    StopCommitTimeout();
  else
    RestartCommitTimeout();
}

void NavigationRequest::StopCommitTimeout() {
  commit_timeout_timer_.Stop();
  render_process_blocked_state_changed_subscription_ = {};
  GetRenderFrameHost()->GetRenderWidgetHost()->RendererIsResponsive();
}

void NavigationRequest::RestartCommitTimeout() {
  commit_timeout_timer_.Stop();
  if (state_ >= DID_COMMIT)
    return;

  RenderProcessHost* renderer_host =
      GetRenderFrameHost()->GetRenderWidgetHost()->GetProcess();
  if (!render_process_blocked_state_changed_subscription_) {
    render_process_blocked_state_changed_subscription_ =
        renderer_host->RegisterBlockStateChangedCallback(base::BindRepeating(
            &NavigationRequest::RenderProcessBlockedStateChanged,
            base::Unretained(this)));
  }
  if (!renderer_host->IsBlocked()) {
    commit_timeout_timer_.Start(
        FROM_HERE, g_commit_timeout,
        base::BindOnce(&NavigationRequest::OnCommitTimeout,
                       weak_factory_.GetWeakPtr()));
  }
}

void NavigationRequest::OnCommitTimeout() {
  CHECK_EQ(READY_TO_COMMIT, state_);
  render_process_blocked_state_changed_subscription_ = {};

  GetRenderFrameHost()->GetRenderWidgetHost()->RendererIsUnresponsive(
      RenderWidgetHostImpl::RendererIsUnresponsiveReason::
          kNavigationRequestCommitTimeout,
      base::BindRepeating(&NavigationRequest::RestartCommitTimeout,
                          weak_factory_.GetWeakPtr()));
}

// static
void NavigationRequest::SetCommitTimeoutForTesting(
    const base::TimeDelta& timeout) {
  if (timeout.is_zero())
    g_commit_timeout = kDefaultCommitTimeout;
  else
    g_commit_timeout = timeout;
}

void NavigationRequest::SetPrerenderActivationNavigationState(
    std::unique_ptr<NavigationEntryImpl> prerender_navigation_entry,
    const blink::mojom::FrameReplicationState& replication_state) {
  CHECK(IsPrerenderedPageActivation());
  if (!prerender_navigation_state_) {
    prerender_navigation_state_.emplace();
  }
  prerender_navigation_state_->prerender_navigation_entry =
      std::move(prerender_navigation_entry);

  // Store the replication state of the prerender main frame to copy the
  // necessary parameters from it during activation commit and compare with the
  // final replication state after activation to ensure it hasn't changed.
  // TODO(crbug.com/40192974): This will need to be removed when the
  // Browsing Instance Frame State is implemented.
  prerender_navigation_state_->prerender_main_frame_replication_state =
      replication_state;
}

void NavigationRequest::RemoveRequestHeader(std::string_view header_name) {
  CHECK(state_ == WILL_REDIRECT_REQUEST);
  headers_update_params_.removed_headers.emplace_back(header_name);
}

void NavigationRequest::SetRequestHeader(std::string_view header_name,
                                         std::string_view header_value) {
  // TODO(523109612): CHECK-exclusion: Convert to a CHECK once we are confident
  // it won't be triggered.
  DCHECK(state_ == WILL_START_REQUEST || state_ == WILL_REDIRECT_REQUEST);
  headers_update_params_.modified_headers.SetHeader(header_name, header_value);
}

void NavigationRequest::SetLCPPNavigationHint(
    blink::mojom::LCPCriticalPathPredictorNavigationTimeHintPtr hint) {
  CHECK(WILL_START_REQUEST == state_ || WILL_REDIRECT_REQUEST == state_)
      << state_;
  commit_params_->lcpp_hint = std::move(hint);
}

const blink::mojom::LCPCriticalPathPredictorNavigationTimeHintPtr&
NavigationRequest::GetLCPPNavigationHint() {
  return commit_params_->lcpp_hint;
}

const net::HttpResponseHeaders* NavigationRequest::GetResponseHeaders() {
  return response_head_.get() ? response_head_->headers.get() : nullptr;
}

const network::mojom::DeclarativePerformanceObserverPolicy*
NavigationRequest::GetDeclarativePerformanceObserverPolicy() {
  if (!response_head_.get() || !response_head_->parsed_headers ||
      !response_head_->parsed_headers
           ->declarative_performance_observer_policy) {
    return nullptr;
  }
  if (!base::FeatureList::IsEnabled(
          blink::features::kDeclarativePerformanceObserver)) {
    return nullptr;
  }

  CHECK(response_head_->headers);
  const bool is_enabled_by_origin_trial =
      blink::TrialTokenValidator().RequestEnablesFeature(
          GetURL(), response_head_->headers.get(),
          "DeclarativePerformanceObserver", base::Time::Now());
  std::optional<bool> override_state = base::FeatureList::GetStateIfOverridden(
      blink::features::kDeclarativePerformanceObserver);
  const bool is_enabled_by_flag_override =
      override_state.has_value() && override_state.value();
  if (!is_enabled_by_origin_trial && !is_enabled_by_flag_override) {
    return nullptr;
  }

  auto* policy = response_head_->parsed_headers
                     ->declarative_performance_observer_policy.get();
  if (!policy) {
    return nullptr;
  }

  if (!blink::features::
           kDeclarativePerformanceObserverSupportCaptureEarlyFailures.Get()) {
    policy->capture_early_failures = false;
  }
  return policy;
}

mojom::DidCommitProvisionalLoadParamsPtr
NavigationRequest::MakeDidCommitProvisionalLoadParamsForActivation() {
  // Use the DidCommitProvisionalLoadParams last used to commit the frame being
  // restored as a starting point.
  mojom::DidCommitProvisionalLoadParamsPtr params =
      GetRenderFrameHost()->GetPage().TakeLastCommitParams();

  // Params must have been set when the RFH being restored from the cache last
  // navigated.
  CHECK(params);

  if (IsPrerenderedPageActivation()) {
    CHECK(!prerender_navigation_utils::IsDisallowedHttpResponseCode(
        params->http_status_code));
  } else {
    CHECK_EQ(params->http_status_code, net::HTTP_OK);
  }
  CHECK_EQ(params->url_is_unreachable, false);

  CHECK_EQ(params->post_id, -1);
  params->navigation_token = commit_params().navigation_token;
  // TODO(524700727): CHECK-exclusion: Convert to a CHECK once we are confident
  // it won't be triggered.
  DCHECK_EQ(params->url, common_params().url);
  params->should_update_history = true;
  // TODO(https://crbug.com/526542699): CHECK-exclusion: Convert to CHECK once
  // we are sure this isn't hit.
  DCHECK_EQ(params->method, common_params().method);
  params->item_sequence_number = frame_entry_item_sequence_number();
  params->document_sequence_number = frame_entry_document_sequence_number();
  params->transition = ui::PageTransitionFromInt(common_params().transition);
  params->history_list_was_cleared = false;
  params->request_id = GetGlobalRequestID().request_id;

  return params;
}

mojom::DidCommitProvisionalLoadParamsPtr
NavigationRequest::MakeDidCommitProvisionalLoadParamsForBFCacheRestore() {
  // Start with the provisional load parameters shared between all page
  // activation types.
  mojom::DidCommitProvisionalLoadParamsPtr params =
      MakeDidCommitProvisionalLoadParamsForActivation();

  // Add bfcache-specific provisional load params:
  params->did_create_new_entry = false;
  params->page_state =
      blink::PageState::CreateFromEncodedData(commit_params().page_state);
  return params;
}

mojom::DidCommitProvisionalLoadParamsPtr
NavigationRequest::MakeDidCommitProvisionalLoadParamsForPrerenderActivation() {
  CHECK(IsPrerenderedPageActivation());

  // Start with the provisional load parameters shared between all page
  // activation types.
  mojom::DidCommitProvisionalLoadParamsPtr params =
      MakeDidCommitProvisionalLoadParamsForActivation();
  // TODO(crbug.com/40169536): Investigate when a new entry should
  // replace an old one when prerendering a page.
  params->did_create_new_entry = true;

  FrameNavigationEntry* frame_entry =
      prerender_navigation_state_->prerender_navigation_entry->GetFrameEntry(
          frame_tree_node());

  // Prerendering already has a navigation entry which has correct PageState.
  // Set params->page_state accordingly to ensure that CHECKs expecting them to
  // match are happy.
  // Note: |params| are using last commit params as a basis (via
  // TakeLastCommitParams call), which have a page state from the last commit,
  // but the page state might have been updated since the last commit.
  params->page_state = frame_entry->page_state();

  if (base::FeatureList::IsEnabled(kPrerenderFixSequenceNumbers)) {
    // These sequence numbers should also be copied from the prerendering
    // navigation entry.
    CHECK_NE(frame_entry->item_sequence_number(), -1);
    CHECK_NE(frame_entry->document_sequence_number(), -1);
    params->item_sequence_number = frame_entry->item_sequence_number();
    params->document_sequence_number = frame_entry->document_sequence_number();
  }

  // insecure_request_policy field of the replication state is set during the
  // navigation commit based on DidCommitProvisionalLoadParams. As prerendering
  // activates existing page, copy its main frame replication state to ensure
  // that the effective replication state doesn't change after activation.
  // TODO(crbug.com/40192974): replication state should belong to the Browsing
  // Instance Frame State.
  params->insecure_request_policy =
      prerender_navigation_state_->prerender_main_frame_replication_state
          .insecure_request_policy;
  return params;
}

bool NavigationRequest::IsExternalProtocol() {
  return !GetContentClient()->browser()->IsHandledURL(common_params_->url);
}

bool NavigationRequest::IsSignedExchangeInnerResponse() {
  return response() && response()->is_signed_exchange_inner_response;
}

net::IPEndPoint NavigationRequest::GetSocketAddress() {
  // TODO(523173951): CHECK-exclusion: Convert to a CHECK once we are confident
  // it won't be triggered.
  DCHECK_GE(state_, WILL_PROCESS_RESPONSE);
  return response() ? response()->remote_endpoint : net::IPEndPoint();
}

bool NavigationRequest::HasCommitted() const {
  return state_ == DID_COMMIT || state_ == DID_COMMIT_ERROR_PAGE;
}

bool NavigationRequest::IsErrorPage() const {
  return state_ == DID_COMMIT_ERROR_PAGE;
}

bool NavigationRequest::DidEncounterError() const {
  return net_error_ != net::OK;
}

net::HttpConnectionInfo NavigationRequest::GetConnectionInfo() {
  return response() ? response()->connection_info : net::HttpConnectionInfo();
}

bool NavigationRequest::IsInMainFrame() const {
  return frame_tree_node()->IsMainFrame();
}

RenderFrameHostImpl* NavigationRequest::GetParentFrame() {
  return IsInMainFrame() ? nullptr : frame_tree_node()->parent();
}

RenderFrameHostImpl* NavigationRequest::GetParentFrameOrOuterDocument() {
  return frame_tree_node()->GetParentOrOuterDocument();
}

bool NavigationRequest::IsInPrimaryMainFrame() const {
  return GetNavigatingFrameType() == FrameType::kPrimaryMainFrame;
}

const std::optional<base::UnguessableToken>&
NavigationRequest::GetScriptToolInvocationId() const {
  static const std::optional<base::UnguessableToken> empty_id = std::nullopt;
  if (!begin_params_) {
    return empty_id;
  }
  CHECK(!begin_params_->script_tool_invocation_id ||
        !commit_params_->is_browser_initiated);
  return begin_params_->script_tool_invocation_id;
}

bool NavigationRequest::IsInOutermostMainFrame() const {
  switch (GetNavigatingFrameType()) {
    case FrameType::kPrimaryMainFrame:
    case FrameType::kPrerenderMainFrame:
    case FrameType::kGuestMainFrame:
      return true;
    case FrameType::kSubframe:
    case FrameType::kFencedFrameRoot:
      return false;
  }
}

bool NavigationRequest::IsInPrerenderedMainFrame() const {
  return GetNavigatingFrameType() == FrameType::kPrerenderMainFrame;
}

bool NavigationRequest::IsPrerenderedPageActivation() const {
  CHECK(activating_prerender_host_id_.has_value());
  return !activating_prerender_host_id_.value().is_null();
}

bool NavigationRequest::IsInFencedFrameTree() const {
  return frame_tree_node()->IsInFencedFrameTree();
}

bool NavigationRequest::IsGuestViewMainFrame() const {
  return GetNavigatingFrameType() == content::FrameType::kGuestMainFrame;
}

FrameType NavigationRequest::GetNavigatingFrameType() const {
  return frame_tree_node()->GetFrameType();
}

FrameTreeNodeId NavigationRequest::GetFrameTreeNodeId() {
  return frame_tree_node()->frame_tree_node_id();
}

bool NavigationRequest::WasResponseCached() {
  return response() && response()->was_fetched_via_cache;
}

bool NavigationRequest::NetworkAccessed() {
  return response() && response()->network_accessed;
}

bool NavigationRequest::HasPrefetchedAlternativeSubresourceSignedExchange() {
  return !commit_params_->prefetched_signed_exchanges.empty();
}

int64_t NavigationRequest::GetNavigationId() const {
  return navigation_id_;
}

void NavigationRequest::SetBypassRedirectChecksForNextRedirect(bool bypass) {
  bypass_redirect_checks_for_next_redirect_ = bypass;
}

bool NavigationRequest::ConsumeBypassRedirectChecksForNextRedirect() {
  bool bypass = bypass_redirect_checks_for_next_redirect_;
  bypass_redirect_checks_for_next_redirect_ = false;
  return bypass;
}

void NavigationHandle::SetBypassRedirectChecksForNextRedirect(
    FrameTreeNodeId frame_tree_node_id,
    int64_t navigation_id) {
  FrameTreeNode* frame_tree_node =
      FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (frame_tree_node && frame_tree_node->navigation_request() &&
      frame_tree_node->navigation_request()->GetNavigationId() ==
          navigation_id) {
    frame_tree_node->navigation_request()
        ->SetBypassRedirectChecksForNextRedirect(true);
  }
}

ukm::SourceId NavigationRequest::GetNextPageUkmSourceId() {
  // If the navigation is restoring from back-forward cache, the UKM id
  // will get restored, too.
  if (IsServedFromBackForwardCache()) {
    return GetRenderFrameHostRestoredFromBackForwardCache()
        ->GetPageUkmSourceId();
  }

  // If this is the same document or a subframe navigation (i.e. iframe or
  // fenced frame), the UKM id will not change from it.
  if (IsSameDocument() || !IsInMainFrame() || IsInFencedFrameTree())
    return previous_page_ukm_source_id_;

  return ukm::ConvertToSourceId(navigation_id_,
                                ukm::SourceIdObj::Type::NAVIGATION_ID);
}

NavigationHandle::BeforeUnloadExecutionMode
NavigationRequest::GetBeforeUnloadExecutionMode() const {
  return before_unload_execution_mode_;
}

const GURL& NavigationRequest::GetURL() {
  return common_params().url;
}

SiteInstanceImpl* NavigationRequest::GetStartingSiteInstance() {
  return starting_site_instance_.get();
}

SiteInstanceImpl* NavigationRequest::GetSourceSiteInstance() {
  return source_site_instance_.get();
}

bool NavigationRequest::IsRendererInitiated() {
  return !commit_params_->is_browser_initiated;
}

bool NavigationRequest::IsSameOrigin() {
  CHECK(HasCommitted());
  return same_origin_;
}

bool NavigationRequest::WasServerRedirect() {
  return was_redirected_;
}

const std::vector<GURL>& NavigationRequest::GetRedirectChain() {
  return redirect_chain_;
}

base::TimeTicks NavigationRequest::NavigationStart() {
  return common_params().navigation_start;
}

base::TimeTicks NavigationRequest::NavigationInputStart() {
  return common_params().input_start;
}

const NavigationHandleTiming& NavigationRequest::GetNavigationHandleTiming() {
  return navigation_handle_timing_;
}

bool NavigationRequest::IsPost() {
  return common_params().method == "POST";
}

std::string NavigationRequest::GetRequestMethod() {
  return request_method_;
}

const blink::mojom::Referrer& NavigationRequest::GetReferrer() {
  return *sanitized_referrer_;
}

void NavigationRequest::SetReferrer(blink::mojom::ReferrerPtr referrer) {
  CHECK(state_ == WILL_START_REQUEST || state_ == WILL_REDIRECT_REQUEST);
  sanitized_referrer_ =
      Referrer::SanitizeForRequest(common_params_->url, *referrer);
  common_params_->referrer = sanitized_referrer_.Clone();
}

bool NavigationRequest::HasUserGesture() {
  return common_params().has_possibly_filtered_user_gesture;
}

bool NavigationRequest::StartedWithTransientActivation() {
  return begin_params_->started_with_transient_activation;
}

bool NavigationRequest::StartedByAd() {
  return begin_params_->started_by_ad;
}

ui::PageTransition NavigationRequest::GetPageTransition() {
  return ui::PageTransitionFromInt(common_params().transition);
}

NavigationUIData* NavigationRequest::GetNavigationUIData() {
  return navigation_ui_data_.get();
}

net::Error NavigationRequest::GetNetErrorCode() {
  return net_error_;
}

int NavigationRequest::GetNetExtendedErrorCode() {
  return extended_error_code_;
}

std::optional<ErrorNavigationTrigger>
NavigationRequest::GetErrorNavigationTrigger() {
  return error_navigation_trigger_;
}

// The RenderFrameHost that will commit the navigation or an error page.
// This is computed when the response is received, or when the navigation
// fails and error page should be displayed.
RenderFrameHostImpl* NavigationRequest::GetRenderFrameHost() const {
  // Only allow the RenderFrameHost to be retrieved once it has been set for
  // this navigation. This will happens either at WillProcessResponse time for
  // regular navigations or at WillFailRequest time for error pages.
  // NavigationRequests created for synchronous renderer commits (see
  // documentation for |is_synchronous_renderer_commit_|) have a
  // RenderFrameHost available from the start.
  if (!is_synchronous_renderer_commit()) {
    CHECK_GE(state_, WILL_PROCESS_RESPONSE)
        << "This accessor should only be called after a RenderFrameHost has "
           "been picked for this navigation.";
  }
  static_assert(WILL_FAIL_REQUEST > WILL_PROCESS_RESPONSE,
                "WillFailRequest state should come after WillProcessResponse");
  if (HasRenderFrameHost()) {
    return &*render_frame_host_.value();
  }
  return nullptr;
}

NavigationRequest::AssociatedRenderFrameHostType
NavigationRequest::GetAssociatedRFHType() const {
  // `associated_rfh_type_` might not be accurate after the navigation had
  // moved to the RFH. This is because if another navigation had committed and
  // committed a new RFH that replaces the current RFH, the
  // `associated_rfh_type_` might be stale and needs to be updated. However,
  // we only update the value for non-pending commit navigations (i.e. the
  // NavigationRequest owned by the FrameTreeNode). See the comments in
  // `RenderFrameHostManager::CommitPendingIfNecessary()` for more details.
  CHECK(state_ < READY_TO_COMMIT || state_ == WILL_FAIL_REQUEST)
      << "Use GetRenderFrameHost() instead when the final RenderFrameHost "
         "for the navigation has been picked";
  return associated_rfh_type_;
}

void NavigationRequest::SetAssociatedRFHType(
    AssociatedRenderFrameHostType type) {
  if (associated_rfh_type_ != AssociatedRenderFrameHostType::NONE &&
      type == AssociatedRenderFrameHostType::NONE) {
    // If we're transitioning to "NONE" when the previous state was not "NONE",
    // we might have called SetExpectedProcess() before, so reset it now.
    ResetExpectedProcess();
  }
  associated_rfh_type_ = type;
}

const net::HttpRequestHeaders& NavigationRequest::GetRequestHeaders() {
  if (!request_headers_) {
    request_headers_.emplace();
    request_headers_->AddHeadersFromString(begin_params_->headers);
  }
  return *request_headers_;
}

const std::optional<net::SSLInfo>& NavigationRequest::GetSSLInfo() {
  return ssl_info_;
}

const std::optional<net::AuthChallengeInfo>&
NavigationRequest::GetAuthChallengeInfo() {
  return auth_challenge_info_;
}

net::ResolveErrorInfo NavigationRequest::GetResolveErrorInfo() {
  return resolve_error_info_;
}

net::IsolationInfo NavigationRequest::GetIsolationInfo() {
  if (isolation_info_)
    return isolation_info_.value();

  // TODO(crbug.com/40634002): Consider changing this code to copy an origin
  // instead of creating one from a URL which lacks opacity information.
  return frame_tree_node_->current_frame_host()
      ->ComputeIsolationInfoForNavigation(
          common_params_->url, is_credentialless(), ComputeFencedFrameNonce());
}

bool NavigationRequest::HasSubframeNavigationEntryCommitted() {
  CHECK(!frame_tree_node_->IsMainFrame());
  CHECK(state_ == DID_COMMIT || state_ == DID_COMMIT_ERROR_PAGE);
  return subframe_entry_committed_;
}

bool NavigationRequest::DidReplaceEntry() {
  CHECK(state_ == DID_COMMIT || state_ == DID_COMMIT_ERROR_PAGE);
  return did_replace_entry_;
}

bool NavigationRequest::ShouldUpdateHistory() {
  CHECK(state_ == DID_COMMIT || state_ == DID_COMMIT_ERROR_PAGE);
  return should_update_history_;
}

const GURL& NavigationRequest::GetPreviousPrimaryMainFrameURL() {
  CHECK(IsInPrimaryMainFrame() ||
        GetParentFrame() && GetParentFrame()->GetPage().IsPrimary());
  return GetPreviousMainFrameURL();
}

const GURL& NavigationRequest::GetPreviousMainFrameURL() const {
  // TODO(https://crbug.com/526542680): CHECK-exclusion: Convert to CHECK once
  // we are sure this isn't hit.
  DCHECK(state_ == DID_COMMIT || state_ == DID_COMMIT_ERROR_PAGE);
  return previous_main_frame_url_;
}

bool NavigationRequest::WasStartedFromContextMenu() {
  return common_params().started_from_context_menu;
}

const GURL& NavigationRequest::GetSearchableFormURL() {
  return begin_params().searchable_form_url;
}

const std::string& NavigationRequest::GetSearchableFormEncoding() {
  return begin_params().searchable_form_encoding;
}

ReloadType NavigationRequest::GetReloadType() const {
  return reload_type_;
}

RestoreType NavigationRequest::GetRestoreType() const {
  return restore_type_;
}

const GURL& NavigationRequest::GetBaseURLForDataURL() {
  return common_params().base_url_for_data_url;
}

const GlobalRequestID& NavigationRequest::GetGlobalRequestID() {
  CHECK_GE(state_, WILL_PROCESS_RESPONSE);
  return request_id_;
}

bool NavigationRequest::IsDownload() {
  return is_download_;
}

bool NavigationRequest::IsFormSubmission() {
  return begin_params().is_form_submission;
}

bool NavigationRequest::WasInitiatedByLinkClick() {
  return begin_params().was_initiated_by_link_click;
}

const std::string& NavigationRequest::GetHrefTranslate() {
  return common_params().href_translate;
}

const std::optional<blink::LocalFrameToken>&
NavigationRequest::GetInitiatorFrameToken() {
  return initiator_frame_token_;
}

ChildProcessId NavigationRequest::GetInitiatorProcessId() {
  return initiator_process_id_;
}

const std::optional<url::Origin>& NavigationRequest::GetInitiatorOrigin() {
  return common_params().initiator_origin;
}

scoped_refptr<InitiatorNavigationState>
NavigationRequest::GetInitiatorNavigationState() {
  return initiator_navigation_state_;
}

const std::optional<GURL>& NavigationRequest::GetInitiatorBaseUrl() {
  return common_params().initiator_base_url;
}

const std::vector<std::string>& NavigationRequest::GetDnsAliases() {
  static const base::NoDestructor<std::vector<std::string>> emptyvector_result;
  return response_head_ ? response_head_->dns_aliases : *emptyvector_result;
}

bool NavigationRequest::IsSameProcess() {
  return is_same_process_;
}

NavigationEntry* NavigationRequest::GetNavigationEntry() const {
  if (nav_entry_id_ == 0)
    return nullptr;

  return GetNavigationController()->GetEntryWithUniqueIDIncludingPending(
      nav_entry_id_);
}

int NavigationRequest::GetNavigationEntryOffset() const {
  return navigation_entry_offset_;
}

GlobalRenderFrameHostId NavigationRequest::GetPreviousRenderFrameHostId() {
  if (previous_render_frame_host_id_ != GlobalRenderFrameHostId()) {
    CHECK_GE(state_, READY_TO_COMMIT);
    // If `previous_render_frame_host_id_` is set to a non-default value, then
    // the navigation had committed and potentially replaced the previous
    // "current RenderFrameHost", so we return the saved value of that previous
    // RenderFrameHost's ID here.
    return previous_render_frame_host_id_;
  }

  // The navigation hasn't committed yet, so the previous RenderFrameHost is
  // still the current RenderFrameHost. Note that this might be different from
  // the current RFH at NavigationRequest construction time (whose FTN id value
  // is saved in `current_render_frame_host_id_at_construction_`), if another
  // navigation caused a new RenderFrameHost to be committed while this
  // navigation is in progress.
  if (frame_tree_node_->current_frame_host()) {
    return frame_tree_node_->current_frame_host()->GetGlobalId();
  } else {
    // It's possible for `frame_tree_node_->current_frame_host()` to be null if
    // we're in the middle of destructing the navigating FrameTreeNode. In this
    // case, just return `current_render_frame_host_id_at_construction_`.
    return current_render_frame_host_id_at_construction_;
  }
}

ChildProcessId NavigationRequest::GetExpectedRenderProcessHostId() {
  CHECK_LT(state_, READY_TO_COMMIT);
  return expected_render_process_host_id_;
}

void NavigationRequest::SetIsOverridingUserAgent(bool override_ua) {
  // Only add specific headers when creating a NavigationRequest before the
  // network request is made, not at commit time.
  if (is_synchronous_renderer_commit_)
    return;

  // This code assumes it is only called from DidStartNavigation().
  CHECK(!ua_change_requires_reload_);

  commit_params_->is_overriding_user_agent = override_ua;
  // The new document, created by this navigation, will be honoring the new
  // value. It will be reflected into its NavigationEntry's when committing the
  // new document at DidCommitNavigation time.

  net::HttpRequestHeaders headers;
  headers.AddHeadersFromString(begin_params_->headers);
  BrowserContext* browser_context =
      frame_tree_node_->navigator().controller().GetBrowserContext();
  ClientHintsControllerDelegate* client_hints_delegate =
      browser_context->GetClientHintsControllerDelegate();
  if (client_hints_delegate) {
    UpdateNavigationRequestClientUaHeaders(
        GetTentativeOriginAtRequestTime(), client_hints_delegate,
        is_overriding_user_agent(), frame_tree_node_, &headers,
        common_params_->url);
  }
  headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                    ComputeUserAgentValue(GetUserAgentOverride()));
  begin_params_->headers = headers.ToString();
  // |request_headers_| comes from |begin_params_|. Clear |request_headers_| now
  // so that if |request_headers_| are needed, they will be updated.
  request_headers_.reset();
}

void NavigationRequest::SetSilentlyIgnoreErrors() {
  silently_ignore_errors_ = true;
}

void NavigationRequest::SetVisitedLinkSalt(uint64_t salt) {
  commit_params_->visited_link_salt = salt;
}

// static
NavigationRequest* NavigationRequest::From(NavigationHandle* handle) {
  return static_cast<NavigationRequest*>(handle);
}

// static
ReloadType NavigationRequest::NavigationTypeToReloadType(
    blink::mojom::NavigationType type) {
  if (type == blink::mojom::NavigationType::RELOAD)
    return ReloadType::NORMAL;
  if (type == blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE)
    return ReloadType::BYPASSING_CACHE;
  return ReloadType::NONE;
}

bool NavigationRequest::IsNavigationStarted() const {
  return is_navigation_started_;
}

bool NavigationRequest::RequiresInitiatorBasedSourceSiteInstance() const {
  // Browser-initiated navigations can supply an initiator origin without having
  // an associated source SiteInstance (e.g. Android intents handled by Chrome).
  // However, the context menu is a case in which we should have one, so it
  // should still require a source SiteInstance.
  if (commit_params_->is_browser_initiated &&
      !common_params().started_from_context_menu) {
    return false;
  }

  // data: URLs, about:blank and empty URL (which are treated the same as
  // about:blank) navigations that have initiator origins require a source
  // SiteInstance.
  const bool is_data_or_about_or_empty =
      common_params_->url.SchemeIs(url::kDataScheme) ||
      common_params_->url.IsAboutBlank() || common_params_->url.is_empty();

  const bool has_valid_initiator =
      common_params_->initiator_origin &&
      common_params_->initiator_origin->GetTupleOrPrecursorTupleIfOpaque()
          .IsValid();

  return is_data_or_about_or_empty && has_valid_initiator &&
         !dest_site_instance_;
}

void NavigationRequest::SetSourceSiteInstanceToInitiatorIfNeeded() {
  if (source_site_instance_ || !RequiresInitiatorBasedSourceSiteInstance())
    return;

  // TODO(crbug.com/349972037): Reconsider source SiteInstance usage. E.g. if
  // the initiator is sandboxed, the source SiteInstance we get here isn't and
  // they don't technically match.
  const auto tuple =
      common_params_->initiator_origin->GetTupleOrPrecursorTupleIfOpaque();
  source_site_instance_ = static_cast<SiteInstanceImpl*>(
      frame_tree_node_->current_frame_host()
          ->GetSiteInstance()
          ->GetRelatedSiteInstance(tuple.GetURL())
          .get());
}

void NavigationRequest::ForceEnableOriginTrials(
    const std::vector<std::string>& trials) {
  CHECK(!HasCommitted());
  commit_params_->force_enabled_origin_trials = trials;
}

network::CrossOriginEmbedderPolicy
NavigationRequest::ComputeCrossOriginEmbedderPolicy() {
  const auto& url = common_params_->url;
  // Fenced Frames should respect the outer frame's COEP.
  RenderFrameHostImpl* const parent = GetParentFrameOrOuterDocument();
  bool is_fenced_frame_from_local_scheme =
      GetNavigatingFrameType() == FrameType::kFencedFrameRoot &&
      (url.SchemeIsBlob() || url.SchemeIs(url::kDataScheme));

  // Some special URLs not loaded using the network inherit the
  // Cross-Origin-Embedder-Policy header from their parent.
  if (parent && (GetContentClient()
                     ->browser()
                     ->ShouldInheritCrossOriginEmbedderPolicyImplicitly(url) ||
                 is_fenced_frame_from_local_scheme)) {
    return parent->cross_origin_embedder_policy();
  }

  // Compute "topLevelCreationURL" for COEP and secure context.
  //
  // [spec]: https://html.spec.whatwg.org/C/#initialise-the-document-object
  // 3. Let creationURL be navigationParams's response's URL.
  // 5. If browsingContext is still on its initial about:blank Document [...]
  // 6. Otherwise:
  // 6.6. Let topLevelCreationURL be creationURL.
  // 6.8. If browsingContext is not a top-level browsing context, then:
  // 6.8.2. Set topLevelCreationURL to parentEnvironment's top-level creation
  //        URL.
  //
  // TODO(arthursonzogni): It would be good to clarify what
  // |topLevelCreationURL| means when loading FencedFrame//GuestView, and how it
  // should affect COEP.
  // Tracking bug:
  // - FencedFrame: https://crbug.com/1277430
  // - GuestView: XXX or slightly related https://crbug.com/1260747
  const GURL& top_level_creation_url = GetParentFrameOrOuterDocument()
                                           ? GetParentFrameOrOuterDocument()
                                                 ->GetOutermostMainFrame()
                                                 ->GetLastCommittedURL()
                                           : url;
  // [spec]: https://html.spec.whatwg.org/C/#obtain-an-embedder-policy
  //
  // 1. Let policy be a new embedder policy.
  // 2. If environment is a non-secure context, then return policy.
  if (network::IsUrlPotentiallyTrustworthy(top_level_creation_url)) {
    if (response_head_) {
      return response_head_->parsed_headers->cross_origin_embedder_policy;
    }
  }
  return network::CrossOriginEmbedderPolicy();
}

NavigationRequest::IntegrityPolicies
NavigationRequest::ComputeIntegrityPolicies() {
  IntegrityPolicies policies;
  if (!base::FeatureList::IsEnabled(
          network::features::kIntegrityPolicyScript)) {
    return policies;
  }
  if (response_head_ && response_head_->parsed_headers) {
    policies.enforced = response_head_->parsed_headers->integrity_policy;
    policies.report_only =
        response_head_->parsed_headers->integrity_policy_report_only;
  }
  return policies;
}

// [spec]:
// https://html.spec.whatwg.org/C/#check-a-navigation-response's-adherence-to-its-embedder-policy
//
// Return whether the child's |coep| is compatible with its parent's COEP. It
// also sends COEP reports if needed.
bool NavigationRequest::CheckResponseAdherenceToCoep(const GURL& url) {
  const auto& coep =
      policy_container_builder_->FinalPolicies().cross_origin_embedder_policy;

  // Fenced Frames should respect the outer frame's COEP.
  RenderFrameHostImpl* const parent = GetParentFrameOrOuterDocument();

  // [spec]: 1. If target is not a child browsing context, then return true.
  if (!parent)
    return true;

  // [spec]: 2. Let parentPolicy be target's container document's policy
  //            container's embedder policy.
  const auto& parent_coep = parent->cross_origin_embedder_policy();
  CrossOriginEmbedderPolicyReporter* parent_coep_reporter =
      parent->coep_reporter();

  // [spec]: 3. If parentPolicy's report-only value is compatible with
  // cross-origin isolation and responsePolicy's value is not, then queue a
  // cross-origin embedder policy inheritance violation [...].
  if (CoepBlockIframe(parent_coep.report_only_value, coep.value,
                      is_credentialless())) {
    if (parent_coep_reporter) {
      parent_coep_reporter->QueueNavigationReport(redirect_chain_[0],
                                                  /*report_only=*/true);
    }
  }

  // [spec]: 4. If parentPolicy's value is not compatible with cross-origin
  // isolation or responsePolicy's value is compatible with cross-origin
  // isolation, then return true.
  if (!CoepBlockIframe(parent_coep.value, coep.value, is_credentialless()))
    return true;

  // [spec]: 5 Queue a cross-origin embedder policy inheritance violation with
  // response, "navigation", parentPolicy's reporting endpoint, "enforce", and
  // target's container document's relevant settings object.
  if (parent_coep_reporter) {
    parent_coep_reporter->QueueNavigationReport(redirect_chain_[0],
                                                /*report_only=*/false);
  }

  // [spec]: 6. Return false.
  return false;
}

std::optional<network::mojom::BlockedByResponseReason>
NavigationRequest::EnforceCOEP() {
  // https://html.spec.whatwg.org/C/#check-a-navigation-response's-adherence-to-its-embedder-policy
  // Spec should be updated:
  // https://github.com/shivanigithub/fenced-frame/issues/11

  // Fenced frames should be treated as an embedded frame, thus COEP must apply.
  RenderFrameHostImpl* const parent_frame = GetParentFrameOrOuterDocument();
  if (!parent_frame) {
    return std::nullopt;
  }
  if (is_credentialless()) {
    return std::nullopt;
  }
  const auto& url = common_params_->url;
  // Some special URLs not loaded using the network are inheriting the
  // Cross-Origin-Embedder-Policy header from their parent.
  if (url.SchemeIsBlob() || url.SchemeIs(url::kDataScheme)) {
    return std::nullopt;
  }
  return network::CrossOriginResourcePolicy::IsNavigationBlocked(
      url, redirect_chain_[0], parent_frame->GetLastCommittedOrigin(),
      *response_head_, request_destination(),
      parent_frame->cross_origin_embedder_policy(),
      parent_frame->coep_reporter());
}

void NavigationRequest::CoopCoepSanityCheck() {
  // Same-document navigations simply reuse the current document and do not use
  // the PolicyContainer, which may contain erroneous information. For example
  // in pushState/popState history navigations. See https://crbug.com/1413081.
  if (IsSameDocument()) {
    return;
  }

  // Credentialless iframes allow frames to be crossOriginIsolated without ever
  // setting COEP, so the below check does not apply.
  if (is_credentialless_) {
    return;
  }

  const PolicyContainerPolicies& policies =
      policy_container_builder_->FinalPolicies();
  // Use GetParentFrameOrOuterDocument() to respect the outer frame's COEP for
  // now.
  network::mojom::CrossOriginOpenerPolicyValue coop_value =
      GetParentFrameOrOuterDocument()
          ? GetRenderFrameHost()
                ->GetOutermostMainFrame()
                ->cross_origin_opener_policy()
                .value
          : policies.cross_origin_opener_policy.value;

  if (coop_value ==
          network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep &&
      !CompatibleWithCrossOriginIsolated(
          policies.cross_origin_embedder_policy)) {
    NOTREACHED();
  }
}

bool NavigationRequest::IsFencedFrameRequiredPolicyFeatureAllowed(
    const url::Origin& origin,
    const network::mojom::PermissionsPolicyFeature feature) {
  const network::PermissionsPolicyFeatureList& feature_list =
      network::GetPermissionsPolicyFeatureList(origin);

  // Check if the outer document's permissions policies allow all of the
  // required policies.
  std::optional<const network::PermissionsPolicy::Allowlist>
      embedder_allowlist = GetParentFrameOrOuterDocument()
                               ->GetPermissionsPolicy()
                               ->GetAllowlistForFeatureIfExists(feature);
  if (embedder_allowlist && !embedder_allowlist->MatchesAll()) {
    return false;
  }

  // Check if the container policies to be committed allow all of the required
  // policies for `origin`. This means that the policy must be either
  // explicitly enabled for `origin`, or the policy must by default
  // be enabled for all origins. Note: because the policies have not been
  // read into a RenderFrameHost's permissions_policy_ yet, we need to check
  // the network::ParsedPermissionsPolicyDeclaration object directly.
  auto policy_iter = std::find_if(
      commit_params_->frame_policy.container_policy.begin(),
      commit_params_->frame_policy.container_policy.end(),
      [feature](const network::ParsedPermissionsPolicyDeclaration& d) {
        return d.feature == feature;
      });
  if (policy_iter == commit_params_->frame_policy.container_policy.end()) {
    return feature_list.at(feature) ==
           network::PermissionsPolicyFeatureDefault::EnableForAll;
  }

  return policy_iter->Contains(origin);
}

bool NavigationRequest::CheckPermissionsPoliciesForFencedFrames(
    const url::Origin& origin) {
  // These checks only apply to fenced frames.
  if (!frame_tree_node_->IsFencedFrameRoot())
    return true;

  const std::optional<FencedFrameProperties>& computed_fenced_frame_properties =
      ComputeFencedFrameProperties();

  // Permissions policies only need to be checked for fenced frames created from
  // an API like FLEDGE or Shared Storage.
  if (!computed_fenced_frame_properties) {
    return true;
  }

  // Check that all of the required policies for a new document with origin
  // `origin` in the fenced frame are allowed. This looks at the outer
  // document's policies and the "allow" attribute. Note that the document will
  // eventually only use the required policies without policy inheritance, so
  // extra policies defined in the outer document/"allow" attribute won't have
  // any effect.
  for (const network::mojom::PermissionsPolicyFeature feature :
       computed_fenced_frame_properties->effective_enabled_permissions()) {
    if (!IsFencedFrameRequiredPolicyFeatureAllowed(origin, feature)) {
      const blink::PermissionsPolicyFeatureToNameMap& feature_to_name_map =
          blink::GetPermissionsPolicyFeatureToNameMap();
      const std::string feature_string(feature_to_name_map.at(feature));
      frame_tree_node_->current_frame_host()->AddMessageToConsole(
          blink::mojom::ConsoleMessageLevel::kError,
          base::StringPrintf(
              "Refused to frame '%s' as a fenced frame because permissions "
              "policy '%s' is not allowed for the frame's origin.",
              origin.Serialize().c_str(), feature_string.c_str()));
      return false;
    }
  }
  return true;
}

std::unique_ptr<viz::PeakGpuMemoryTracker>
NavigationRequest::TakePeakGpuMemoryTracker() {
  return std::move(loading_mem_tracker_);
}

std::unique_ptr<NavigationEarlyHintsManager>
NavigationRequest::TakeEarlyHintsManager() {
  return std::move(early_hints_manager_);
}

network::mojom::ClientSecurityStatePtr
NavigationRequest::BuildClientSecurityStateForNavigationFetch() {
  switch (GetNavigatingFrameType()) {
    // The client [1] of the navigation fetch request is the navigation
    // initiator, so use the initiator's policies to set the
    // `ClientSecurityState`. Note that we use the initiator's policies
    // regardless of whether they are inheritable or not, as the main purpose of
    // this ClientSecurityState is to enforce LNA. Restricting ourselves to
    // inheritable policies would create an LNA bypass, so we always pass the
    // initiator policies.
    //
    // [1] https://fetch.spec.whatwg.org/#concept-request-client
    //
    // The `kPrimaryMainFrame` case also covers guest views
    // (https://crbug.com/1261928) when the MPArch implementation is not being
    // used.
    //
    // TODO(crbug.com/40258826): Determine how to treat guest views.
    case FrameType::kPrimaryMainFrame:
    case FrameType::kGuestMainFrame: {
      if (!GetInitiatorPolicyContainerPolicies()) {
        return nullptr;
      }

      return DeriveClientSecurityStateForRendererInitiatedNavigation(
          *GetInitiatorPolicyContainerPolicies(),
          LocalNetworkAccessRequestContext::kMainFrameNavigation);
    }
    case FrameType::kSubframe: {
      if (!GetInitiatorPolicyContainerPolicies()) {
        return nullptr;
      }

      network::mojom::ClientSecurityStatePtr state =
          DeriveClientSecurityStateForRendererInitiatedNavigation(
              *GetInitiatorPolicyContainerPolicies(),
              LocalNetworkAccessRequestContext::kSubframeNavigation);

      // Check for policy overrides on LNA. For subframe navigations, we apply
      // policy overrides based on the initiator.
      // TODO(crbug.com/452389539): Centralize these policy overrides.
      if (GetInitiatorOrigin()) {
        ContentBrowserClient* client = GetContentClient()->browser();
        BrowserContext* context =
            frame_tree_node_->navigator().controller().GetBrowserContext();
        url::Origin origin = GetInitiatorOrigin().value();
        ContentBrowserClient::LocalNetworkAccessRequestPolicyOverride
            policy_override =
                client->ShouldOverrideLocalNetworkAccessRequestPolicy(context,
                                                                      origin);
        state->local_network_access_request_policy =
            OverrideLocalNetworkAccessPolicy(
                state->local_network_access_request_policy, policy_override);
      }

      return state;
    }

    // Fenced frames can only be navigated in two ways:
    //
    // 1. By the embedder document, via the fencedframe.config attribute.
    //    The implementation uses the parent policies directly, because
    //    initiator policies are not currently plumbed in this case. This is
    //    correct anyway because the initiator is the parent.
    //    Note: contrary to an iframe, the navigation can never happens at
    //    distance, using e.g. `window.open(url, target)` or `<a target>`.
    //
    // 2. By a document in the <fencedframe> frame tree. In this case the
    //    initiator policies are properly plumbed and should be used.
    //
    // TODO(crbug.com/40258851): Use the initiator policies. On can use
    // `is_embedder_initiated_fenced_frame_navigation_` to discriminate (1) from
    // (2).
    //
    // NOTE: Fenced frames always have an outer document,
    // `GetParentFrameOrOuterDocument()` is never nullptr.
    case FrameType::kFencedFrameRoot: {
      auto client_security_state =
          GetParentFrameOrOuterDocument()->BuildClientSecurityState();

      // TODO(crbug.com/40258851): Remove COEP from
      // `client_security_state`, see the reasoning for subframes above.
      client_security_state->local_network_access_request_policy =
          DeriveLocalNetworkAccessRequestPolicy(
              client_security_state->ip_address_space,
              client_security_state->is_web_secure_context,
              LocalNetworkAccessRequestContext::kFencedFrameNavigation);

      return client_security_state;
    }

    // TODO(crbug.com/40258824): Determine how to treat prerendered
    // main frames.
    case FrameType::kPrerenderMainFrame:
      return nullptr;
  }
}

network::mojom::ClientSecurityStatePtr
NavigationRequest::BuildClientSecurityStateForCommittedDocument() {
  const PolicyContainerPolicies& policies =
      policy_container_builder_->FinalPolicies();

  return network::mojom::ClientSecurityState::New(
      policies.cross_origin_embedder_policy, policies.is_web_secure_context,
      policies.ip_address_space, local_network_access_request_policy_,
      policies.document_isolation_policy);
}

std::string NavigationRequest::GetUserAgentOverride() {
  return is_overriding_user_agent()
             ? frame_tree_node_->navigator()
                   .GetDelegate()
                   ->GetUserAgentOverride(frame_tree_node_->frame_tree())
                   .ua_string_override
             : std::string();
}

NavigationControllerImpl* NavigationRequest::GetNavigationController() const {
  return &frame_tree_node_->navigator().controller();
}

PrerenderHostRegistry& NavigationRequest::GetPrerenderHostRegistry() {
  PrerenderHostRegistry* registry = frame_tree_node_->current_frame_host()
                                        ->delegate()
                                        ->GetPrerenderHostRegistry();
  CHECK(registry);
  return *registry;
}

mojo::PendingRemote<network::mojom::CookieAccessObserver>
NavigationRequest::CreateCookieAccessObserver() {
  mojo::PendingRemote<network::mojom::CookieAccessObserver> remote;
  cookie_observers_->Add(remote.InitWithNewPipeAndPassReceiver(),
                         CookieAccessDetails::Source::kNavigation);
  return remote;
}

mojo::PendingRemote<network::mojom::TrustTokenAccessObserver>
NavigationRequest::CreateTrustTokenAccessObserver() {
  mojo::PendingRemote<network::mojom::TrustTokenAccessObserver> remote;
  trust_token_observers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

mojo::PendingRemote<network::mojom::SharedDictionaryAccessObserver>
NavigationRequest::CreateSharedDictionaryAccessObserver() {
  mojo::PendingRemote<network::mojom::SharedDictionaryAccessObserver> remote;
  shared_dictionary_observers_.Add(this,
                                   remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

mojo::PendingRemote<network::mojom::DeviceBoundSessionAccessObserver>
NavigationRequest::CreateDeviceBoundSessionObserver() {
  mojo::PendingRemote<network::mojom::DeviceBoundSessionAccessObserver> remote;
  device_bound_session_observers_.Add(this,
                                      remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

void NavigationRequest::NotifyCookiesAccessed(
    std::vector<network::mojom::CookieAccessDetailsPtr> details_vector,
    CookieAccessDetails::Source source) {
  TRACE_EVENT("navigation", "NavigationRequest::NotifyCookiesAccessed",
              perfetto::Flow::FromPointer(this));
  std::optional<base::ElapsedTimer> timer;
  if (base::ShouldRecordSubsampledMetric(0.01)) {
    timer.emplace();
  }
  for (auto& details : details_vector) {
    // TODO(crbug.com/40520047): We should not send information to the current
    // frame about (potentially unrelated) ongoing navigation, but at the moment
    // we don't have another way to add messages to DevTools console.
    EmitCookieWarningsAndMetrics(frame_tree_node()->current_frame_host(),
                                 details);

    CookieAccessDetails allowed;
    CookieAccessDetails blocked;
    SplitCookiesIntoAllowedAndBlocked(details, source, &allowed, &blocked);
    if (!allowed.cookie_access_result_list.empty()) {
      GetDelegate()->OnCookiesAccessed(this, allowed);
    }
    if (!blocked.cookie_access_result_list.empty()) {
      GetDelegate()->OnCookiesAccessed(this, blocked);
    }

    // When determining the BFCache eligibility, we explicitly ignore the cookie
    // changes from the navigation itself because we want the
    // `CookieChangeListener` to only track the cookie changes that potentially
    // make the document initially rendered by the navigation request outdated.
    if (allowed.type == CookieAccessDetails::Type::kChange) {
      if (cookie_change_listener_) {
        for (const net::CookieWithAccessResult& cookie_with_access_result :
             allowed.cookie_access_result_list) {
          cookie_change_listener_->AddNavigationCookieToIgnore(
              base::PassKey<NavigationRequest>(),
              cookie_with_access_result.cookie);
        }
      }
    }
  }
  if (timer) {
    base::UmaHistogramCustomMicrosecondsTimes(
        "Browser.CookieAccessObserver.NavigationRequest.Duration.Subsampled",
        timer->Elapsed(), base::Microseconds(1), base::Seconds(1), 100);
  }
}

std::vector<
    std::pair<mojo::PendingReceiver<network::mojom::CookieAccessObserver>,
              CookieAccessDetails::Source>>
NavigationRequest::TakeCookieObservers() {
  return cookie_observers_->TakeReceiversWithContext();
}

void NavigationRequest::OnTrustTokensAccessed(
    network::mojom::TrustTokenAccessDetailsPtr details) {
  GetDelegate()->OnTrustTokensAccessed(this, TrustTokenAccessDetails(details));
}

void NavigationRequest::Clone(
    mojo::PendingReceiver<network::mojom::TrustTokenAccessObserver> observer) {
  trust_token_observers_.Add(this, std::move(observer));
}

std::vector<mojo::PendingReceiver<network::mojom::TrustTokenAccessObserver>>
NavigationRequest::TakeTrustTokenObservers() {
  return trust_token_observers_.TakeReceivers();
}

void NavigationRequest::OnSharedDictionaryAccessed(
    network::mojom::SharedDictionaryAccessDetailsPtr details) {
  GetDelegate()->OnSharedDictionaryAccessed(this, *details);
}

void NavigationRequest::Clone(
    mojo::PendingReceiver<network::mojom::SharedDictionaryAccessObserver>
        observer) {
  shared_dictionary_observers_.Add(this, std::move(observer));
}

void NavigationRequest::OnDeviceBoundSessionAccessed(
    const net::device_bound_sessions::SessionAccess& access) {
  GetDelegate()->OnDeviceBoundSessionAccessed(this, access);
}
void NavigationRequest::Clone(
    mojo::PendingReceiver<network::mojom::DeviceBoundSessionAccessObserver>
        observer) {
  device_bound_session_observers_.Add(this, std::move(observer));
}

std::vector<
    mojo::PendingReceiver<network::mojom::SharedDictionaryAccessObserver>>
NavigationRequest::TakeSharedDictionaryAccessObservers() {
  return shared_dictionary_observers_.TakeReceivers();
}

std::vector<
    mojo::PendingReceiver<network::mojom::DeviceBoundSessionAccessObserver>>
NavigationRequest::TakeDeviceBoundSessionAccessObservers() {
  return device_bound_session_observers_.TakeReceivers();
}

RenderFrameHostImpl* NavigationRequest::GetInitiatorDocumentRenderFrameHost() {
  return initiator_document_token_
             ? RenderFrameHostImpl::FromDocumentToken(
                   initiator_process_id_, *initiator_document_token_)
             : nullptr;
}

void NavigationRequest::RecordAddressSpaceFeature() {
  CHECK(response_head_);
  CHECK(policy_container_builder_);

  RenderFrameHostImpl* initiator_render_frame_host =
      GetInitiatorDocumentRenderFrameHost();
  if (!initiator_render_frame_host) {
    // The initiator document is no longer available, so we cannot log a feature
    // use against it. This case may result in a slight undercounting, but is
    // expected to be rare enough that it should not matter for compat risk
    // evaluation.
    return;
  }

  // Get the initiator policies. Note that we are not checking whether the
  // policies from the initiator's InitiatorNavigationState may be inherited.
  // The address space of the initiator should always be taken into account.
  // If we lack an |initiator_navigation_state_|, then get the policies from the
  // initiator RenderFrameHost instead.
  // TODO(crbug.com/510258191): Check that |initiator_navigation_state_| is not
  // null. Currently, it is still possible to pass an |initiator_frame_token_|
  // without an |initiator_navigation_state_|.
  const PolicyContainerPolicies& initiator_policies =
      initiator_navigation_state_impl()
          ? initiator_navigation_state_impl()->policy_container_policies()
          : initiator_render_frame_host->policy_container_host()->policies();

  std::optional<blink::mojom::WebFeature> optional_feature =
      blink::AddressSpaceFeature(blink::FetchType::kNavigation,
                                 initiator_policies.ip_address_space,
                                 initiator_policies.is_web_secure_context,
                                 response_head_->response_address_space);
  if (!optional_feature.has_value()) {
    return;
  }

  ContentBrowserClient* client = GetContentClient()->browser();
  client->LogWebFeatureForCurrentPage(initiator_render_frame_host,
                                      *optional_feature);
  client->LogWebFeatureForCurrentPage(
      initiator_render_frame_host,
      IsInOutermostMainFrame()
          ? blink::mojom::WebFeature::kPrivateNetworkAccessFetchedTopFrame
          : blink::mojom::WebFeature::kPrivateNetworkAccessFetchedSubFrame);
}

void NavigationRequest::ComputePoliciesToCommit() {
  const auto& url = common_params_->url;

  network::mojom::IPAddressSpace response_address_space =
      CalculateIPAddressSpace(url, response_head_.get(),
                              GetContentClient()->browser());
  policy_container_builder_->SetIPAddressSpace(response_address_space);

  network::ConnectionAllowlists connection_allowlists_to_commit;
  bool has_connection_allowlists = false;
  if (ResponseContainsConnectionAllowlist(response_head_.get()) &&
      base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
    // Connection allowlist needs to be enforced once the allowlist response
    // header is received.
    //
    // The allowlist is stored in the policy container if the base::Feature is
    // enabled.
    connection_allowlists_to_commit =
        std::move(response_head_->parsed_headers->connection_allowlists);
    has_connection_allowlists = true;
  }

  // Connection-Allowlist embedded enforcement: when the framed document opted
  // into blanket enforcement (validated in
  // CheckConnectionAllowlistEmbeddedEnforcement(), which also resolved the
  // `response-origin` token), the embedder's required allowlist becomes the
  // frame's enforced allowlist. This is only set for non-local schemes, whose
  // committed policies come from these delivered policies.
  if (enforce_required_connection_allowlist_) {
    DCHECK(required_connection_allowlist_.has_value());
    connection_allowlists_to_commit.enforced = required_connection_allowlist_;
    if (connection_allowlists_to_commit.response_url.is_empty()) {
      connection_allowlists_to_commit.response_url = GetURL();
    }
    has_connection_allowlists = true;
  }

  if (has_connection_allowlists) {
    policy_container_builder_->SetConnectionAllowlists(
        std::move(connection_allowlists_to_commit));
  }

  if (!devtools_instrumentation::ShouldBypassCSP(*this)) {
    if (response_head_) {
      policy_container_builder_->AddContentSecurityPolicies(
          mojo::Clone(response_head_->parsed_headers->content_security_policy));
    }
  }

  // Use the unchecked / non-sandboxed origin to calculate potential
  // trustworthiness. Indeed, the potential trustworthiness check should apply
  // to the origin of the creation URL, prior to opaquification.
  policy_container_builder_->SetIsOriginPotentiallyTrustworthy(
      network::IsOriginPotentiallyTrustworthy(
          GetOriginForURLLoaderFactoryUnchecked()));

  policy_container_builder_->SetCrossOriginEmbedderPolicy(
      ComputeCrossOriginEmbedderPolicy());

  IntegrityPolicies integrity_policies = ComputeIntegrityPolicies();
  policy_container_builder_->SetIntegrityPolicy(
      std::move(integrity_policies.enforced));
  policy_container_builder_->SetIntegrityPolicyReportOnly(
      std::move(integrity_policies.report_only));

  // If the navigation is the result of a network response, set DIP to the
  // one in the network response. Otherwise, DIP should follow normal rules of
  // policy inheritance, which should be handled by the policy container.
  if (response_head_) {
    SanitizeDocumentIsolationPolicyHeader();
    policy_container_builder_->SetDocumentIsolationPolicy(
        response_head_->parsed_headers->document_isolation_policy);
  }

  CHECK(commit_params_);
  CHECK(!HasCommitted());
  CHECK(!IsErrorPage());

  // Computed here, at commit time, so the final (post-redirect) `url` is used
  // and error-page commits (`ComputePoliciesToCommitForError()`) are excluded.
  commit_params_->is_secure_context_root =
      GetContentClient()->browser()->IsSecureContextRoot(
          GetParentFrame(), GetFrameTreeNodeId(), url);

  policy_container_builder_->ComputePolicies(
      this, GetInitiatorPolicyContainerPoliciesForInheritance(),
      IsMhtmlOrSubframe(), commit_params_->frame_policy.sandbox_flags,
      is_credentialless(), commit_params_->is_secure_context_root);
}

void NavigationRequest::ComputePoliciesToCommitForError() {
  CHECK(!IsMhtmlOrSubframe());
  policy_container_builder_->ComputePoliciesForError();
}

void NavigationRequest::DidUpdateInitiatorStateToken(
    const base::UnguessableToken& new_initiator_state_token) {
  initiator_state_token_to_commit_ = new_initiator_state_token;
}
void NavigationRequest::CheckStateTransition(NavigationState state) const {
#if DCHECK_IS_ON()
  // See
  // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/navigation-request-navigation-state.png
  // clang-format off
  static const base::NoDestructor<base::StateTransitions<NavigationState>>
      transitions(base::StateTransitions<NavigationState>({
          {NOT_STARTED, {
              WAITING_FOR_RENDERER_RESPONSE,
              WILL_START_NAVIGATION,
              WILL_START_REQUEST,
          }},
          {WAITING_FOR_RENDERER_RESPONSE, {
              WILL_START_NAVIGATION,
              WILL_START_REQUEST,
          }},
          {WILL_START_NAVIGATION, {
              WILL_START_REQUEST,
              WILL_FAIL_REQUEST,
          }},
          {WILL_START_REQUEST, {
              WILL_REDIRECT_REQUEST,
              WILL_PROCESS_RESPONSE,
              WILL_COMMIT_WITHOUT_URL_LOADER,
              READY_TO_COMMIT,
              DID_COMMIT,
              CANCELING,
              WILL_FAIL_REQUEST,
              DID_COMMIT_ERROR_PAGE,
          }},
          {WILL_REDIRECT_REQUEST, {
              WILL_REDIRECT_REQUEST,
              WILL_PROCESS_RESPONSE,
              CANCELING,
              WILL_FAIL_REQUEST,
          }},
          {WILL_PROCESS_RESPONSE, {
              READY_TO_COMMIT,
              CANCELING,
              WILL_FAIL_REQUEST,
          }},
          {WILL_COMMIT_WITHOUT_URL_LOADER, {
              READY_TO_COMMIT,
              CANCELING,
              WILL_FAIL_REQUEST,
          }},
          {READY_TO_COMMIT, {
              NOT_STARTED,
              DID_COMMIT,
              DID_COMMIT_ERROR_PAGE,
          }},
          {CANCELING, {
              READY_TO_COMMIT,
              WILL_FAIL_REQUEST,
          }},
          {WILL_FAIL_REQUEST, {
              READY_TO_COMMIT,
              CANCELING,
              WILL_FAIL_REQUEST,
          }},
          {DID_COMMIT, {}},
          {DID_COMMIT_ERROR_PAGE, {}},
      }));
  // clang-format on
  DCHECK_STATE_TRANSITION(transitions, state_, state);
#endif  // DCHECK_IS_ON()
}

void NavigationRequest::SetState(NavigationState state) {
  CheckStateTransition(state);
  state_ = state;
}

bool NavigationRequest::MaybeCancelFailedNavigation() {
  // TODO(crbug.com/41349746): Maybe take `ThrottleCheckResult::action()` into
  // account as well.
  std::optional<ErrorNavigationTrigger> error_reason;

  // If the request was canceled by the user, do not show an error page.
  if (net::ERR_ABORTED == net_error_) {
    error_reason = ErrorNavigationTrigger::kFailedWithSilentErrorOnNetAborted;
  }

  // Some embedders suppress error pages to allow custom error handling.
  if (silently_ignore_errors_) {
    error_reason = ErrorNavigationTrigger::kFailedWithSilentErrorOnIgnore;
  }

  // <webview> guests suppress net::ERR_BLOCKED_BY_CLIENT.
  if (net::ERR_BLOCKED_BY_CLIENT == net_error_ &&
      silently_ignore_blocked_by_client_) {
    error_reason =
        ErrorNavigationTrigger::kFailedWithSilentErrorOnBlockedByClient;
  }

  if (error_reason.has_value()) {
    error_navigation_trigger_ = error_reason;
    frame_tree_node_->ResetNavigationRequest(
        NavigationDiscardReason::kInternalCancellation);
    return true;
  }

  // Per https://whatwg.org/C/iframe-embed-object.html#the-object-element,
  // this implements step 4.7 from "determine what the object element
  // represents": "If the load failed (e.g. there was an HTTP 404 error, there
  // was a DNS error), fire an event named error at the element, then jump to
  // the step below labeled fallback."
  //
  // This case handles navigation failure, e.g. due to the navigation being
  // blocked by WebRequest, DNS errors, et cetera.
  if (frame_tree_node()->frame_owner_element_type() ==
      blink::FrameOwnerElementType::kObject) {
    error_navigation_trigger_ =
        ErrorNavigationTrigger::kNavigationOfObjectFailed;
    RenderFallbackContentForObjectTag();
    frame_tree_node_->ResetNavigationRequest(
        NavigationDiscardReason::kInternalCancellation);
    return true;
  }

  return false;
}

bool NavigationRequest::ShouldRenderFallbackContentForResponse(
    const net::HttpResponseHeaders& http_headers) const {
  return frame_tree_node()->frame_owner_element_type() ==
             blink::FrameOwnerElementType::kObject &&
         !network::IsSuccessfulStatus(http_headers.response_code());
}

// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigating-across-documents:hh-replace
bool NavigationRequest::ShouldReplaceCurrentEntryForSameUrlNavigation() const {
  CHECK_LE(state_, WILL_START_NAVIGATION);

  // Not a same-url navigation. Note that this is comparing against the "last
  // loading URL" since this is what was used in the renderer check that was
  // moved here. This means for error pages we should compare against the URL
  // that failed to load (the last committed URL), while for other navigations
  // we should compare against the last document URL, which might be different
  // from the last committed URL due to document.open() changing the URL.
  if (common_params_->url !=
      GetLastLoadingURLInRendererForNavigationReplacement(
          frame_tree_node_->current_frame_host())) {
    return false;
  }

  if (IsLoadDataWithBaseURL()) {
    // Preserve old behavior of loadDataWithBaseURL() navigations, which almost
    // never does same-URL replacement when the (data) URL is the same, since it
    // used to compare the data URL against the "history URL", which in almost
    // all cases wouldn't match the data: URL (in most cases it's either the
    // same as the base/document URL, or about:blank [the default value], see
    // https://crbug.com/1244746#c1 for more details).
    return false;
  }

  // Never replace if there is no NavigationEntry to replace.
  if (!frame_tree_node_->navigator().controller().GetEntryCount())
    return false;

  // The NavigationAPI allows a page to request a navigation that pushes even in
  // situations where the browser would implicitly convert the navigation to
  // a replace.
  if (begin_params_->force_history_push ==
      blink::mojom::ForceHistoryPush::kYes) {
    return false;
  }

  // Only (1) cross-document navigations, (2) same-document navigations from
  // browser UI (e.g., address bar or bookmark), and (3) navigations recovering
  // from an error document need to consider replacing the entry for same URL
  // cases. Reloads and history navigations have special handling and don't need
  // to. Note that same-document navigations with fragments from browser UI are
  // not treated as reloads.
  // Note that for same document navigation, even though the navigation request
  // starts with should_replace_current_entry, no new history entry is created.
  // With the logic in RenderFrameImpl::MakeDidCommitProvisionalLoadParams, we
  // scroll to the fragment without change to navigation history.
  // LocalFrame::ShouldReplaceForSameUrlNavigation also returns true for
  // renderer initiated same url navigation within the same document.
  const ui::PageTransition transition =
      ui::PageTransitionFromInt(common_params_->transition);
  const bool is_same_document_navigation_from_browser_ui =
      (common_params_->navigation_type ==
       blink::mojom::NavigationType::SAME_DOCUMENT) &&
      ((transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) ||
       PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_AUTO_BOOKMARK));
  const bool is_navigating_from_error =
      base::FeatureList::IsEnabled(kReplaceEntryWhenRecoveringFromError) &&
      frame_tree_node_->current_frame_host()->IsErrorDocument();
  if (!is_same_document_navigation_from_browser_ui &&
      !is_navigating_from_error &&
      (common_params_->navigation_type !=
       blink::mojom::NavigationType::DIFFERENT_DOCUMENT)) {
    return false;
  }
  // Form submissions to the same url should not replace.
  if (begin_params_->is_form_submission)
    return false;

  // If the initiating frame is cross-origin to the target frame, do not
  // replace the history entry for same-URL navigations to prevent observable
  // differences in session history length.
  if (!InitiatorMayObserveSameUrlReplacement()) {
    return false;
  }

  // Otherwise, replace current entry.
  return true;
}

bool NavigationRequest::
    ShouldReplaceCurrentEntryForNavigationFromInitialEmptyDocumentOrEntry()
        const {
  CHECK_LE(state_, WILL_START_NAVIGATION);
  // Never replace if there is no NavigationEntry to replace.
  if (!frame_tree_node_->navigator().controller().GetEntryCount())
    return false;

  if (common_params_->navigation_type !=
          blink::mojom::NavigationType::SAME_DOCUMENT &&
      common_params_->navigation_type !=
          blink::mojom::NavigationType::DIFFERENT_DOCUMENT) {
    // History navigations, session restore, and reloads shouldn't do
    // replacement.
    return false;
  }

  // Check the "initial NavigationEntry" status and the "initial empty document"
  // status.

  if (frame_tree_node_->navigator()
          .controller()
          .GetLastCommittedEntry()
          ->IsInitialEntry()) {
    // Initial NavigationEntry must always be replaced, to ensure that as long
    // as the NavigationEntry exists, it will be the only NavigationEntry in
    // the session history list, making history navigations to initial
    // NavigationEntry possible. See the comment for `is_initial_entry_` in
    // NavigationEntryImpl for more details.
    return true;
  }

  // For non-initial NavigationEntries, the initial empty document should also
  // be replaced in subframes and non-outermost main frames (fenced frames).
  // For outermost main frames, the initial empty document will usually only
  // exist when on the initial NavigationEntry, but it can also exist in a
  // restored or cloned NavigationController before the first commit. It is
  // important not to replace one of the restored entries in that case. See
  // https://crbug.com/1284566 and https://crbug.com/1295723.
  return frame_tree_node_->is_on_initial_empty_document() &&
         frame_tree_node_->GetParentOrOuterDocument();
}

bool NavigationRequest::ShouldReplaceCurrentEntryForFailedNavigation() const {
  CHECK(state_ == CANCELING || state_ == WILL_FAIL_REQUEST);

  if (common_params_->should_replace_current_entry)
    return true;

  // Never replace if there is no NavigationEntry to replace.
  if (!frame_tree_node_->navigator().controller().GetEntryCount())
    return false;

  auto page_state =
      blink::PageState::CreateFromEncodedData(commit_params_->page_state);
  // Failed history navigations with valid PageState should not do replacement.
  if (page_state.IsValid())
    return false;

  bool is_reload_or_history =
      NavigationTypeUtils::IsReload(common_params_->navigation_type) ||
      NavigationTypeUtils::IsHistory(common_params_->navigation_type);
  // Otherwise, these navigations should do replacement:
  // - Failed history/reloads with invalid PageState (e.g. same-URL navigations
  //   that got converted to a reload).
  // - Same-URL navigations. Note that even though we had a same-URL check
  //   earlier in the navigation's lifetime (which would convert same-URL
  //   navigations to reload or replacement), those compare against the initial
  //   URL instead of the final URL, which is what we're using here. Also, this
  //   is using the "loading URL", since that is the URL that was used in the
  //   renderer before we moved the replacement conversion here. As in
  //   ShouldReplaceCurrentEntryForSameUrlNavigation(), only replace for the
  //   same-URL case when the initiator is same-origin to the target frame.
  // TODO(crbug.com/396645696): Reconsider whether these two cases should
  // do replacement or not, since we're just preserving old behavior here.
  return is_reload_or_history ||
         (common_params_->url ==
              GetLastLoadingURLInRendererForNavigationReplacement(
                  frame_tree_node_->current_frame_host()) &&
          InitiatorMayObserveSameUrlReplacement());
}

bool NavigationRequest::InitiatorMayObserveSameUrlReplacement() const {
  if (!common_params_->initiator_origin) {
    return true;
  }

  RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host();
  if (common_params_->initiator_origin->IsSameOriginWith(
          current_rfh->GetLastCommittedOrigin())) {
    return true;
  }

  // If the current document is an error page, its origin is opaque, so the
  // comparison above will fail even when the initiator would have been
  // same-origin to the document had it loaded successfully. To allow such an
  // initiator to retry the failed load without leaving the error page in the
  // back/forward list, also compare against the origin derived from the URL
  // that failed to load.
  if (current_rfh->IsErrorDocument() &&
      common_params_->initiator_origin->IsSameOriginWith(
          url::Origin::Create(current_rfh->GetLastCommittedURL()))) {
    return true;
  }

  return false;
}

const std::optional<FencedFrameProperties>&
NavigationRequest::ComputeFencedFrameProperties(
    FencedFramePropertiesNodeSource node_source) const {
  if (node_source == FencedFramePropertiesNodeSource::kFrameTreeRoot &&
      !frame_tree_node_->IsFencedFrameRoot()) {
    // Sometimes nodes other than the frame tree root (urn iframes) have
    // FencedFrameProperties in their navigation requests. When the node source
    // is kFrameTreeRoot and this navigation request is not for the frame tree
    // root, get the properties from the frame tree root.
    return frame_tree_node_->GetFencedFrameProperties(node_source);
  }

  if (fenced_frame_properties_) {
    return fenced_frame_properties_;
  }

  return frame_tree_node_->GetFencedFrameProperties(node_source);
}

const std::optional<base::UnguessableToken>
NavigationRequest::ComputeFencedFrameNonce() const {
  // For partition nonce, all nested frame inside a fenced frame tree should
  // operate on the partition nonce of the frame tree root.
  const std::optional<FencedFrameProperties>& computed_fenced_frame_properties =
      ComputeFencedFrameProperties(
          /*node_source=*/FencedFramePropertiesNodeSource::kFrameTreeRoot);
  if (!computed_fenced_frame_properties.has_value()) {
    return std::nullopt;
  }
  if (!computed_fenced_frame_properties->partition_nonce().has_value()) {
    // It is only possible for there to be `FencedFrameProperties` but no
    // partition nonce in urn iframes (which could indeed be nested inside a
    // fenced frame).
    CHECK(blink::features::IsAllowURNsInIframeEnabled());
    return std::nullopt;
  }
  return computed_fenced_frame_properties->partition_nonce()
      ->GetValueIgnoringVisibility();
}

void NavigationRequest::RenderFallbackContentForObjectTag() {
  // https://whatwg.org/C/iframe-embed-object.html#the-object-element:fallback-content-5:
  // Fallback content is represented by the children of the <object> tag, so it
  // will be rendered in the process of the parent's document.
  CHECK_EQ(blink::FrameOwnerElementType::kObject,
           frame_tree_node_->frame_owner_element_type());
  if (RenderFrameProxyHost* proxy =
          frame_tree_node_->render_manager()->GetProxyToParent()) {
    if (proxy->is_render_frame_proxy_live()) {
      proxy->GetAssociatedRemoteFrame()->RenderFallbackContent();
    }
  } else {
    frame_tree_node_->current_frame_host()
        ->GetAssociatedLocalFrame()
        ->RenderFallbackContent();
  }
}

std::optional<base::UnguessableToken>
NavigationRequest::GetNavigationTokenForDeferringSubframes() {
  CHECK(IsInMainFrame());
  if (!IsSameDocument() ||
      !NavigationTypeUtils::IsHistory(common_params_->navigation_type)) {
    return std::nullopt;
  }
  RenderFrameHostImpl* current_frame_host =
      frame_tree_node_->current_frame_host();
  if (!current_frame_host->has_navigate_event_handler()) {
    return std::nullopt;
  }
  if (commit_params_->is_browser_initiated &&
      !current_frame_host->IsHistoryUserActivationActive()) {
    return std::nullopt;
  }
  return commit_params_->navigation_token;
}

void NavigationRequest::AddDeferredSubframeNavigationThrottle(
    base::WeakPtr<SubframeHistoryNavigationThrottle> throttle) {
  CHECK(IsInMainFrame());
  subframe_history_navigation_throttles_.push_back(throttle);
}

void NavigationRequest::UnblockPendingSubframeNavigationRequestsIfNeeded() {
  // After a main frame same-document history navigation completes successfully,
  // we can resume any corresponding subframe history navigations that were
  // blocked on it.
  base::WeakPtr<NavigationRequest> self = GetWeakPtr();
  for (auto& throttle : subframe_history_navigation_throttles_) {
    if (throttle) {
      throttle->Resume();
      if (!self) {
        return;
      }
    }
  }
  subframe_history_navigation_throttles_.clear();
}

void NavigationRequest::MaybeDispatchNavigateEventForCrossDocumentTraversal() {
  // If this is a cross-document history navigation, notify the renderer to
  // fire the navigate event now that we know which frames are navigating and
  // whether the navigation is same-origin. Note that while the navigate event
  // can normally intercept or cancel a navigation, it has neither of those
  // powers for a cross-document history navigation, and therefore can be
  // dispatched without waiting for a result. The worst it can do is detach the
  // frame asynchronously, which javascript could do at any time anyway.
  if (common_params_->navigation_type !=
      blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT) {
    return;
  }
  // Only fire the navigate event if the destination is same-origin. Because
  // this check is performed at navigation start time, `destination_origin` is
  // based on the pre-redirect URL, which is consistent with the renderer
  // process logic for firing the navigate event for non-history navigations.
  url::Origin destination_origin = url::Origin::Resolve(
      common_params_->url,
      common_params_->initiator_origin.value_or(url::Origin()));
  if (!frame_tree_node_->current_origin().IsSameOriginWith(
          destination_origin)) {
    return;
  }
  frame_tree_node_->current_frame_host()
      ->GetAssociatedLocalFrame()
      ->DispatchNavigateEventForCrossDocumentTraversal(
          common_params_->url, commit_params_->page_state,
          commit_params_->is_browser_initiated);
}

bool NavigationRequest::IsServedFromBackForwardCache() const {
  return is_back_forward_cache_restore_;
}

bool NavigationRequest::IsPageActivation() const {
  return const_cast<NavigationRequest*>(this)->IsPrerenderedPageActivation() ||
         IsServedFromBackForwardCache();
}

bool NavigationRequest::IsNavigatingFromInitialEmptyDocument() const {
  return frame_tree_node_->is_on_initial_empty_document();
}

bool NavigationRequest::IsBlockedByConnectionAllowlist() const {
  return is_blocked_by_connection_allowlist_;
}

std::unique_ptr<NavigationEntryImpl>
NavigationRequest::TakePrerenderNavigationEntry() {
  CHECK(IsPrerenderedPageActivation());
  return std::move(prerender_navigation_state_->prerender_navigation_entry);
}

bool NavigationRequest::IsWaitingForBeforeUnload() {
  return state_ < WILL_START_NAVIGATION;
}

void NavigationRequest::AddDeferredConsoleMessage(
    blink::mojom::ConsoleMessageLevel level,
    std::string message) {
  CHECK_LE(state_, READY_TO_COMMIT);
  console_messages_.push_back(ConsoleMessage{level, std::move(message)});
}

void NavigationRequest::SendDeferredConsoleMessages() {
  for (auto& message : console_messages_) {
    // TODO(crbug.com/40520047): We should have a way of sending console
    // messages to devtools without going through the renderer.
    GetRenderFrameHost()->AddMessageToConsole(message.level,
                                              std::move(message.message));
  }
  console_messages_.clear();
}

std::optional<AgentClusterKey::CrossOriginIsolationKey>
NavigationRequest::ComputeCrossOriginIsolationKey() {
  // If the navigation doesn't have an origin, we cannot create a
  // CrossOriginIsolationKey for it, since it must be tied to an origin.
  url::Origin origin;
  if (state_ < WILL_PROCESS_RESPONSE) {
    origin = GetTentativeOriginAtRequestTime();
  } else {
    std::optional<url::Origin> origin_to_commit = GetOriginToCommit();
    if (!origin_to_commit.has_value()) {
      return std::nullopt;
    }
    origin = origin_to_commit.value();
  }

  // If the final security policies have not been computed yet, return our best
  // guess of what the final CrossOriginIsolationKey will be. This is because
  // we cannot compute the proper CrossOriginIsolationKey for the navigation
  // yet. However, if we return an empty CrossOriginIsolationKey, we might
  // create a spurious speculative RFH due to CrossOriginIsolationKey
  // differences. To avoid this, we return the current CrossOriginIsolationKey
  // if the navigation is same-origin.
  if (!policy_container_builder_->HasComputedPolicies()) {
    if (origin.IsSameOriginWith(frame_tree_node_->current_origin())) {
      // Note: we do not return the override CrossOriginIsolationKey of the
      // PolicyContainer here because it is only set in contexts where
      // SiteInstance switching is impossible (Android WebView). In those cases,
      // spurious speculative RFHs are never created, and it is fine to just
      // return the CrossOriginIsolationKey of the SiteInstance, which should
      // always be null.
      return frame_tree_node_->current_frame_host()
          ->GetSiteInstance()
          ->GetSiteInfo()
          .agent_cluster_key()
          .GetCrossOriginIsolationKey();
    }
    return std::nullopt;
  }

  // If the navigation does not have a Document-Isolation-Policy, return an
  // empty CrossOriginIsolationKey.
  // TODO(crbug.com/342365083): Use a CrossOriginIsolationKey when the
  // navigation has COOP and COEP, or happens in cross-origin isolated
  // BrowsingInstance.
  if (policy_container_builder_->FinalPolicies()
          .document_isolation_policy.value ==
      network::mojom::DocumentIsolationPolicyValue::kNone) {
    return std::nullopt;
  }

  // The document we're navigating to has a DocumentIsolationPolicy of
  // "isolate-and-require-corp" or "isolate-and-credentialless". This means that
  // the document requested crossOriginIsolation, so return a cross-origin
  // isolation key with the current origin. Its cross-origin isolation mode
  // depends on the capabilities of the platform.  Currently, we only support a
  // cross-origin isolation mode of kConcrete and platforms with full Site
  // Isolation.
  // TODO(crbug.com/342364564): Support platforms that do not
  // support OOPIF and return an AgentClusterKey with a CrossOriginIsolationKey
  // that has a kLogical cross-origin isolation mode.
  CHECK(policy_container_builder_->FinalPolicies()
                .document_isolation_policy.value ==
            network::mojom::DocumentIsolationPolicyValue::
                kIsolateAndRequireCorp ||
        policy_container_builder_->FinalPolicies()
                .document_isolation_policy.value ==
            network::mojom::DocumentIsolationPolicyValue::
                kIsolateAndCredentialless);

  // Inform the PolicyContainer that DocumentIsolationPolicy has enabled
  // crossOriginIsolation for the document.
  policy_container_builder_->SetCrossOriginIsolationEnabledByDIP();

  blink::mojom::CrossOriginIsolationMode coi_mode =
      GetContentClient()->browser()->OriginSupportsConcreteCrossOriginIsolation(
          GetNavigationController()->GetBrowserContext(), origin)
          ? blink::mojom::CrossOriginIsolationMode::kConcrete
          : blink::mojom::CrossOriginIsolationMode::kLogical;
  return AgentClusterKey::CrossOriginIsolationKey(
      origin, coi_mode,
      /*cross_origin_isolated_through_dip=*/true);
}

std::optional<WebExposedIsolationInfo>
NavigationRequest::ComputeWebExposedIsolationInfo() {
  // If we are in an iframe, we inherit the isolation state of the top level
  // frame. This can be inferred from the main frame SiteInstance. Note that
  // Iframes have to pass COEP tests in |OnResponseStarted| before being loaded
  // and inheriting this cross-origin isolated state.
  //
  // Embedded content that cannot always provide a separate process (Fenced
  // frames) should use the crossOriginIsolated state of their
  // parent. Therefore we use IsOutermostMainFrame.
  //
  // TODO(crbug.com/40180791): This may change as we work out the model for
  // isolation mechanisms beyond "cross-origin isolation".
  if (!frame_tree_node_->IsOutermostMainFrame()) {
    return frame_tree_node_->current_frame_host()
        ->GetMainFrame()
        ->GetSiteInstance()
        ->GetWebExposedIsolationInfo();
  }

  // This accommodates for web tests that use COOP. They expect an about:blank
  // page to stay in process, and hang otherwise. In general, it is safe to
  // allow about:blank pages to stay in process, since scriptability is limited
  // to the BrowsingInstance and all pages with the same web-exposed isolation
  // level are trusted.
  if (common_params_->url.IsAboutBlank())
    return std::nullopt;

  // If we haven't yet received a definitive network response, it is too early
  // to guess the isolation state.
  if (state_ < WILL_PROCESS_RESPONSE)
    return std::nullopt;

  // We consider navigations to be cross-origin isolated if the response
  // asserts proper COOP and COEP headers.
  if ((coop_status().current_coop().value !=
       network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep)) {
    return WebExposedIsolationInfo::CreateNonIsolated();
  }

  const GURL& url = common_params().url;

  // TODO(https://crbug.com/415943168): This should take into account Sandbox
  // Flags. However, we can only do so when the CrossOriginOpenerPolicyStatus
  // also take them into account. Because the CrossOriginOpenerPolicyStatus does
  // not take into account sandbox flags, it does not mandate a BrowsingInstance
  // switch when navigating between two same-origin pages where one of the pages
  // has sandbox flags that make its origin opaque. So the two pages are going
  // to commit in the same BrowsingInstance. If we use an opaque origin here, we
  // would end up with a mismatch between the WebExposedIsolationInfo for the
  // navigation and that of the BrowsingInstance it is set to commit into.
  const url::Origin origin = GetOriginForURLLoaderFactoryUnchecked();

  return SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel(
             GetNavigationController()->GetBrowserContext(), url)
             ? WebExposedIsolationInfo::CreateIsolatedApplication(origin)
             : WebExposedIsolationInfo::CreateIsolated(origin);
}

void NavigationRequest::MaybeAssignInvalidActivatingPrerenderHostId() {
  if (!activating_prerender_host_id_.has_value()) {
    // This navigation won't activate a prerendered page. Otherwise,
    // `activating_prerender_host_id_` should have already been set before this
    // in OnPrerenderingActivationChecksComplete().
    activating_prerender_host_id_ = PrerenderHostId();
  }
}

void NavigationRequest::RendererCancellationWindowEnded() {
  // The renderer had indicated that the navigation cancellation window had
  // ended, so the navigation can resume if it is currently waiting for this
  // signal.
  renderer_cancellation_window_ended_ = true;
  renderer_cancellation_listener_.reset();
  if (renderer_cancellation_window_ended_callback_) {
    std::move(renderer_cancellation_window_ended_callback_).Run();
    // DO NOT ADD CODE after this. The callback triggers
    // RendererCancellationThrottle::NavigationCancellationWindowEnded() and
    // eventually NavigationThrottle::Resume(), which might have destroyed the
    // NavigationRequest.
  }
}

bool NavigationRequest::ShouldWaitForRendererCancellationWindowToEnd() {
  return renderer_cancellation_listener_.is_bound() &&
         !renderer_cancellation_window_ended_;
}

NavigationRequest::ScopedCrashKeys::ScopedCrashKeys(
    NavigationRequest& navigation_request)
    : initiator_origin_(
          GetNavigationRequestInitiatorCrashKey(),
          base::OptionalToPtr(navigation_request.GetInitiatorOrigin())),
      url_(GetNavigationRequestUrlCrashKey(), navigation_request.GetURL()),
      is_same_document_(
          GetNavigationRequestIsSameDocumentCrashKey(),
          navigation_request.IsSameDocument() ? "same-doc" : "cross-doc") {}

NavigationRequest::ScopedCrashKeys::~ScopedCrashKeys() = default;

PreloadingTriggerType NavigationRequest::GetPrerenderTriggerType() {
  CHECK(reserved_prerender_host_info_.has_value());
  return reserved_prerender_host_info_->trigger_type;
}

std::string NavigationRequest::GetPrerenderHistogramSuffix() {
  CHECK(reserved_prerender_host_info_.has_value());
  return reserved_prerender_host_info_->histogram_suffix;
}

bool NavigationRequest::IsPrerenderHostReused() {
  CHECK(reserved_prerender_host_info_.has_value());
  return reserved_prerender_host_info_->is_prerender_host_reused;
}

#if BUILDFLAG(IS_ANDROID)
const base::android::JavaRef<jobject>&
NavigationRequest::GetJavaNavigationHandle() {
  return navigation_handle_proxy_->java_navigation_handle();
}
#endif

void NavigationRequest::SetViewTransitionState(
    const url::Origin& source_origin,
    std::unique_ptr<ScopedViewTransitionResources> resources,
    blink::ViewTransitionState view_transition_state) {
  commit_params_->view_transition_state = std::move(view_transition_state);
  CHECK(resources);
  view_transition_resources_ = std::move(resources);
  view_transition_source_origin_ = source_origin;
}

void NavigationRequest::ResetViewTransitionState() {
  if (!commit_params_->view_transition_state) {
    CHECK(!view_transition_resources_);
    return;
  }

  CHECK(view_transition_resources_);
  commit_params_->view_transition_state.reset();
  view_transition_resources_.reset();

  // If we cached a view transition for the old Document and the transition
  // has been aborted, inform the old Document to discard the pending
  // ViewTransition.
  //
  // Note: If the transition is aborted before the renderer acks the
  // snapshot IPC, we won't have any resources here. The
  // ViewTransitionCommitDeferringCondition is responsible for discarding the
  // pending transition in this case.
  if (auto* previous_rfh =
          RenderFrameHostImpl::FromID(GetPreviousRenderFrameHostId())) {
    previous_rfh->GetAssociatedLocalFrame()
        ->NotifyViewTransitionAbortedToOldDocument();
  }
}

blink::RuntimeFeatureStateContext&
NavigationRequest::GetMutableRuntimeFeatureStateContext() {
  // runtime_feature_state_context_ shouldn't be modified after READY_TO_COMMIT
  // as its state has already been sent to the renderer.
  CHECK_LE(state_, NavigationState::READY_TO_COMMIT);
  return runtime_feature_state_context_;
}

const blink::RuntimeFeatureStateContext&
NavigationRequest::GetRuntimeFeatureStateContext() const {
  return runtime_feature_state_context_;
}

ProcessSelectionUserData& NavigationRequest::GetProcessSelectionUserData() {
  return process_selection_user_data_;
}

// The NavigationDownloadPolicy is currently computed by the renderer process.
// The problem: not every navigation are initiated from the renderer. Most
// fields from the bitfield can be computed from the browser process. This
// function is a partial attempt at doing it.
void NavigationRequest::ComputeDownloadPolicy() {
  // [ViewSource]
  if (GetNavigationEntry() && GetNavigationEntry()->IsViewSourceMode()) {
    download_policy().SetDisallowed(blink::NavigationDownloadType::kViewSource);
  }

  // [Sandbox]
  if ((commit_params_->frame_policy.sandbox_flags &
       network::mojom::WebSandboxFlags::kDownloads) ==
      network::mojom::WebSandboxFlags::kDownloads) {
    download_policy().SetDisallowed(blink::NavigationDownloadType::kSandbox);
  }

  // [OpenerCrossOrigin]
  bool is_cross_origin =
      GetInitiatorOrigin() && !GetInitiatorOrigin()->IsSameOriginWith(
                                  frame_tree_node_->current_origin());

  if (is_opener_navigation_ && is_cross_origin) {
    download_policy().SetDisallowed(
        blink::NavigationDownloadType::kOpenerCrossOrigin);
  }

  // TODO(arthursonzogni): Check if the following fields from the
  // NavigationDownloadPolicy could be computed here from the browser process
  // instead:
  //
  // [NoGesture]
  // [AdFrameNoGesture]
  // [AdFrame]
  // [Interstitial]
}

bool NavigationRequest::ShouldQueueDueToExistingPendingCommitRFH() const {
  CHECK_EQ(this, frame_tree_node_->navigation_request());
  CHECK(state_ < READY_TO_COMMIT || state_ == WILL_FAIL_REQUEST);

  if (RenderFrameHostImpl* speculative_rfh =
          frame_tree_node_->render_manager()->speculative_frame_host()) {
    // Queue the navigation if there is a pending commit RenderFrameHost.
    return speculative_rfh->HasPendingCommitForCrossDocumentNavigation();
  }
  return false;
}

void NavigationRequest::RecordMetricsForBlockedGetFrameHostAttempt(
    bool commit_attempt) {
  CHECK(!pending_commit_metrics_.start_time.is_null());
  ++pending_commit_metrics_.blocked_count;
  if (commit_attempt) {
    ++pending_commit_metrics_.blocked_commit_count;
  }
}

void NavigationRequest::PostResumeCommitTask() {
  CHECK(!ShouldQueueDueToExistingPendingCommitRFH());
  // TODO(crbug.com/40186427): Add some metrics for how often:
  // - this is run
  // - how long navigations remain queued
  // - how often it ends up having to simply re-queue itself
  if (resume_commit_closure_) {
    // Post a task so that we resume the navigation asynchronously. Note
    // that we're guaranteed to not have a new RFH get into a pending commit
    // stage in between the time we post this task and the time we run it.
    // `this` is the previously-queued NavigationRequest and is still owned by
    // the `FrameTreeNode`. If a new `NavigationRequest` is created, it will
    // replace and delete `this` and the resume callback for `this` will be
    // skipped.
    base::SequencedTaskRunner::GetCurrentDefault()->PostNonNestableTask(
        FROM_HERE, std::move(resume_commit_closure_));
  }
}

void NavigationRequest::CheckSoftNavigationHeuristicsInvariants() {
  if (!commit_params_->soft_navigation_heuristics_task_id) {
    return;
  }
  // TODO(crbug.com/40283341): Checking for a restore here, to
  // accommodate for restored same document navigations. They are currently
  // NOT executed as same-document. The task ID is cleared to ensure it never
  // leak toward a different document.
  if (IsRestore()) {
    CHECK(!IsSameDocument());
    commit_params_->soft_navigation_heuristics_task_id = {};
    return;
  }

  // In NavigationControllerImpl::NavigateToExistingPendingEntry we're verifying
  // that the task ID is only passed along if the initiator RFH is the same as
  // the navigated RFH.
  //
  // TODO(https://crbug.com/526542223): CHECK-exclusion: Convert to CHECK once
  // we are sure this isn't hit.
  DCHECK(IsSameDocument());
  DCHECK(IsInMainFrame());
  DCHECK(!frame_tree_node()->IsFencedFrameRoot());
}

StoragePartition* NavigationRequest::GetStoragePartitionWithCurrentSiteInfo() {
  // `site_info_`'s StoragePartitionConfig should refer to the correct
  // `StoragePartition` for this navigation.
  return frame_tree_node_->navigator()
      .controller()
      .GetBrowserContext()
      ->GetStoragePartition(site_info_.GetStoragePartitionConfig());
}

void NavigationRequest::CreateWebUIIfNeeded(RenderFrameHostImpl* frame_host) {
  TRACE_EVENT("content", "NavigationRequest::CreateWebUI", "url", GetURL());

  WebUI::TypeID new_web_ui_type =
      WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
          frame_tree_node_->navigator().controller().GetBrowserContext(),
          GetURL());
  if (new_web_ui_type == WebUI::kNoWebUI) {
    // The navigation doesn't need a WebUI.
    return;
  }
  CHECK(!web_ui_);

  // We reuse WebUI on navigations with the same WebUI type where we use the
  // same RFH, so don't create a new one if there is already an existing WebUI
  // in `frame_host`. However, it is useful to verify that its type hasn't
  // changed. Site isolation guarantees that RenderFrameHostImpl will be changed
  // if the WebUI type differs.
  if (frame_host && frame_host->web_ui()) {
    CHECK_EQ(new_web_ui_type, frame_host->web_ui_type());
    return;
  }

  web_ui_ = std::make_unique<WebUIImpl>(this);
  std::unique_ptr<WebUIController> controller(
      WebUIControllerFactoryRegistry::GetInstance()
          ->CreateWebUIControllerForURL(web_ui_.get(), GetURL()));

  // If we have assigned (zero or more) bindings to the NavigationEntry in
  // the past, make sure we're not granting it different bindings than it
  // had before. If so, note it and don't give it any bindings, to avoid a
  // potential privilege escalation.
  if (bindings().has_value() && bindings().value() != web_ui_->GetBindings()) {
    RecordAction(base::UserMetricsAction("ProcessSwapBindingsMismatch_RVHM"));
    base::WeakPtr<NavigationRequest> self = GetWeakPtr();
    // Reset `controller` first before resetting `web_ui_`, since the controller
    // still has a pointer to `web_ui_`, to avoid referencing to the already
    // deleted  `web_ui_` object from `controller`'s destructor. See also
    // https://crbug.com/345640549.
    controller.reset();
    web_ui_.reset();
    // Resetting the WebUI may indirectly call content's embedders and delete
    // `this`. There are no known occurrences of it, so we assume this never
    // happen and crash immediately if it does, because there are no easy ways
    // to recover.
    CHECK(self);
    return;
  }

  web_ui_->SetController(std::move(controller));
}

bool NavigationRequest::IsDeferred() {
  return !throttle_registry_->GetDeferringThrottles().empty();
}

void NavigationRequest::OnResponseBodyReady(MojoResult) {
  size_t available_bytes = 0;
  MojoResult result = response_body().ReadData(
      MOJO_READ_DATA_FLAG_QUERY, base::span<uint8_t>(), available_bytes);
  CHECK_EQ(result, MOJO_RESULT_OK);

  std::string response_body_contents(available_bytes, '\0');
  size_t actually_read_bytes = 0;
  result = response_body().ReadData(
      MOJO_READ_DATA_FLAG_PEEK,
      base::as_writable_byte_span(response_body_contents), actually_read_bytes);
  switch (result) {
    case MOJO_RESULT_OK:
      // The watcher is reset before calling the callback since the callback may
      // resume the throttle. If the watcher is still active, resumption via
      // callback results in running the OnceCallback twice.
      response_body_watcher_.reset();
      response_body_contents.resize(actually_read_bytes);
      std::move(response_body_callback_).Run(std::move(response_body_contents));
      break;
    case MOJO_RESULT_SHOULD_WAIT:
      response_body_watcher_->ArmOrNotify();
      break;
    default:
      // The watcher is reset before calling the callback since the callback may
      // resume the throttle. If the watcher is still active, resumption via
      // callback results in running the OnceCallback twice.
      response_body_watcher_.reset();
      // The client throttle may be waiting for the response body before
      // resuming navigation, so call the callback with an empty response body
      // to unblock the throttle.
      std::move(response_body_callback_).Run(std::string());
      break;
  }
}

void NavigationRequest::RecordEarlyRenderFrameHostSwapMetrics() {
  base::UmaHistogramEnumeration("Navigation.EarlyRenderFrameHostSwapType",
                                early_render_frame_host_swap_type_);
  if (early_render_frame_host_swap_type_ !=
      NavigationRequest::EarlyRenderFrameHostSwapType::kNone) {
    base::UmaHistogramBoolean(
        "Navigation.EarlyRenderFrameHostSwap.HasCommitted", HasCommitted());
    base::UmaHistogramBoolean(
        "Navigation.EarlyRenderFrameHostSwap.IsInOutermostMainFrame",
        IsInOutermostMainFrame());
  }
}

url::Origin NavigationRequest::GetOriginForURLLoaderFactoryUnchecked() {
  if (DidEncounterError()) {
    // Error pages commit in an opaque origin in the renderer process. If this
    // NavigationRequest resulted in committing an error page, return an opaque
    // origin. We usually derive the precursor for that opaque origin from the
    // destination URL, with one exception: if the error page commits in the
    // current process (e.g., for unrecoverable errors in subframes), we leave
    // the precursor empty. This prevents compromised renderers from gaining
    // access to opaque origins with precursors that aren't normally allowed in
    // the process (crbug.com/502348223).
    if (ComputeErrorPageProcess() == ErrorPageProcess::kCurrentProcess) {
      return url::Origin();
    }
    return url::Origin::Create(common_params().url).DeriveNewOpaqueOrigin();
  }

  // Check if this is loadDataWithBaseUrl (which needs special treatment).
  if (IsLoadDataWithBaseURL()) {
    // A (potentially attacker-controlled) renderer process should not be able
    // to use loadDataWithBaseUrl code path to initiate fetches on behalf of a
    // victim origin (fetches controlled by attacker-provided
    // |common_params.url| data: URL in a victim's origin from the
    // attacker-provided |common_params.base_url_for_data_url|).  Browser
    // process should verify that |common_params.base_url_for_data_url| is empty
    // for all renderer-initiated navigations (e.g. see
    // VerifyBeginNavigationCommonParams), but as a defense-in-depth this is
    // also asserted below.
    // History navigations are exempt from this rule because, although they can
    // be renderer-initiated via the js history API, the renderer does not
    // choose the url being navigated to. A renderer-initiated history
    // navigation may therefore navigate back to a previous browser-initiated
    // loadDataWithBaseUrl.
    CHECK(browser_initiated() ||
          NavigationTypeUtils::IsHistory(common_params().navigation_type));

    // loadDataWithBaseUrl submits a data: |common_params.url| (which has a
    // opaque origin), but commits that URL as if it came from
    // |common_params.base_url_for_data_url|.  See also
    // https://crbug.com/976253.
    return url::Origin::Create(common_params().base_url_for_data_url);
  }

  // Use the cached tentative data origin to commit in the data: URL case. When
  // there are multiple data: URLs in the same SiteInstanceGroup, we can rely on
  // the nonce from the origin and that of the site URL to match, which will let
  // us uniquely identify the correct data: SiteInstance.
  if (common_params().url.SchemeIs(url::kDataScheme) &&
      tentative_data_origin_to_commit_.has_value()) {
    return tentative_data_origin_to_commit_.value();
  }

  // Srcdoc subframes need to inherit their origin from their parent frame.
  if (GetURL().IsAboutSrcdoc()) {
    RenderFrameHostImpl* parent = frame_tree_node()->parent();

    if (parent) {
      return parent->GetLastCommittedOrigin();
    } else {
      // The only path for `parent` to be missing for a srcdoc navigation is if
      // a mainframe renderer executes `location = "about:srcdoc` instead of
      // embedding an <iframe srcdoc="..."></iframe> element; this is covered by
      // NavigationBrowserTest.BlockedSrcDoc* tests. While this will result in
      // an error page, we might still get here via GetURLInfo if the navigation
      // encounters a COOP header. In that case we return the origin of the
      // page that executed the script, knowing that the navigation will fail
      // anyways.
      return frame_tree_node()->current_frame_host()->GetLastCommittedOrigin();
    }
  }

  if (GetURL().SchemeIsBlob()) {
    // Blob URLs either have the origin embedded within the URL, or have a
    // URL -> origin mapping for it saved in the BlobURLRegistry.
    std::optional<int> target_rph_id;
    if (HasRenderFrameHost() && GetRenderFrameHost()->GetProcess()) {
      target_rph_id = GetRenderFrameHost()->GetProcess()->GetDeprecatedID();
    }
    return static_cast<StoragePartitionImpl*>(
               GetStoragePartitionWithCurrentSiteInfo())
        ->GetBlobUrlRegistry()
        ->GetOriginForNavigation(
            GetURL(), common_params().initiator_origin.value_or(url::Origin()),
            target_rph_id);
  }

  // In cases not covered above, URLLoaderFactory should be associated with the
  // origin of |common_params.url| and/or |common_params.initiator_origin|.
  url::Origin resolved_origin = url::Origin::Resolve(
      common_params().url,
      common_params().initiator_origin.value_or(url::Origin()));

  if (common_params().url.SchemeIs(url::kDataScheme)) {
    // Cache the origin for data: URLs, so that its nonce remains stable.
    tentative_data_origin_to_commit_ = resolved_origin;
  }

  return resolved_origin;
}

bool NavigationRequest::HasLoader() const {
  return loader_.get() != nullptr;
}

blink::mojom::PageSwapEventParamsPtr NavigationRequest::WillDispatchPageSwap() {
  CHECK(ShouldDispatchPageSwapEvent());

  did_fire_page_swap_ = true;

  if (did_encounter_cross_origin_redirect_) {
    return nullptr;
  }

  // The `pageswap` event is fired on the old Document to provide information
  // about the new Document. The information shared must be restricted to
  // same-origin Documents.
  const bool is_same_origin =
      frame_tree_node_->current_origin().IsSameOriginWith(
          is_running_potential_prerender_activation_checks_
              ? GetTentativeOriginAtRequestTime()
              : *GetOriginToCommit());
  if (!is_same_origin) {
    return nullptr;
  }

  auto page_swap_event_params = blink::mojom::PageSwapEventParams::New();
  page_swap_event_params->url = common_params_->url;

  switch (common_params_->navigation_type) {
    case blink::mojom::NavigationType::RELOAD:
    case blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE:
      page_swap_event_params->navigation_type =
          blink::mojom::NavigationTypeForNavigationApi::kReload;
      break;

    case blink::mojom::NavigationType::RESTORE:
    case blink::mojom::NavigationType::RESTORE_WITH_POST:
      // When traversing to a restored entry, we use these navigation types.
      // Process them same as traverse navigations.
    case blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT:
      page_swap_event_params->navigation_type =
          blink::mojom::NavigationTypeForNavigationApi::kTraverse;
      page_swap_event_params->page_state = commit_params_->page_state;
      break;

    case blink::mojom::NavigationType::DIFFERENT_DOCUMENT:
      page_swap_event_params->navigation_type =
          common_params_->should_replace_current_entry
              ? blink::mojom::NavigationTypeForNavigationApi::kReplace
              : blink::mojom::NavigationTypeForNavigationApi::kPush;
      break;

    case blink::mojom::NavigationType::HISTORY_SAME_DOCUMENT:
    case blink::mojom::NavigationType::SAME_DOCUMENT:
      NOTREACHED() << "Same-document navigations shouldn't fire pageswap";
  }

  return page_swap_event_params;
}

std::optional<NavigationDiscardReason>
NavigationRequest::GetNavigationDiscardReason() {
  return navigation_discard_reason_;
}

NavigationDiscardReason NavigationRequest::GetTypeForNavigationDiscardReason() {
  if (NavigationTypeUtils::IsReload(common_params_->navigation_type)) {
    return NavigationDiscardReason::kNewReloadNavigation;
  }

  if (NavigationTypeUtils::IsHistory(common_params_->navigation_type)) {
    return NavigationDiscardReason::kNewHistoryNavigation;
  }

  if (IsRendererInitiated()) {
    return NavigationDiscardReason::kNewOtherNavigationRendererInitiated;
  }
  return NavigationDiscardReason::kNewOtherNavigationBrowserInitiated;
}

std::optional<ukm::builders::NavigationTimeline>
NavigationRequest::GetNavigationTimelineUkmBuilder() {
  if (ShouldRecordNavigationTimelineUkm() && IsInMainFrame() &&
      // UKM data is sampled at a frequency of `GetUkmSamplingRate()`.
      base::RandDouble() < GetUkmSamplingRate()) {
    return ukm::builders::NavigationTimeline(GetNextPageUkmSourceId());
  }
  return std::nullopt;
}

bool NavigationRequest::ShouldRecordNavigationTimelineUkm() const {
  return !IsSameDocument() && !IsRestore() &&
         !NavigationTypeUtils::IsHistory(common_params_->navigation_type) &&
         !NavigationTypeUtils::IsReload(common_params_->navigation_type) &&
         (common_params_->url.SchemeIsHTTPOrHTTPS() ||
          common_params_->url.SchemeIs(content::kChromeUIScheme)) &&
         !IsPrerenderedPageActivation();
}

bool NavigationRequest::CanRecordEarlyNavigationFailure() const {
  // `site_info_` reflects the target frame's StoragePartition only after
  // StartNavigation() has run; before that it holds a default configuration.
  // To prevent cross-partition leaks from non-default partitions (e.g., guest
  // <webview>) while keeping metrics for standard primary main frames, require
  // that StartNavigation() has run (`state_ >= WILL_START_REQUEST`) or that
  // the current frame already belongs to the default StoragePartition.
  return state_ >= WILL_START_REQUEST ||
         frame_tree_node_->current_frame_host()
             ->GetSiteInstance()
             ->GetSecurityPrincipal()
             .GetStoragePartitionConfig()
             .is_default();
}

void NavigationRequest::MaybeRecordTraceEventsAndHistograms() {
  if (navigation_handle_timing_.navigation_commit_sent_time.is_null()) {
    return;
  }

  bool record_metrics = ShouldRecordNavigationTimelineUkm();

  CHECK(!blink::IsRendererDebugURL(common_params_->url));
  base::TimeTicks navigation_start_time = common_params_->navigation_start;
  CHECK(!navigation_start_time.is_null());
  const auto& trace_id = breakdown_trace_track_;
  const base::TimeTicks loader_start_time =
      navigation_handle_timing_.loader_start_time;
  const base::TimeTicks first_request_start_time =
      navigation_handle_timing_.first_request_start_time;
  const base::TimeTicks navigation_commit_sent_time =
      navigation_handle_timing_.navigation_commit_sent_time;

#define MAYBE_RECORD_TRACE_AND_HISTOGRAM0(name, begin_time, end_time) \
  do {                                                                \
    if (!begin_time.is_null() && !end_time.is_null() &&               \
        navigation_start_time <= begin_time &&                        \
        end_time <= navigation_commit_sent_time) {                    \
      TRACE_EVENT_BEGIN("navigation", name, trace_id, begin_time);    \
      TRACE_EVENT_END("navigation", trace_id, end_time);              \
      if (record_metrics) {                                           \
        base::UmaHistogramTimes(                                      \
            "Navigation.MainFrame.NewNavigation.IgnoreRestore."       \
            "IsHTTPOrHTTPS." name ".Time2",                           \
            end_time - begin_time);                                   \
      }                                                               \
    }                                                                 \
  } while (0)

#define MAYBE_RECORD_TRACE_AND_HISTOGRAM1(name, begin_time, end_time,        \
                                          arg1_name, arg1_val)               \
  do {                                                                       \
    if (!begin_time.is_null() && !end_time.is_null() &&                      \
        navigation_start_time <= begin_time &&                               \
        end_time <= navigation_commit_sent_time) {                           \
      TRACE_EVENT_BEGIN("navigation", name, trace_id, begin_time, arg1_name, \
                        arg1_val);                                           \
      TRACE_EVENT_END("navigation", trace_id, end_time);                     \
      if (record_metrics) {                                                  \
        base::UmaHistogramTimes(                                             \
            "Navigation.MainFrame.NewNavigation.IgnoreRestore."              \
            "IsHTTPOrHTTPS." name ".Time2",                                  \
            end_time - begin_time);                                          \
      }                                                                      \
    }                                                                        \
  } while (0)

  MAYBE_RECORD_TRACE_AND_HISTOGRAM0("NavigationStartToBeginNavigation",
                                    navigation_start_time,
                                    begin_navigation_time_);
  MAYBE_RECORD_TRACE_AND_HISTOGRAM0("BeginNavigationToLoaderStart",
                                    begin_navigation_time_, loader_start_time);
  MAYBE_RECORD_TRACE_AND_HISTOGRAM1("LoaderStartToReceiveResponse",
                                    loader_start_time, receive_response_time_,
                                    "URL", common_params_->url.spec());

  // `first_fetch_start_time_` can be earlier than
  // `loader_start_time` when Prefetch or Prerendering
  // is enabled. The following UMAs are not recorded in such cases because it
  // will skew the data. Also the following trace events are not recorded in
  // such cases because such traces will not be rendered correctly.
  if (loader_start_time <= first_fetch_start_time_) {
    MAYBE_RECORD_TRACE_AND_HISTOGRAM0(
        "LoaderStartToFetchStart", loader_start_time, first_fetch_start_time_);
    MAYBE_RECORD_TRACE_AND_HISTOGRAM0("FetchStart", first_fetch_start_time_,
                                      first_request_start_time);
    MAYBE_RECORD_TRACE_AND_HISTOGRAM0("ReceiveHeaders",
                                      first_request_start_time,
                                      final_receive_headers_end_time_);
    MAYBE_RECORD_TRACE_AND_HISTOGRAM0("ReceiveHeadersToReceiveResponse",
                                      final_receive_headers_end_time_,
                                      receive_response_time_);
  }

  MAYBE_RECORD_TRACE_AND_HISTOGRAM0("ReceiveResponseToCommitNavigation",
                                    receive_response_time_,
                                    navigation_commit_sent_time);

  TRACE_EVENT_END("navigation", breakdown_trace_track_,
                  navigation_commit_sent_time);  // Navigation StartToCommit
  navigation_start_to_commit_ended_ = true;

  // UKM data is sampled at a frequency of `GetUkmSamplingRate()`.
  if (record_metrics && base::RandDouble() < GetUkmSamplingRate() &&
      !navigation_start_time.is_null() && !begin_navigation_time_.is_null() &&
      !loader_start_time.is_null() && !receive_response_time_.is_null() &&
      navigation_start_time <= begin_navigation_time_ &&
      begin_navigation_time_ <= loader_start_time &&
      loader_start_time <= receive_response_time_ &&
      receive_response_time_ <= navigation_commit_sent_time) {
    ukm::builders::NavigationRequestBreakDown ukm(GetNextPageUkmSourceId());
    ukm.SetNavigationStartToBeginNavigation(
           (begin_navigation_time_ - navigation_start_time).InMilliseconds())
        .SetBeginNavigationToLoaderStart(
            (loader_start_time - begin_navigation_time_).InMilliseconds())
        .SetLoaderStartToReceiveResponse(
            (receive_response_time_ - loader_start_time).InMilliseconds())
        .SetReceiveResponseToCommitNavigation(
            (navigation_commit_sent_time - receive_response_time_)
                .InMilliseconds());

    // To avoid affecting other metrics, we check the following conditions
    // separately. These conditions should usually be true, but there can be
    // uncommon error cases.
    if (!first_fetch_start_time_.is_null() &&
        !first_request_start_time.is_null() &&
        loader_start_time <= first_fetch_start_time_ &&
        first_fetch_start_time_ <= first_request_start_time) {
      ukm.SetLoaderStartToFetchStart(
             (first_fetch_start_time_ - loader_start_time).InMilliseconds())
          .SetFetchStartToRequestStart(
              (first_request_start_time - first_fetch_start_time_)
                  .InMilliseconds());
    }

    ukm.Record(ukm::UkmRecorder::Get());
  }

#undef MAYBE_RECORD_TRACE_AND_HISTOGRAM0
#undef MAYBE_RECORD_TRACE_AND_HISTOGRAM1
}

void NavigationRequest::MaybeRecordNavigationStartAdjustments() {
  // Only record metrics if we have a navigation start time.
  if (common_params().navigation_start.is_null()) {
    return;
  }

  if (IsSameDocument()) {
    // No adjustments are made for same-document navigations.
    CHECK(original_navigation_start_.is_null());
    return;
  }

  // Some navigations do not adjust the start time, in which case
  // `original_navigation_start_` is left as null.
  if (original_navigation_start_.is_null()) {
    base::UmaHistogramEnumeration("Navigation.StartAdjustment.AllFrames",
                                  NavigationStartAdjustmentType::kNone);
    if (IsInPrimaryMainFrame()) {
      base::UmaHistogramEnumeration("Navigation.StartAdjustment.MainFrameOnly",
                                    NavigationStartAdjustmentType::kNone);
    }
    return;
  }

  // Compute the adjustment made and what percentage of the total navigation
  // time it includes (approximating now as the end of the navigation).
  base::TimeDelta adjustment =
      common_params().navigation_start - original_navigation_start_;
  base::TimeDelta original_start_to_finish =
      base::TimeTicks::Now() - original_navigation_start_;
  // Note that in unit tests, all timestamps can be the same. Skip recording
  // duration metrics if no time has elapsed during the navigation.
  if (original_start_to_finish.is_zero()) {
    return;
  }

  // Report the adjustment in UMA metrics specific to the case that occurred.
  NavigationStartAdjustmentType adjustment_type =
      NavigationStartAdjustmentType::kNone;
  std::string histogram_name = "Navigation.StartAdjustment";
  if (navigation_start_adjustment_for_legacy_) {
    // No beforeunload handlers actually run in legacy mode.
    CHECK(!beforeunload_dialog_shown_);
    adjustment_type = NavigationStartAdjustmentType::kLegacyPostTask;
    histogram_name += ".LegacyPostTask";
  } else if (!beforeunload_dialog_shown_) {
    adjustment_type = NavigationStartAdjustmentType::kBeforeUnloadHandlers;
    histogram_name += ".BeforeUnloadHandlers";
  } else {
    adjustment_type = NavigationStartAdjustmentType::kBeforeUnloadDialog;
    histogram_name += ".BeforeUnloadDialog";
  }
  base::UmaHistogramEnumeration("Navigation.StartAdjustment.AllFrames",
                                adjustment_type);
  if (IsInPrimaryMainFrame()) {
    base::UmaHistogramEnumeration("Navigation.StartAdjustment.MainFrameOnly",
                                  adjustment_type);
  }

  // It is currently possible for the adjustment to be negative, due to a bug
  // where the updated start time from an earlier navigation is applied to the
  // current NavigationRequest. See https://crbug.com/385170155.
  if (adjustment.is_negative()) {
    // In this case, report the absolute value of the adjustment in a different
    // per-type metric, without reporting the (ill-defined) percentage or trying
    // to create a trace event.
    histogram_name += ".Negative";
    base::UmaHistogramTimes(histogram_name, adjustment.magnitude());
    return;
  }

  // The duration of the navigation should never be negative. For now, return
  // early if that happens, after reporting a DumpWithoutCrashing. Upgrade this
  // to a CHECK failure if no reports are received by M135.
  if (original_start_to_finish.is_negative()) {
    NOTREACHED() << original_start_to_finish;
  }

  base::UmaHistogramTimes(histogram_name, adjustment);
  size_t percentage = 100 * adjustment / original_start_to_finish;
  base::UmaHistogramPercentage(histogram_name + ".Percentage", percentage);

  // Show trace events indicating where the adjustment occurred in time.
  TRACE_EVENT_BEGIN("navigation", "NavigationStartAdjustment",
                    breakdown_trace_track_, original_navigation_start_,
                    "Percentage", percentage);
  TRACE_EVENT_END(
      "navigation", breakdown_trace_track_,
      common_params().navigation_start);  // NavigationStartAdjustment
}

void NavigationRequest::WillStartBeforeUnload() {
  SetWaitingForRendererResponse();
  beforeunload_phase2_start_time_ = base::TimeTicks().Now();
}

void NavigationRequest::set_beforeunload_phase2_dialog_opened_time(
    const base::TimeTicks& dialog_opened_time) {
  beforeunload_phase2_dialog_opened_time_ = dialog_opened_time;
}

void NavigationRequest::set_beforeunload_phase2_dialog_closed_time(
    const base::TimeTicks& dialog_closed_time) {
  beforeunload_phase2_dialog_closed_time_ = dialog_closed_time;
}

NavigationRequest::Timeline::Timeline() = default;
NavigationRequest::Timeline::Timeline(
    const NavigationRequest::Timeline& timeline) = default;
NavigationRequest::Timeline::~Timeline() = default;

void NavigationRequest::Timeline::MarkFinish() {
  finish = base::TimeTicks().Now();
}

NavigationRequest::Timeline
NavigationRequest::GenerateNavigationTimelineForMetrics(
    const mojom::DidCommitProvisionalLoadParams& params,
    const base::TimeTicks& did_commit_ipc_received_time) {
  NavigationRequest::Timeline timeline;

  // Similar to how `actual_navigation_start` is handled for `timeline.start` in
  // synchronous renderer commits below, the input start time is not implemented
  // for all navigations. For example, synchronous about:blank commits and
  // same-document navigations do not currently set `input_start`, but it's
  // possible to send the actual input start later in the future if it's useful.
  if (!common_params().input_start.is_null()) {
    timeline.user_interaction = common_params().input_start;
  }

  if (is_synchronous_renderer_commit()) {
    // For synchronous renderer commits, the start time in the renderer process
    // should be provided in `actual_navigation_start`.
    if (!common_params().actual_navigation_start.is_null()) {
      timeline.start = common_params().actual_navigation_start;
    } else {
      // If `actual_navigation_start` is unexpectedly missing, fall back to the
      // time this IPC was received.
      timeline.start = did_commit_ipc_received_time;
    }
  } else if (!common_params().actual_navigation_start.is_null()) {
    // Use the actual start time if it is provided, and record how long was
    // spent on beforeunload phase 1 in the initiating renderer (if any).
    timeline.start = common_params().actual_navigation_start;
    if (!begin_params().before_unload_start.is_null()) {
      timeline.beforeunload_phase1_start = begin_params().before_unload_start;
      timeline.beforeunload_phase1_end = begin_params().before_unload_end;
      if (!begin_params().before_unload_dialog_opened.is_null() &&
          !begin_params().before_unload_dialog_closed.is_null()) {
        timeline.beforeunload_phase1_dialog_opened =
            begin_params().before_unload_dialog_opened;
        timeline.beforeunload_phase1_dialog_closed =
            begin_params().before_unload_dialog_closed;
      }
    }
  } else {
    // For any legacy cases where the actual start time isn't provided, fall
    // back to the `navigation_start` used by web-exposed metrics. However, if
    // the navigation start time was adjusted due to beforeunload processing,
    // use the original timestamp to ensure that the trace event start time is
    // still accurate.
    //
    // TODO(alexmos): examine whether these timestamps, as well as their other
    // uses, should use InterProcessTimeTicksConverter since they may come from
    // the renderer process.
    timeline.start = !original_navigation_start_.is_null()
                         ? original_navigation_start_
                         : common_params().navigation_start;
  }

  timeline.navigation_request_creation = creation_time_;
  if (!beforeunload_phase2_start_time_.is_null()) {
    timeline.beforeunload_phase2_start = beforeunload_phase2_start_time_;
    timeline.beforeunload_phase2_end = beforeunload_phase2_end_time_;
    if (!beforeunload_phase2_dialog_opened_time_.is_null() &&
        !beforeunload_phase2_dialog_closed_time_.is_null()) {
      timeline.beforeunload_phase2_dialog_opened =
          beforeunload_phase2_dialog_opened_time_;
      timeline.beforeunload_phase2_dialog_closed =
          beforeunload_phase2_dialog_closed_time_;
    }
  }
  timeline.common_params_start = common_params().navigation_start;
  timeline.begin_navigation = begin_navigation_time_;
  timeline.loader_start = navigation_handle_timing_.loader_start_time;
  if (!IsPageActivation()) {
    // Prerender and bfcache activations should not use the stale loader values
    // from the original commit. (Note: `loader_start` and `receive_response`
    // are both set to fresh values for page activations.)
    timeline.loader_fetch_start = first_fetch_start_time_;
    timeline.loader_receive_headers = final_receive_headers_end_time_;
  }
  timeline.receive_response = receive_response_time_;
  timeline.commit_ipc_sent =
      !IsPageActivation()
          ? navigation_handle_timing_.navigation_commit_sent_time
          : page_activation_commit_time_;

  // Note that we can't use NavigationRequest's
  // navigation_handle_timing_.navigation_commit_received_time because it's
  // not populated yet when this function is called.
  if (!IsPageActivation()) {
    timeline.renderer_commit_ipc_received = params.commit_navigation_start;
    timeline.renderer_did_commit_ipc_sent = params.commit_reply_sent;
    timeline.did_commit_ipc_received = did_commit_ipc_received_time;
  } else {
    // Page activations don't send a commit IPC, so use a zero size interval.
    timeline.did_commit_ipc_received = timeline.commit_ipc_sent;
  }

  // Populates information in `navigation_handle_timing_` from the
  // `NavigationTimeline` so that it can be accessed by PageLoadMetricsObservers
  // via `NavigationRequest::GetNavigationHandleTiming()`.
  UpdateNavigationHandleTimingsFromNavigationTimeline(timeline);

  // Populate the renderer process creation and launch times.
  if (GetRenderFrameHost() && GetRenderFrameHost()->GetProcess()) {
    timeline.renderer_process_created =
        GetRenderFrameHost()->GetProcess()->GetLastInitTime();
    timeline.renderer_process_launched =
        GetRenderFrameHost()->GetProcess()->GetProcessLaunchedTime();
  }

  return timeline;
}

void NavigationRequest::UpdateNavigationHandleTimingsFromNavigationTimeline(
    const NavigationRequest::Timeline& timeline) {
  navigation_handle_timing_.user_interaction = timeline.user_interaction;

  navigation_handle_timing_.actual_navigation_start = timeline.start;

  navigation_handle_timing_.before_unload_dialog_duration =
      std::max(base::TimeDelta(),
               timeline.beforeunload_phase1_dialog_closed -
                   timeline.beforeunload_phase1_dialog_opened) +
      std::max(base::TimeDelta(),
               timeline.beforeunload_phase2_dialog_closed -
                   timeline.beforeunload_phase2_dialog_opened);
}

void NavigationRequest::SanitizeDocumentIsolationPolicyHeader() {
  if (!response_head_) {
    return;
  }

  // If the DocumentIsolationPolicy feature is not enabled, set its
  // DocumentIsolationPolicy to its default value.
  if (!base::FeatureList::IsEnabled(
          network::features::kDocumentIsolationPolicy)) {
    response_head_->parsed_headers->document_isolation_policy =
        network::DocumentIsolationPolicy();
    return;
  }

  // Check if the process model of the platform supports
  // DocumentIsolationPolicy.
  bool document_isolation_policy_supported = false;

  // DocumentIsolationPolicy is supported in full SiteIsolation mode.
  document_isolation_policy_supported |=
      SiteIsolationPolicy::UseDedicatedProcessesForAllSites();

  // When kDocumentIsolationPolicyWithoutSiteIsolation is enabled,
  // DocumentIsolationPolicy is also supported without full SiteIsolation.
  if (base::FeatureList::IsEnabled(
          features::kDocumentIsolationPolicyWithoutSiteIsolation)) {
    document_isolation_policy_supported = true;
  }

  // If the process model cannot support DocumentIsolationPolicy, set the header
  // to its default value.
  if (!document_isolation_policy_supported) {
    response_head_->parsed_headers->document_isolation_policy =
        network::DocumentIsolationPolicy();
    return;
  }

  network::DocumentIsolationPolicy& dip =
      response_head_->parsed_headers->document_isolation_policy;
  bool has_dip_header =
      dip.value != network::mojom::DocumentIsolationPolicyValue::kNone ||
      dip.report_only_value !=
          network::mojom::DocumentIsolationPolicyValue::kNone ||
      dip.reporting_endpoint || dip.report_only_reporting_endpoint;

  // DocumentIsolationPolicy should only be used by secure contexts.
  if (!network::IsUrlPotentiallyTrustworthy(GetURL()) && has_dip_header) {
    response_head_->parsed_headers->document_isolation_policy =
        network::DocumentIsolationPolicy();
    AddDeferredConsoleMessage(
        blink::mojom::ConsoleMessageLevel::kError,
        "The Document-Isolation-Policy header has been ignored because "
        "the URL's origin was untrustworthy. Please deliver the response using "
        "the HTTPS protocol. You can also use the 'localhost' origin "
        "instead. See "
        "https://www.w3.org/TR/powerful-features/"
        "#potentially-trustworthy-origin.");
  }
}

void NavigationRequest::ValidateCommitOrigin(
    const url::Origin& origin_to_commit) {
  NavigationEntryImpl* nav_entry =
      static_cast<NavigationEntryImpl*>(GetNavigationEntry());
  if (!nav_entry) {
    return;
  }

  FrameNavigationEntry* frame_entry =
      nav_entry->GetFrameEntry(frame_tree_node_);
  if (!frame_entry || !frame_entry->committed_origin().has_value()) {
    return;
  }

  const url::Origin& expected_origin = frame_entry->committed_origin().value();
  bool origins_match = false;
  // Current weakened check: allows precursor tuple comparison if *either*
  // origin is opaque. This is a temporary workaround because sandbox
  // navigations do not currently clear PageState properly.
  //
  // TODO(crbug.com/421948889): After this bug is fixed, tighten this check
  // to only allow precursor tuple comparison if *both* origins are opaque.
  if (expected_origin.opaque() || origin_to_commit.opaque()) {
    // Both origins are opaque — compare by their precursor tuples.
    origins_match = expected_origin.GetTupleOrPrecursorTupleIfOpaque() ==
                    origin_to_commit.GetTupleOrPrecursorTupleIfOpaque();
  } else {
    // If either is non-opaque, use regular strict comparison.
    origins_match = expected_origin.IsSameOriginWith(origin_to_commit);
  }

  if (!origins_match) {
    // TODO(crbug.com/420965165): In redirects or other origin-changing
    // cases (e.g., CSP), FrameNavigationEntry may retain a stale
    // committed_origin(). We should clear it when the origin changes, so
    // that if it exists at commit time, it can be trusted to match.
    //
    // In the meantime, it’s safe for a stale committed_origin() to stick
    // around **only** if there’s no origin-related state (e.g., PageState)
    // being sent in commit_params_.
    CHECK(commit_params_->page_state.empty(), base::NotFatalUntil::M140)
        << "PageState wasn't cleared after a commit origin mismatch."
        << "expected_origin: " << expected_origin
        << ", origin_to_commit: " << origin_to_commit;
  }
}

PrerenderHostId NavigationRequest::GetPrerenderHostId() const {
  return prerender_host_id_;
}

void NavigationRequest::SetAsyncBeforeUnloadCommitResumeClosure(
    base::OnceClosure commit_resume_closure) {
  CHECK(!async_before_unload_pending_replies_.empty());
  CHECK(!async_before_unload_commit_resume_closure_);
  async_before_unload_commit_resume_closure_ = std::move(commit_resume_closure);
}

void NavigationRequest::StartAsyncBeforeUnloadTimer() {
  CHECK(!async_before_unload_pending_replies_.empty());
  // Note: The AsyncBeforeUnload event runs in parallel with the navigation
  // setup and is guaranteed to complete before commit. It is shown on a
  // separate track to avoid overlapping with sequential breakdown phases.
  TRACE_EVENT_BEGIN("navigation", "AsyncBeforeUnload",
                    async_before_unload_trace_track_, "Initial URL",
                    common_params_->url.spec());
  async_before_unload_timeout_.Start(
      FROM_HERE, features::kAsyncBeforeUnloadTimeout.Get(),
      base::BindOnce(
          [](base::WeakPtr<NavigationRequest> self) {
            if (!self) {
              return;
            }
            // If the timeout occurs, we proceed with the navigation commit
            // regardless of any pending replies. We pass nullopt to indicate
            // that the timer expired.
            self->async_before_unload_pending_replies_.clear();
            self->MaybeResumeAsyncBeforeUnloadCommit(std::nullopt);
          },
          weak_factory_.GetWeakPtr()));
}

void NavigationRequest::MaybeResumeAsyncBeforeUnloadCommit(
    std::optional<GlobalRenderFrameHostId> acked_rfh_id) {
  if (acked_rfh_id) {
    // Remove the frame that just replied. (If `acked_rfh_id` is nullopt, it
    // means the timer expired).
    async_before_unload_pending_replies_.erase(*acked_rfh_id);
  }

  // All IDs remaining in the pending replies set must correspond to live
  // frames, because RenderFrameHostImpl::~RenderFrameHostImpl handles
  // removing destroyed frames from the set.
  //
  // Using DCHECK because this validation is computationally expensive as it
  // iterates over all pending replies.
  DCHECK(std::ranges::all_of(async_before_unload_pending_replies_,
                             [](const GlobalRenderFrameHostId& id) -> bool {
                               return RenderFrameHostImpl::FromID(id);
                             }));

  if (!async_before_unload_pending_replies_.empty()) {
    // Wait for the remaining reply for beforeunload.
    return;
  }
  TRACE_EVENT_END("navigation",
                  async_before_unload_trace_track_);  // AsyncBeforeUnload
  // Proceed with navigation commit.
  async_before_unload_timeout_.Stop();
  if (async_before_unload_commit_resume_closure_) {
    std::move(async_before_unload_commit_resume_closure_).Run();
  }
}

bool NavigationRequest::IsWaitingForAsyncBeforeUnload() const {
  return !async_before_unload_pending_replies_.empty();
}

bool NavigationRequest::IsInitialWebUINavigation() {
#if !BUILDFLAG(IS_ANDROID)
  return GetContentClient()->browser()->IsInitialWebUIURL(GetURL());
#else
  return false;
#endif
}

perfetto::NamedTrack NavigationRequest::GetNavigationTracingTrack() const {
  return navigation_trace_track_;
}

bool NavigationRequest::HasPrefetchedSignedExchange() const {
  CHECK(base::FeatureList::IsEnabled(features::kNavigationFastFetchDryRun));
  if (!prefetched_signed_exchange_cache_) {
    return false;
  }
  const auto& exchanges = prefetched_signed_exchange_cache_->GetExchanges();
  return exchanges.find(common_params().url) != exchanges.end();
}

}  // namespace content
