// Copyright 2015 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 <optional>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/i18n/number_formatting.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/navigation_throttle_runner.h"
#include "content/browser/url_info.h"
#include "content/common/features.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "content/public/browser/process_selection_user_data.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/web_contents_observer.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/url_constants.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_content_browser_client.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_utils.h"
#include "content/test/fenced_frame_test_utils.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.h"
#include "net/base/features.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.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/origin_trials/scoped_test_origin_trial_policy.h"
#include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_context.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"

namespace content {

namespace {

// A simple ProcessSelectionUserData::Data implementation for testing.
class ProcessSelectionTestData
    : public ProcessSelectionUserData::Data<ProcessSelectionTestData> {
 public:
  explicit ProcessSelectionTestData(int value) : value_(value) {}
  int value() const { return value_; }

 private:
  friend ProcessSelectionUserData::Data<ProcessSelectionTestData>;
  PROCESS_SELECTION_USER_DATA_KEY_DECL();
  int value_;
};

PROCESS_SELECTION_USER_DATA_KEY_IMPL(ProcessSelectionTestData);

}  // namespace

class NavigationRequestTest : public RenderViewHostImplTestHarness {
 public:
  NavigationRequestTest() : callback_result_(NavigationThrottle::DEFER) {}

  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();
    CreateNavigationHandle();
    contents()->GetPrimaryMainFrame()->InitializeRenderFrameIfNeeded();
  }

  void TearDown() override { RenderViewHostImplTestHarness::TearDown(); }

  void CancelDeferredNavigation(
      NavigationThrottle::ThrottleCheckResult result) {
    GetNavigationRequest()->CancelDeferredNavigationInternal(result);
  }

  // Helper function to call WillStartRequest on |handle|. If this function
  // returns DEFER, |callback_result_| will be set to the actual result of
  // the throttle checks when they are finished.
  void SimulateWillStartRequest() {
    was_callback_called_ = false;
    callback_result_ = NavigationThrottle::DEFER;

    // It's safe to use base::Unretained since the NavigationRequest is owned by
    // the NavigationRequestTest.
    GetNavigationRequest()->set_complete_callback_for_testing(
        base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
                       base::Unretained(this)));

    GetNavigationRequest()->WillStartRequest();
  }

  // Helper function to call WillRedirectRequest on |handle|. If this function
  // returns DEFER, |callback_result_| will be set to the actual result of the
  // throttle checks when they are finished.
  // TODO(clamy): this should also simulate that WillStartRequest was called if
  // it has not been called before.
  void SimulateWillRedirectRequest() {
    was_callback_called_ = false;
    callback_result_ = NavigationThrottle::DEFER;

    // It's safe to use base::Unretained since the NavigationRequest is owned by
    // the NavigationRequestTest.
    GetNavigationRequest()->set_complete_callback_for_testing(
        base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
                       base::Unretained(this)));

    GetNavigationRequest()->WillRedirectRequest(
        GURL(), nullptr /* post_redirect_process */);
  }

  // Helper function to call WillFailRequest on |handle|. If this function
  // returns DEFER, |callback_result_| will be set to the actual result of the
  // throttle checks when they are finished.
  void SimulateWillFailRequest(
      net::Error net_error_code,
      const std::optional<net::SSLInfo> ssl_info = std::nullopt) {
    was_callback_called_ = false;
    callback_result_ = NavigationThrottle::DEFER;
    GetNavigationRequest()->set_net_error(net_error_code);

    // It's safe to use base::Unretained since the NavigationRequest is owned by
    // the NavigationRequestTest.
    GetNavigationRequest()->set_complete_callback_for_testing(
        base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
                       base::Unretained(this)));

    GetNavigationRequest()->WillFailRequest();
  }

  // Helper function to call WillCommitWithoutUrlLoader on |handle|. If this
  // function returns DEFER, |callback_result_| will be set to the actual result
  // of the throttle checks when they are finished.
  void SimulateWillCommitWithoutUrlLoader() {
    was_callback_called_ = false;
    callback_result_ = NavigationThrottle::DEFER;

    // It's safe to use base::Unretained since the NavigationRequest is owned by
    // the NavigationRequestTest.
    GetNavigationRequest()->set_complete_callback_for_testing(
        base::BindOnce(&NavigationRequestTest::UpdateThrottleCheckResult,
                       base::Unretained(this)));

    GetNavigationRequest()->ComputePoliciesToCommit();
    GetNavigationRequest()->WillCommitWithoutUrlLoader();
  }

  // Whether the callback was called.
  bool was_callback_called() const { return was_callback_called_; }

  // Returns the callback_result.
  NavigationThrottle::ThrottleCheckResult callback_result() const {
    return callback_result_;
  }

  NavigationRequest::NavigationState state() {
    return GetNavigationRequest()->state();
  }

  bool call_counts_match(TestNavigationThrottle* throttle,
                         int start,
                         int redirect,
                         int failure,
                         int process,
                         int withoutUrlLoader) {
    return start == throttle->GetCallCount(
                        TestNavigationThrottle::WILL_START_REQUEST) &&
           redirect == throttle->GetCallCount(
                           TestNavigationThrottle::WILL_REDIRECT_REQUEST) &&
           failure == throttle->GetCallCount(
                          TestNavigationThrottle::WILL_FAIL_REQUEST) &&
           process == throttle->GetCallCount(
                          TestNavigationThrottle::WILL_PROCESS_RESPONSE) &&
           withoutUrlLoader ==
               throttle->GetCallCount(
                   TestNavigationThrottle::WILL_COMMIT_WITHOUT_URL_LOADER);
  }

  // Creates, register and returns a TestNavigationThrottle that will
  // synchronously return |result| on checks by default.
  TestNavigationThrottle* CreateTestNavigationThrottle(
      NavigationThrottle::ThrottleCheckResult result) {
    TestNavigationThrottle* test_throttle = new TestNavigationThrottle(
        *GetNavigationRequest()->GetNavigationThrottleRegistryForTesting());
    test_throttle->SetResponseForAllMethods(TestNavigationThrottle::SYNCHRONOUS,
                                            result);
    GetNavigationRequest()->RegisterThrottleForTesting(
        std::unique_ptr<TestNavigationThrottle>(test_throttle));
    return test_throttle;
  }

  // Creates, register and returns a TestNavigationThrottle that will
  // synchronously return |result| on check for the given |method|, and
  // NavigationThrottle::PROCEED otherwise.
  TestNavigationThrottle* CreateTestNavigationThrottle(
      TestNavigationThrottle::ThrottleMethod method,
      NavigationThrottle::ThrottleCheckResult result) {
    TestNavigationThrottle* test_throttle =
        CreateTestNavigationThrottle(NavigationThrottle::PROCEED);
    test_throttle->SetResponse(method, TestNavigationThrottle::SYNCHRONOUS,
                               result);
    return test_throttle;
  }

  // TODO(zetamoo): Use NavigationSimulator instead of creating
  // NavigationRequest and NavigationHandleImpl.
  void CreateNavigationHandle() {
    auto common_params = blink::CreateCommonNavigationParams();
    common_params->initiator_origin =
        url::Origin::Create(GURL("https://initiator.example.com"));
    auto commit_params = blink::CreateCommitNavigationParams();
    commit_params->frame_policy =
        main_test_rfh()->frame_tree_node()->pending_frame_policy();
    auto request = NavigationRequest::CreateBrowserInitiated(
        main_test_rfh()->frame_tree_node(), std::move(common_params),
        std::move(commit_params), false /* was_opener_suppressed */,
        std::string() /* extra_headers */, nullptr /* frame_entry */,
        nullptr /* entry */, false /* is_form_submission */,
        nullptr /* navigation_ui_data */, EmbedderIsolationInfo::Mode::kNone);
    main_test_rfh()->frame_tree_node()->TakeNavigationRequest(
        std::move(request));
    GetNavigationRequest()->StartNavigation();
  }

  // Builds a browser-initiated subframe NavigationRequest directly.
  // NavigationSimulator would invoke process selection, which has stricter
  // setup than these EmbedderIsolationInfo propagation tests exercise.
  std::unique_ptr<NavigationRequest> CreateSubframeNavigationRequest(
      FrameTreeNode* child_node,
      const GURL& url) {
    auto common_params = blink::CreateCommonNavigationParams();
    common_params->url = url;
    common_params->method = "GET";
    auto commit_params = blink::CreateCommitNavigationParams();
    commit_params->original_url = url;
    commit_params->frame_policy = child_node->pending_frame_policy();
    return NavigationRequest::CreateBrowserInitiated(
        child_node, std::move(common_params), std::move(commit_params),
        /*was_opener_suppressed=*/false, /*extra_headers=*/std::string(),
        /*frame_entry=*/nullptr, /*entry=*/nullptr,
        /*is_form_submission=*/false, /*navigation_ui_data=*/nullptr,
        EmbedderIsolationInfo::Mode::kNone);
  }

  FrameTreeNode* AddFrame(FrameTree& frame_tree,
                          RenderFrameHostImpl* parent,
                          int process_id,
                          int new_routing_id,
                          const blink::FramePolicy& frame_policy,
                          blink::FrameOwnerElementType owner_type) {
    return frame_tree.AddFrame(
        parent, process_id, new_routing_id,
        TestRenderFrameHost::CreateStubFrameRemote(),
        TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
        TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
        TestRenderFrameHost::CreateStubAssociatedInterfaceProviderReceiver(),
        blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName0",
        false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
        blink::DocumentToken(), frame_policy,
        blink::mojom::FrameOwnerProperties(), false, owner_type,
        /*is_dummy_frame_for_inner_tree=*/false);
  }

 private:
  // The callback provided to NavigationRequest::WillStartRequest,
  // NavigationRequest::WillRedirectRequest, and
  // NavigationRequest::WillFailRequest during the tests.
  bool UpdateThrottleCheckResult(
      NavigationThrottle::ThrottleCheckResult result) {
    callback_result_ = result;
    was_callback_called_ = true;
    return true;
  }

  // This must be called after CreateNavigationHandle().
  NavigationRequest* GetNavigationRequest() {
    return main_test_rfh()->frame_tree_node()->navigation_request();
  }

  bool was_callback_called_ = false;
  NavigationThrottle::ThrottleCheckResult callback_result_;
};

// Checks that the request_context_type is properly set.
// Note: can be extended to cover more internal members.
TEST_F(NavigationRequestTest, SimpleDataChecksRedirectAndProcess) {
  const GURL kUrl1 = GURL("http://chromium.org");
  const GURL kUrl2 = GURL("http://google.com");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl1, main_rfh());
  navigation->Start();
  EXPECT_EQ(blink::mojom::RequestContextType::LOCATION,
            NavigationRequest::From(navigation->GetNavigationHandle())
                ->request_context_type());
  EXPECT_EQ(net::HttpConnectionInfo::kUNKNOWN,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  navigation->set_http_connection_info(net::HttpConnectionInfo::kHTTP1_1);
  navigation->Redirect(kUrl2);
  EXPECT_EQ(blink::mojom::RequestContextType::LOCATION,
            NavigationRequest::From(navigation->GetNavigationHandle())
                ->request_context_type());
  EXPECT_EQ(net::HttpConnectionInfo::kHTTP1_1,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  navigation->set_http_connection_info(net::HttpConnectionInfo::kQUIC_35);
  navigation->ReadyToCommit();
  EXPECT_EQ(blink::mojom::RequestContextType::LOCATION,
            NavigationRequest::From(navigation->GetNavigationHandle())
                ->request_context_type());
  EXPECT_EQ(net::HttpConnectionInfo::kQUIC_35,
            navigation->GetNavigationHandle()->GetConnectionInfo());
}

TEST_F(NavigationRequestTest, SimpleDataCheckNoRedirect) {
  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  navigation->Start();
  EXPECT_EQ(net::HttpConnectionInfo::kUNKNOWN,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  navigation->set_http_connection_info(net::HttpConnectionInfo::kQUIC_35);
  navigation->ReadyToCommit();
  EXPECT_EQ(net::HttpConnectionInfo::kQUIC_35,
            navigation->GetNavigationHandle()->GetConnectionInfo());
}

TEST_F(NavigationRequestTest, SimpleDataChecksFailure) {
  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  navigation->Start();
  EXPECT_EQ(blink::mojom::RequestContextType::LOCATION,
            NavigationRequest::From(navigation->GetNavigationHandle())
                ->request_context_type());
  EXPECT_EQ(net::HttpConnectionInfo::kUNKNOWN,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  navigation->Fail(net::ERR_CERT_DATE_INVALID);
  EXPECT_EQ(blink::mojom::RequestContextType::LOCATION,
            NavigationRequest::From(navigation->GetNavigationHandle())
                ->request_context_type());
  EXPECT_EQ(net::ERR_CERT_DATE_INVALID,
            navigation->GetNavigationHandle()->GetNetErrorCode());
}

// Checks that `ShouldRecordNavigationTimelineUkm` returns true for `chrome://`
// URLs.
TEST_F(NavigationRequestTest, ShouldRecordNavigationTimelineUkmForChromeUI) {
  const GURL kUrl = GURL("chrome://webui-toolbar.top-chrome/");
  auto navigation =
      NavigationSimulator::CreateBrowserInitiated(kUrl, web_contents());
  navigation->Start();

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());

  EXPECT_TRUE(request->ShouldRecordNavigationTimelineUkm());
}

// Checks that a navigation deferred during WillStartRequest can be properly
// cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillStart) {
  TestNavigationThrottle* test_throttle =
      CreateTestNavigationThrottle(NavigationThrottle::DEFER);
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 0));

  // Simulate WillStartRequest. The request should be deferred. The callback
  // should not have been called.
  SimulateWillStartRequest();
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0, 0));

  // Cancel the request. The callback should have been called.
  CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
  EXPECT_EQ(NavigationRequest::CANCELING, state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0, 0));
}

// Checks that a navigation deferred during WillRedirectRequest can be properly
// cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillRedirect) {
  TestNavigationThrottle* test_throttle =
      CreateTestNavigationThrottle(NavigationThrottle::DEFER);
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 0));

  // Simulate WillRedirectRequest. The request should be deferred. The callback
  // should not have been called.
  SimulateWillRedirectRequest();
  EXPECT_EQ(NavigationRequest::WILL_REDIRECT_REQUEST, state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 1, 0, 0, 0));

  // Cancel the request. The callback should have been called.
  CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
  EXPECT_EQ(NavigationRequest::CANCELING, state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 1, 0, 0, 0));
}

// Checks that a navigation deferred during WillFailRequest can be properly
// cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillFail) {
  TestNavigationThrottle* test_throttle = CreateTestNavigationThrottle(
      TestNavigationThrottle::WILL_FAIL_REQUEST, NavigationThrottle::DEFER);
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 0));

  // Simulate WillStartRequest.
  SimulateWillStartRequest();
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0, 0));

  // Simulate WillFailRequest. The request should be deferred. The callback
  // should not have been called.
  SimulateWillFailRequest(net::ERR_CERT_DATE_INVALID);
  EXPECT_EQ(NavigationRequest::WILL_FAIL_REQUEST, state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0, 0));

  // Cancel the request. The callback should have been called.
  CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
  EXPECT_EQ(NavigationRequest::CANCELING, state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0, 0));
}

// Checks that a navigation deferred can be canceled and not ignored.
TEST_F(NavigationRequestTest, CancelDeferredWillRedirectNoIgnore) {
  TestNavigationThrottle* test_throttle =
      CreateTestNavigationThrottle(NavigationThrottle::DEFER);
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 0));

  // Simulate WillStartRequest. The request should be deferred. The callback
  // should not have been called.
  SimulateWillStartRequest();
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0, 0));

  // Cancel the request. The callback should have been called with CANCEL, and
  // not CANCEL_AND_IGNORE.
  CancelDeferredNavigation(NavigationThrottle::CANCEL);
  EXPECT_EQ(NavigationRequest::CANCELING, state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(NavigationThrottle::CANCEL, callback_result());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0, 0));
}

// Checks that a navigation deferred by WillFailRequest can be canceled and not
// ignored.
TEST_F(NavigationRequestTest, CancelDeferredWillFailNoIgnore) {
  TestNavigationThrottle* test_throttle = CreateTestNavigationThrottle(
      TestNavigationThrottle::WILL_FAIL_REQUEST, NavigationThrottle::DEFER);
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 0));

  // Simulate WillStartRequest.
  SimulateWillStartRequest();
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 0, 0, 0));

  // Simulate WillFailRequest. The request should be deferred. The callback
  // should not have been called.
  SimulateWillFailRequest(net::ERR_CERT_DATE_INVALID);
  EXPECT_EQ(NavigationRequest::WILL_FAIL_REQUEST, state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0, 0));

  // Cancel the request. The callback should have been called with CANCEL, and
  // not CANCEL_AND_IGNORE.
  CancelDeferredNavigation(NavigationThrottle::CANCEL);
  EXPECT_EQ(NavigationRequest::CANCELING, state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(NavigationThrottle::CANCEL, callback_result());
  EXPECT_TRUE(call_counts_match(test_throttle, 1, 0, 1, 0, 0));
}

// Checks that a navigation deferred during WillCommitWithoutUrlLoader can be
// properly cancelled.
TEST_F(NavigationRequestTest, CancelDeferredWillCommitWithoutUrlLoader) {
  TestNavigationThrottle* test_throttle =
      CreateTestNavigationThrottle(NavigationThrottle::DEFER);
  EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, state());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 0));

  // Simulate WillCommitWithoutUrlLoader. The request should be deferred. The
  // callback should not have been called.
  SimulateWillCommitWithoutUrlLoader();
  EXPECT_EQ(NavigationRequest::WILL_COMMIT_WITHOUT_URL_LOADER, state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 1));

  // Cancel the request. The callback should have been called.
  CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
  EXPECT_EQ(NavigationRequest::CANCELING, state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(NavigationThrottle::CANCEL_AND_IGNORE, callback_result());
  EXPECT_TRUE(call_counts_match(test_throttle, 0, 0, 0, 0, 1));
}

// Checks that data from the SSLInfo passed into SimulateWillStartRequest() is
// stored on the handle.
TEST_F(NavigationRequestTest, WillFailRequestSetsSSLInfo) {
  uint16_t cipher_suite = 0xc02f;  // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  int connection_status = 0;
  net::SSLConnectionStatusSetCipherSuite(cipher_suite, &connection_status);

  // Set some test values.
  net::SSLInfo ssl_info;
  ssl_info.cert_status = net::CERT_STATUS_AUTHORITY_INVALID;
  ssl_info.connection_status = connection_status;

  const GURL kUrl = GURL("https://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  navigation->SetSSLInfo(ssl_info);
  navigation->Fail(net::ERR_CERT_DATE_INVALID);

  EXPECT_EQ(net::CERT_STATUS_AUTHORITY_INVALID,
            navigation->GetNavigationHandle()->GetSSLInfo()->cert_status);
  EXPECT_EQ(connection_status,
            navigation->GetNavigationHandle()->GetSSLInfo()->connection_status);
}

namespace {

// Helper throttle which checks that it can access NavigationHandle's
// RenderFrameHost in WillFailRequest() and then defers the failure.
class GetRenderFrameHostOnFailureNavigationThrottle
    : public NavigationThrottle {
 public:
  explicit GetRenderFrameHostOnFailureNavigationThrottle(
      NavigationThrottleRegistry& registry)
      : NavigationThrottle(registry) {}

  GetRenderFrameHostOnFailureNavigationThrottle(
      const GetRenderFrameHostOnFailureNavigationThrottle&) = delete;
  GetRenderFrameHostOnFailureNavigationThrottle& operator=(
      const GetRenderFrameHostOnFailureNavigationThrottle&) = delete;

  ~GetRenderFrameHostOnFailureNavigationThrottle() override = default;

  NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
    EXPECT_TRUE(navigation_handle()->GetRenderFrameHost());
    return NavigationThrottle::DEFER;
  }

  const char* GetNameForLogging() override {
    return "GetRenderFrameHostOnFailureNavigationThrottle";
  }
};

class ThrottleTestContentBrowserClient : public ContentBrowserClient {
  void CreateThrottlesForNavigation(
      NavigationThrottleRegistry& registry) override {
    registry.AddThrottle(
        std::make_unique<GetRenderFrameHostOnFailureNavigationThrottle>(
            registry));
  }
};

}  // namespace

// Verify that the NavigationHandle::GetRenderFrameHost() can be retrieved by a
// throttle in WillFailRequest(), as well as after deferring the failure.  This
// is allowed, since at that point the final RenderFrameHost will have already
// been chosen. See https://crbug.com/817881.
TEST_F(NavigationRequestTest, WillFailRequestCanAccessRenderFrameHost) {
  std::unique_ptr<ContentBrowserClient> client(
      new ThrottleTestContentBrowserClient);
  ContentBrowserClient* old_browser_client =
      SetBrowserClientForTesting(client.get());

  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  navigation->SetAutoAdvance(false);
  navigation->Start();
  navigation->Fail(net::ERR_CERT_DATE_INVALID);
  EXPECT_EQ(
      NavigationRequest::WILL_FAIL_REQUEST,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_TRUE(navigation->GetNavigationHandle()->GetRenderFrameHost());
  auto* registry = NavigationRequest::From(navigation->GetNavigationHandle())
                       ->GetNavigationThrottleRegistryForTesting();
  ASSERT_EQ(1u, registry->GetDeferringThrottles().size());
  registry->ResumeProcessingNavigationEvent(
      *registry->GetDeferringThrottles().cbegin());
  EXPECT_TRUE(navigation->GetNavigationHandle()->GetRenderFrameHost());

  SetBrowserClientForTesting(old_browser_client);
}

TEST_F(NavigationRequestTest, PolicyContainerInheritance) {
  struct TestCase {
    const char* url;
    bool expect_inherit;
  } cases[]{{"about:blank", true},
            {"data:text/plain,hello", true},
            {"file://local", false},
            {"http://chromium.org", false}};

  const GURL kUrl1 = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl1, main_rfh());
  navigation->Commit();

  for (auto test : cases) {
    // We navigate child frames because the BlockedSchemeNavigationThrottle
    // restricts navigations in the main frame.
    auto* child_frame = static_cast<TestRenderFrameHost*>(
        content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));

    // We set the referrer policy of the frame to "always". We then create a new
    // navigation, set as initiator the frame itself, start the navigation, and
    // change the referrer policy of the frame to "never". After we commit the
    // navigation:
    // - If navigating to a local scheme, the target frame should have inherited
    //   the referrer policy of the initiator ("always").
    // - If navigating to a non-local scheme, the target frame should have a new
    //   policy container (hence referrer policy set to "default").
    const GURL kUrl = GURL(test.url);
    navigation =
        NavigationSimulatorImpl::CreateRendererInitiated(kUrl, child_frame);
    static_cast<blink::mojom::PolicyContainerHost*>(
        child_frame->policy_container_host())
        ->SetReferrerPolicy(network::mojom::ReferrerPolicy::kAlways,
                            base::UnguessableToken::Create());
    navigation->SetInitiatorFrame(child_frame);
    navigation->Start();
    static_cast<blink::mojom::PolicyContainerHost*>(
        child_frame->policy_container_host())
        ->SetReferrerPolicy(network::mojom::ReferrerPolicy::kNever,
                            base::UnguessableToken::Create());
    navigation->Commit();
    EXPECT_EQ(
        test.expect_inherit ? network::mojom::ReferrerPolicy::kAlways
                            : network::mojom::ReferrerPolicy::kDefault,
        static_cast<RenderFrameHostImpl*>(navigation->GetFinalRenderFrameHost())
            ->policy_container_host()
            ->referrer_policy());
  }
}

TEST_F(NavigationRequestTest, DnsAliasesCanBeAccessed) {
  // Create simulated NavigationRequest for the URL, which has aliases.
  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  std::vector<std::string> dns_aliases({"alias1", "alias2"});
  navigation->SetResponseDnsAliases(std::move(dns_aliases));

  // Start the navigation.
  navigation->Start();
  EXPECT_EQ(net::HttpConnectionInfo::kUNKNOWN,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  // Commit the navigation.
  navigation->set_http_connection_info(net::HttpConnectionInfo::kQUIC_35);
  navigation->ReadyToCommit();
  EXPECT_EQ(net::HttpConnectionInfo::kQUIC_35,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  // Verify that the aliases are accessible from the NavigationRequest.
  EXPECT_THAT(navigation->GetNavigationHandle()->GetDnsAliases(),
              testing::ElementsAre("alias1", "alias2"));
}

TEST_F(NavigationRequestTest, NoDnsAliases) {
  // Create simulated NavigationRequest for the URL, which does not
  // have aliases. (Note the empty alias list.)
  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  std::vector<std::string> dns_aliases;
  navigation->SetResponseDnsAliases(std::move(dns_aliases));

  // Start the navigation.
  navigation->Start();
  EXPECT_EQ(net::HttpConnectionInfo::kUNKNOWN,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  // Commit the navigation.
  navigation->set_http_connection_info(net::HttpConnectionInfo::kQUIC_35);
  navigation->ReadyToCommit();
  EXPECT_EQ(net::HttpConnectionInfo::kQUIC_35,
            navigation->GetNavigationHandle()->GetConnectionInfo());

  // Verify that there are no aliases in the NavigationRequest.
  EXPECT_TRUE(navigation->GetNavigationHandle()->GetDnsAliases().empty());
}

TEST_F(NavigationRequestTest, ProcessSelectionUserDataIsAvailableFromUrlInfo) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kProcessSelectionDeferringConditions);

  NavigationRequest* request =
      main_test_rfh()->frame_tree_node()->navigation_request();
  ProcessSelectionUserData& user_data = request->GetProcessSelectionUserData();
  user_data.SetUserData(ProcessSelectionTestData::UserDataKey(),
                        std::make_unique<ProcessSelectionTestData>(42));

  UrlInfo url_info = request->GetUrlInfo();
  ASSERT_TRUE(url_info.process_selection_user_data);

  const ProcessSelectionTestData* retrieved_data_from_url_info =
      ProcessSelectionTestData::FromProcessSelectionUserData(
          url_info.process_selection_user_data);
  ASSERT_TRUE(retrieved_data_from_url_info);
  EXPECT_EQ(42, retrieved_data_from_url_info->value());
}

TEST_F(NavigationRequestTest, StorageKeyToCommit) {
  TestRenderFrameHost* child_document = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild(""));
  auto attributes = child_document->frame_tree_node()->attributes_->Clone();
  attributes->credentialless = true;
  child_document->frame_tree_node()->SetAttributes(std::move(attributes));

  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, child_document);
  navigation->ReadyToCommit();
  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  EXPECT_TRUE(request->commit_params().storage_key.nonce().has_value());
  EXPECT_EQ(child_document->GetPage().credentialless_iframes_nonce(),
            request->commit_params().storage_key.nonce().value());

  navigation->Commit();
  child_document =
      static_cast<TestRenderFrameHost*>(navigation->GetFinalRenderFrameHost());
  EXPECT_TRUE(child_document->IsCredentialless());
  EXPECT_EQ(blink::StorageKey::CreateWithNonce(
                url::Origin::Create(kUrl),
                child_document->GetPage().credentialless_iframes_nonce()),
            child_document->GetStorageKey());
}

TEST_F(NavigationRequestTest,
       NavigationToCredentiallessDocumentNetworkIsolationInfo) {
  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_test_rfh())
          ->AppendChild("child"));
  auto attributes = child_frame->frame_tree_node()->attributes_->Clone();
  attributes->credentialless = true;
  child_frame->frame_tree_node()->SetAttributes(std::move(attributes));

  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(
          GURL("https://example.com/navigation.html"), child_frame);
  navigation->ReadyToCommit();

  EXPECT_EQ(main_test_rfh()->GetPage().credentialless_iframes_nonce(),
            static_cast<NavigationRequest*>(navigation->GetNavigationHandle())
                ->isolation_info_for_subresources()
                .network_isolation_key()
                .GetNonce());
  EXPECT_EQ(main_test_rfh()->GetPage().credentialless_iframes_nonce(),
            static_cast<NavigationRequest*>(navigation->GetNavigationHandle())
                ->GetIsolationInfo()
                .network_isolation_key()
                .GetNonce());
}

TEST_F(NavigationRequestTest, UpdatePrivateNetworkRequestPolicy) {
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(GURL("https://example.com/"),
                                                   main_test_rfh());
  navigation->SetSocketAddress(net::IPEndPoint());

  navigation->ReadyToCommit();
  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  EXPECT_FALSE(request->GetSocketAddress().address().IsValid());
  navigation->Commit();
}

// Test to ensure that the SanitizeRedirectsForCommit method correctly removes
// the query parameters parts of the URL that can contain sensitive information.
TEST_F(NavigationRequestTest, SanitizeRedirectsForCommit) {
  const GURL start_url("https://a.com?param=1");
  const GURL url_2("https://b.com?param=2#foo");
  const GURL url_3("https://c.com?param=3");
  const GURL final_url("https://d.com?param=4");
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
  navigation->Start();
  navigation->Redirect(url_2);
  navigation->Redirect(url_3);
  navigation->Redirect(final_url);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  auto common_params = request->common_params().Clone();
  auto commit_params = request->commit_params().Clone();
  request->SanitizeRedirectsForCommit(common_params, commit_params);

  // redirect_params contains entries for B, C, and D, but not the starting URL.
  // Ensure that the full URL for D is preserved.
  EXPECT_EQ(3, commit_params->redirect_params.size());
  EXPECT_EQ(GURL("https://b.com"),
            commit_params->redirect_params[0]->redirect_info.new_url);
  EXPECT_EQ(GURL("https://c.com"),
            commit_params->redirect_params[1]->redirect_info.new_url);
  EXPECT_EQ(final_url,
            commit_params->redirect_params[2]->redirect_info.new_url);

  // In contrast, redirects contains A, B, and C (i.e., the starting URL but not
  // the final URL).
  EXPECT_EQ(3, commit_params->redirects.size());
  EXPECT_EQ(GURL("https://a.com"), commit_params->redirects[0]);
  EXPECT_EQ(GURL("https://b.com"), commit_params->redirects[1]);
  EXPECT_EQ(GURL("https://c.com"), commit_params->redirects[2]);
}

// Test to ensure that relative Location headers are handled correctly during
// sanitization (not cleared if same-origin, and sanitized to origin if
// cross-origin).
TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitRelativeLocation) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeLocationHeadersDuringNavigation,
                            features::kSanitizeOriginalUrlDuringNavigation},
      /*disabled_features=*/{});
  const GURL start_url("https://a.com/start");
  const GURL url_2("https://a.com/foo");
  const GURL url_3("https://b.com/bar");
  const GURL url_4("https://b.com/baz");
  const GURL final_url("https://b.com/final");

  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
  navigation->Start();

  // 1. Redirect to same-site (relative). Cross-origin to final URL.
  auto headers1 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers1->SetHeader("Location", "/foo");
  navigation->SetRedirectHeaders(headers1);
  navigation->Redirect(url_2);

  // 2. Redirect to cross-site (absolute). Same-origin to final URL.
  auto headers2 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers2->SetHeader("Location", "https://b.com/bar");
  navigation->SetRedirectHeaders(headers2);
  navigation->Redirect(url_3);

  // 3. Redirect to same-site (relative). Same-origin to final URL.
  auto headers3 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers3->SetHeader("Location", "/baz");
  navigation->SetRedirectHeaders(headers3);
  navigation->Redirect(url_4);

  // Final navigation to D.
  navigation->Redirect(final_url);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  auto common_params = request->common_params().Clone();
  auto commit_params = request->commit_params().Clone();

  request->SanitizeRedirectsForCommit(common_params, commit_params);

  EXPECT_EQ(4u, commit_params->redirect_params.size());

  size_t iter = 0;
  std::optional<std::string_view> location;

  // 1. "Location: /foo" resolves to cross-origin URL. Should be sanitized to
  // origin.
  location = commit_params->redirect_params[0]
                 ->response_head->headers->EnumerateHeader(&iter, "Location");
  ASSERT_TRUE(location.has_value());
  EXPECT_EQ("https://a.com/", location.value());

  // 2. "Location: https://b.com/bar" is same-origin to final URL. Should be
  // left alone.
  iter = 0;
  location = commit_params->redirect_params[1]
                 ->response_head->headers->EnumerateHeader(&iter, "Location");
  ASSERT_TRUE(location.has_value());
  EXPECT_EQ("https://b.com/bar", location.value());

  // 3. "Location: /baz" resolves to same-origin URL. Should be left alone as
  // relative URL.
  iter = 0;
  location = commit_params->redirect_params[2]
                 ->response_head->headers->EnumerateHeader(&iter, "Location");
  ASSERT_TRUE(location.has_value());
  EXPECT_EQ("/baz", location.value());

  // The original navigation URL should be sanitized to origin when
  // kSanitizeOriginalUrlDuringNavigation is enabled.
  EXPECT_EQ(GURL("https://a.com/"), commit_params->original_url);
  EXPECT_EQ(start_url, request->original_url());
}

// Test to ensure that relative Location headers on non-standard schemes are
// handled correctly during sanitization.
TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitNonStandardRelative) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeLocationHeadersDuringNavigation,
                            features::kSanitizeOriginalUrlDuringNavigation},
      /*disabled_features=*/{});

  url::ScopedSchemeRegistryForTests scoped_registry;
  url::AddStandardScheme("chrome-foo", url::SCHEME_WITH_HOST);

  const GURL start_url("chrome-foo://history/start");
  const GURL url_2("chrome-foo://history/foo");
  const GURL url_3("chrome-foo://newtab/bar");
  const GURL final_url("chrome-foo://newtab/final");

  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
  navigation->Start();

  // 1. Redirect to same-site (relative). Cross-origin to final URL.
  auto headers1 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers1->SetHeader("Location", "/foo");
  navigation->SetRedirectHeaders(headers1);
  navigation->Redirect(url_2);

  // 2. Redirect to cross-site (absolute). Same-origin to final URL.
  auto headers2 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers2->SetHeader("Location", "chrome-foo://newtab/bar");
  navigation->SetRedirectHeaders(headers2);
  navigation->Redirect(url_3);

  // Final navigation to D.
  navigation->Redirect(final_url);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  auto common_params = request->common_params().Clone();
  auto commit_params = request->commit_params().Clone();

  request->SanitizeRedirectsForCommit(common_params, commit_params);

  EXPECT_EQ(3u, commit_params->redirect_params.size());

  size_t iter = 0;
  std::optional<std::string_view> location;

  // 1. "Location: /foo" resolves to cross-origin URL. Should be sanitized to
  // origin.
  location = commit_params->redirect_params[0]
                 ->response_head->headers->EnumerateHeader(&iter, "Location");
  ASSERT_TRUE(location.has_value());
  EXPECT_EQ("chrome-foo://history/", location.value());

  // 2. "Location: chrome-foo://newtab/bar" is same-origin to final URL.
  // Should be left alone.
  iter = 0;
  location = commit_params->redirect_params[1]
                 ->response_head->headers->EnumerateHeader(&iter, "Location");
  ASSERT_TRUE(location.has_value());
  EXPECT_EQ("chrome-foo://newtab/bar", location.value());

  // The original navigation URL should be sanitized to origin when
  // kSanitizeOriginalUrlDuringNavigation is enabled.
  EXPECT_EQ(GURL("chrome-foo://history/"), commit_params->original_url);
  EXPECT_EQ(start_url, request->original_url());
}

// Test to ensure that hostless non-standard schemes (like data:) are handled
// safely and treated as cross-origin during sanitization.
TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitHostlessNonStandard) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeLocationHeadersDuringNavigation,
                            features::kSanitizeOriginalUrlDuringNavigation},
      /*disabled_features=*/{});

  const GURL start_url("https://a.com/start");
  const GURL url_2("data:text/html,foo");
  const GURL final_url("https://a.com/final");

  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
  navigation->Start();

  // 1. Redirect to data: URL.
  auto headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers->SetHeader("Location", "data:text/html,foo");
  navigation->SetRedirectHeaders(headers);
  navigation->Redirect(url_2);

  // Final navigation to D.
  navigation->Redirect(final_url);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  auto common_params = request->common_params().Clone();
  auto commit_params = request->commit_params().Clone();

  request->SanitizeRedirectsForCommit(common_params, commit_params);

  EXPECT_EQ(2u, commit_params->redirect_params.size());

  size_t iter = 0;
  std::optional<std::string_view> location;

  // "Location: data:text/html,foo" resolves to cross-origin URL (since data:
  // has no origin). Should be sanitized to empty string because
  // GetOriginForSanitization returns empty!
  location = commit_params->redirect_params[0]
                 ->response_head->headers->EnumerateHeader(&iter, "Location");
  ASSERT_TRUE(location.has_value());
  EXPECT_EQ("", location.value());

  // The original navigation URL should be sanitized to origin when
  // kSanitizeOriginalUrlDuringNavigation is enabled.
  EXPECT_EQ(GURL("https://a.com/"), commit_params->original_url);
  EXPECT_EQ(start_url, request->original_url());
}

// Test to ensure that SanitizeRedirectsForCommit is called when a navigation
// fails and commits an error page.
TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitErrorPage) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeOriginalUrlDuringNavigation},
      /*disabled_features=*/{});

  const GURL start_url("https://a.com?param=1");
  const GURL url_2("https://b.com?param=2#foo");
  const GURL final_url("https://d.com?param=4");

  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
  navigation->Start();
  navigation->Redirect(url_2);
  navigation->Redirect(final_url);
  navigation->Fail(net::ERR_CONNECTION_RESET);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());

  // We expect the redirects in the NavigationRequest's commit_params_ to be
  // sanitized.
  const auto& commit_params = request->commit_params();

  // redirects contains entries for A and B.
  EXPECT_EQ(2u, commit_params.redirects.size());
  EXPECT_EQ(GURL("https://a.com"), commit_params.redirects[0]);
  EXPECT_EQ(GURL("https://b.com"), commit_params.redirects[1]);

  // redirect_params contains entries for B and D.
  // The last entry (D) should NOT be sanitized.
  EXPECT_EQ(2u, commit_params.redirect_params.size());
  EXPECT_EQ(GURL("https://b.com"),
            commit_params.redirect_params[0]->redirect_info.new_url);
  EXPECT_EQ(final_url, commit_params.redirect_params[1]->redirect_info.new_url);

  // The original navigation URL should be sanitized to origin when
  // kSanitizeOriginalUrlDuringNavigation is enabled.
  EXPECT_EQ(GURL("https://a.com/"), commit_params.original_url);
  EXPECT_EQ(start_url, request->original_url());
}

// Helper class that turns off subframe error page isolation. Used for tests
// that rely on subframe error pages staying in the current process rather than
// going into an isolated error process.
class NavigationRequestWithoutSubframeErrorPageIsolationTest
    : public NavigationRequestTest {
 public:
  NavigationRequestWithoutSubframeErrorPageIsolationTest() = default;

  void SetUp() override {
    NavigationRequestTest::SetUp();
    browser_client_ =
        std::make_unique<NoSubframeErrorPageIsolationContentBrowserClient>();
    old_client_ = SetBrowserClientForTesting(browser_client_.get());
  }

  void TearDown() override {
    SetBrowserClientForTesting(old_client_);
    browser_client_.reset();
    NavigationRequestTest::TearDown();
  }

 private:
  class NoSubframeErrorPageIsolationContentBrowserClient
      : public TestContentBrowserClient {
   public:
    NoSubframeErrorPageIsolationContentBrowserClient() = default;
    bool ShouldIsolateErrorPage(bool in_main_frame) override {
      if (!in_main_frame) {
        return false;
      }
      return TestContentBrowserClient::ShouldIsolateErrorPage(in_main_frame);
    }
  };

  std::unique_ptr<NoSubframeErrorPageIsolationContentBrowserClient>
      browser_client_;
  raw_ptr<ContentBrowserClient> old_client_ = nullptr;
};

// Test that when a redirected subframe navigation is blocked and the resulting
// error page commits in the initiator's process, the final URL is reduced to
// its origin in the parameters sent to the renderer. See crbug.com/517156678.
TEST_F(NavigationRequestWithoutSubframeErrorPageIsolationTest,
       SanitizeRedirectsForCommitErrorPageInCurrentProcess) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls,
                            features::kSanitizeLocationHeadersDuringNavigation},
      /*disabled_features=*/{});

  // Commit an initial page so the subframe has a parent document.
  NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://a.com/"),
                                                     main_test_rfh());
  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));

  const GURL start_url("https://b.com/start?param=1");
  const GURL final_url("https://c.com/path?param=2");
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
  navigation->Start();

  auto headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers->SetHeader("Location", "https://c.com/path?param=2");
  navigation->SetRedirectHeaders(headers);

  navigation->Redirect(final_url);
  navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
            request->ComputeErrorPageProcess());

  // The error page commits in the initiator's process, so the final URL (which
  // is the post-redirect target) should be reduced to its origin in both the
  // common and commit params.
  EXPECT_EQ(GURL("https://c.com/"), request->common_params().url);
  ASSERT_EQ(1u, request->commit_params().redirect_params.size());
  EXPECT_EQ(GURL("https://c.com/"),
            request->commit_params().redirect_params[0]->redirect_info.new_url);
  ASSERT_EQ(1u, request->commit_params().redirects.size());
  EXPECT_EQ(GURL("https://b.com/"), request->commit_params().redirects[0]);

  if (base::FeatureList::IsEnabled(
          features::kSanitizeLocationHeadersDuringNavigation)) {
    size_t iter = 0;
    std::optional<std::string_view> location =
        request->commit_params()
            .redirect_params[0]
            ->response_head->headers->EnumerateHeader(&iter, "Location");
    ASSERT_TRUE(location.has_value());
    EXPECT_EQ("https://c.com/", location.value());
  }
}

TEST_F(
    NavigationRequestWithoutSubframeErrorPageIsolationTest,
    SanitizeRedirectsForCommitErrorPageInCurrentProcess_FinalURLFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeLocationHeadersDuringNavigation},
      /*disabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls});

  // Commit an initial page so the subframe has a parent document.
  NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://a.com/"),
                                                     main_test_rfh());
  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));

  const GURL start_url("https://b.com/start?param=1");
  const GURL final_url("https://c.com/path?param=2");
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
  navigation->Start();

  auto headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers->SetHeader("Location", "https://c.com/path?param=2");
  navigation->SetRedirectHeaders(headers);

  navigation->Redirect(final_url);
  navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
            request->ComputeErrorPageProcess());

  // The feature is disabled, so the final URL should NOT be reduced to its
  // origin.
  EXPECT_EQ(final_url, request->common_params().url);
  ASSERT_EQ(1u, request->commit_params().redirect_params.size());
  EXPECT_EQ(final_url,
            request->commit_params().redirect_params[0]->redirect_info.new_url);
  ASSERT_EQ(1u, request->commit_params().redirects.size());
  EXPECT_EQ(GURL("https://b.com/"), request->commit_params().redirects[0]);

  // Even if kSanitizeLocationHeadersDuringNavigation is enabled, it should not
  // sanitize the Location header because sanitize_final_url is false (due to
  // the disabled feature flag), which makes it use the final URL's origin
  // (c.com) as target_commit_origin, which is same-origin with the redirect
  // target (c.com).
  if (base::FeatureList::IsEnabled(
          features::kSanitizeLocationHeadersDuringNavigation)) {
    size_t iter = 0;
    std::optional<std::string_view> location =
        request->commit_params()
            .redirect_params[0]
            ->response_head->headers->EnumerateHeader(&iter, "Location");
    ASSERT_TRUE(location.has_value());
    EXPECT_EQ("https://c.com/path?param=2", location.value());
  }
}

TEST_F(NavigationRequestWithoutSubframeErrorPageIsolationTest,
       DontSanitizeRedirectsForCommitErrorPageInCurrentProcessSameOrigin) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls,
                            features::kSanitizeLocationHeadersDuringNavigation},
      /*disabled_features=*/{});

  // Commit an initial page so the subframe has a parent document.
  NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://c.com/"),
                                                     main_test_rfh());
  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));

  const GURL start_url("https://b.com/start?param=1");
  const GURL final_url("https://c.com/path?param=2");
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
  navigation->Start();

  auto headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers->SetHeader("Location", "https://c.com/path?param=2");
  navigation->SetRedirectHeaders(headers);

  navigation->Redirect(final_url);
  navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
            request->ComputeErrorPageProcess());

  // The final URL is same-origin with the receiving process (c.com), so it
  // should NOT be reduced to its origin.
  EXPECT_EQ(final_url, request->common_params().url);
  ASSERT_EQ(1u, request->commit_params().redirect_params.size());
  EXPECT_EQ(final_url,
            request->commit_params().redirect_params[0]->redirect_info.new_url);
  ASSERT_EQ(1u, request->commit_params().redirects.size());
  EXPECT_EQ(GURL("https://b.com/"), request->commit_params().redirects[0]);

  if (base::FeatureList::IsEnabled(
          features::kSanitizeLocationHeadersDuringNavigation)) {
    size_t iter = 0;
    std::optional<std::string_view> location =
        request->commit_params()
            .redirect_params[0]
            ->response_head->headers->EnumerateHeader(&iter, "Location");
    ASSERT_TRUE(location.has_value());
    EXPECT_EQ("https://c.com/path?param=2", location.value());
  }
}

// Test that when a subframe navigation with multiple redirects (same-origin to
// each other, but cross-origin to the main page) is blocked and commits an
// error page in the initiator's process, all redirect URLs are reduced to
// origin.
TEST_F(
    NavigationRequestWithoutSubframeErrorPageIsolationTest,
    SanitizeRedirectsForCommitErrorPageInCurrentProcessMultipleRedirectsSameOriginWithEachOther) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls,
                            features::kSanitizeLocationHeadersDuringNavigation},
      /*disabled_features=*/{});

  // Commit an initial page so the subframe has a parent document (origin A).
  NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://a.com/"),
                                                     main_test_rfh());
  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));

  const GURL start_url("https://b.com/start?param=1");
  const GURL url_2("https://b.com/path1?param=2");
  const GURL final_url("https://b.com/path2?param=3");
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
  navigation->Start();

  auto headers1 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers1->SetHeader("Location", "https://b.com/path1?param=2");
  navigation->SetRedirectHeaders(headers1);
  navigation->Redirect(url_2);

  auto headers2 =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
  headers2->SetHeader("Location", "https://b.com/path2?param=3");
  navigation->SetRedirectHeaders(headers2);
  navigation->Redirect(final_url);

  navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());
  ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
            request->ComputeErrorPageProcess());

  // The error page commits in process A (initiator). Both redirect URLs and
  // their Location headers pointing to origin B should be sanitized to origin.
  EXPECT_EQ(GURL("https://b.com/"), request->common_params().url);
  ASSERT_EQ(2u, request->commit_params().redirect_params.size());
  EXPECT_EQ(GURL("https://b.com/"),
            request->commit_params().redirect_params[0]->redirect_info.new_url);
  EXPECT_EQ(GURL("https://b.com/"),
            request->commit_params().redirect_params[1]->redirect_info.new_url);

  if (base::FeatureList::IsEnabled(
          features::kSanitizeLocationHeadersDuringNavigation)) {
    size_t iter = 0;
    std::optional<std::string_view> location1 =
        request->commit_params()
            .redirect_params[0]
            ->response_head->headers->EnumerateHeader(&iter, "Location");
    ASSERT_TRUE(location1.has_value());
    EXPECT_EQ("https://b.com/", location1.value());

    iter = 0;
    std::optional<std::string_view> location2 =
        request->commit_params()
            .redirect_params[1]
            ->response_head->headers->EnumerateHeader(&iter, "Location");
    ASSERT_TRUE(location2.has_value());
    EXPECT_EQ("https://b.com/", location2.value());
  }
}

TEST_F(NavigationRequestTest, AbortsDeletedNavigationInProgress) {
  const GURL kUrl1 = GURL("http://a.com");
  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl1, main_rfh());
  navigation->Start();

  testing::NiceMock<MockWebContentsObserver> failed_observer(web_contents());
  EXPECT_CALL(failed_observer, DidFinishNavigation(testing::_))
      .WillOnce([](NavigationHandle* navigation_handle) {
        EXPECT_EQ(navigation_handle->GetNetErrorCode(),
                  net::Error::ERR_ABORTED);
      });
  DeleteContents();
}

TEST_F(NavigationRequestTest, AbortsDeletedNavigationInProgressWithRedirect) {
  const GURL kUrl1 = GURL("http://a.com");
  const GURL kUrl2 = GURL("http://b.com");

  std::unique_ptr<NavigationSimulator> navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl1, main_rfh());
  navigation->Start();
  navigation->Redirect(kUrl2);

  testing::NiceMock<MockWebContentsObserver> failed_observer(web_contents());
  EXPECT_CALL(failed_observer, DidFinishNavigation(testing::_))
      .WillOnce([](NavigationHandle* navigation_handle) {
        EXPECT_EQ(navigation_handle->GetNetErrorCode(),
                  net::Error::ERR_ABORTED);
      });
  DeleteContents();
}

// Test that the required CSP of every frame is computed/inherited correctly and
// that the Sec-Required-CSP header is set.
class CSPEmbeddedEnforcementUnitTest : public NavigationRequestTest {
 protected:
  TestRenderFrameHost* main_rfh() {
    return static_cast<TestRenderFrameHost*>(NavigationRequestTest::main_rfh());
  }

  // Simulate the |csp| attribute being set in |rfh|'s frame. Then navigate it.
  // Returns the request's Sec-Required-CSP header.
  std::string NavigateWithRequiredCSP(TestRenderFrameHost** rfh,
                                      std::string required_csp) {
    TestRenderFrameHost* document = *rfh;

    if (!required_csp.empty()) {
      auto headers =
          base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
      headers->SetHeader("Content-Security-Policy", required_csp);
      std::vector<network::mojom::ContentSecurityPolicyPtr> policies;
      network::AddContentSecurityPolicyFromHeaders(
          *headers, GURL("https://example.com/"), &policies);
      auto attributes = document->frame_tree_node()->attributes_->Clone();
      // Set csp value.
      attributes->parsed_csp_attribute = std::move(policies[0]);
      document->frame_tree_node()->SetAttributes(std::move(attributes));
    }

    // Chrome blocks a document navigating to a URL if more than one of its
    // ancestors have the same URL. Use a different URL every time, to
    // avoid blocking navigation of the grandchild frame.
    static int nonce = 0;
    GURL url("https://www.example.com" + base::NumberToString(nonce++));

    auto navigation =
        content::NavigationSimulator::CreateRendererInitiated(url, *rfh);
    navigation->Start();
    NavigationRequest* request =
        NavigationRequest::From(navigation->GetNavigationHandle());
    std::string sec_required_csp = request->GetRequestHeaders()
                                       .GetHeader("sec-required-csp")
                                       .value_or(std::string());

    // Complete the navigation so that the required csp is stored in the
    // RenderFrameHost, so that when we will add children to this document they
    // will be able to get the parent's required csp (and hence also test that
    // the whole logic works).
    auto response_headers =
        base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
    response_headers->SetHeader("Allow-CSP-From", "*");
    navigation->SetResponseHeaders(response_headers);
    navigation->Commit();

    *rfh = static_cast<TestRenderFrameHost*>(
        navigation->GetFinalRenderFrameHost());

    return sec_required_csp;
  }

  TestRenderFrameHost* AddChild(TestRenderFrameHost* parent) {
    return static_cast<TestRenderFrameHost*>(
        content::RenderFrameHostTester::For(parent)->AppendChild(""));
  }
};

TEST_F(CSPEmbeddedEnforcementUnitTest, TopLevel) {
  TestRenderFrameHost* top_document = main_rfh();
  std::string sec_required_csp = NavigateWithRequiredCSP(&top_document, "");
  EXPECT_EQ("", sec_required_csp);
  EXPECT_FALSE(top_document->required_csp());
}

TEST_F(CSPEmbeddedEnforcementUnitTest, ChildNoCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  std::string sec_required_csp = NavigateWithRequiredCSP(&child_document, "");
  EXPECT_EQ("", sec_required_csp);
  EXPECT_FALSE(child_document->required_csp());
}

TEST_F(CSPEmbeddedEnforcementUnitTest, ChildWithCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            child_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest, ChildSiblingNoCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* sibling_document = AddChild(top_document);
  std::string sec_required_csp = NavigateWithRequiredCSP(&sibling_document, "");
  EXPECT_FALSE(sibling_document->required_csp());
}

TEST_F(CSPEmbeddedEnforcementUnitTest, ChildSiblingCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* sibling_document = AddChild(top_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&sibling_document, "script-src 'none'");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(sibling_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            sibling_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest, GrandChildNoCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* grand_child_document = AddChild(child_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&grand_child_document, "");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(grand_child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            grand_child_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest, GrandChildSameCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* grand_child_document = AddChild(child_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&grand_child_document, "script-src 'none'");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(grand_child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            grand_child_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest, GrandChildDifferentCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* grand_child_document = AddChild(child_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&grand_child_document, "img-src 'none'");

  // This seems weird, but it is the intended behaviour according to the spec.
  // The problem is that "script-src 'none'" does not subsume "img-src 'none'",
  // so "img-src 'none'" on the grandchild is an invalid csp attribute, and we
  // just discard it in favour of the parent's csp attribute.
  //
  // This should probably be fixed in the specification:
  // https://github.com/w3c/webappsec-cspee/pull/11
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(grand_child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            grand_child_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest, InvalidCSP) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&child_document, "report-to group");
  EXPECT_EQ("", sec_required_csp);
  EXPECT_FALSE(child_document->required_csp());
}

TEST_F(CSPEmbeddedEnforcementUnitTest, InvalidCspAndInheritFromParent) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* grand_child_document = AddChild(child_document);
  std::string sec_required_csp =
      NavigateWithRequiredCSP(&grand_child_document, "report-to group");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(grand_child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            grand_child_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest,
       SemiInvalidCspAndInheritSameCspFromParent) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* grand_child_document = AddChild(child_document);
  std::string sec_required_csp = NavigateWithRequiredCSP(
      &grand_child_document, "script-src 'none'; report-to group");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(grand_child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            grand_child_document->required_csp()->header->header_value);
}

TEST_F(CSPEmbeddedEnforcementUnitTest,
       SemiInvalidCspAndInheritDifferentCspFromParent) {
  TestRenderFrameHost* top_document = main_rfh();
  TestRenderFrameHost* child_document = AddChild(top_document);
  NavigateWithRequiredCSP(&child_document, "script-src 'none'");
  TestRenderFrameHost* grand_child_document = AddChild(child_document);
  std::string sec_required_csp = NavigateWithRequiredCSP(
      &grand_child_document, "sandbox; report-to group");
  EXPECT_EQ("script-src 'none'", sec_required_csp);
  EXPECT_TRUE(grand_child_document->required_csp());
  EXPECT_EQ("script-src 'none'",
            grand_child_document->required_csp()->header->header_value);
}

namespace {

// Mock that allows us to avoid depending on the origin_trials component.
class OriginTrialsControllerDelegateMock
    : public OriginTrialsControllerDelegate {
 public:
  ~OriginTrialsControllerDelegateMock() override = default;

  void PersistTrialsFromTokens(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      const base::span<const std::string> header_tokens,
      const base::Time current_time,
      std::optional<ukm::SourceId> source_id) override {
    persisted_tokens_[origin] =
        std::vector<std::string>(header_tokens.begin(), header_tokens.end());
  }
  void PersistAdditionalTrialsFromTokens(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      const base::span<const url::Origin> script_origins,
      const base::span<const std::string> header_tokens,
      const base::Time current_time,
      std::optional<ukm::SourceId> source_id) override {
    NOTREACHED() << "not used by test";
  }
  bool IsFeaturePersistedForOrigin(const url::Origin& origin,
                                   const url::Origin& partition_origin,
                                   blink::mojom::OriginTrialFeature feature,
                                   const base::Time current_time) override {
    DCHECK(false) << "Method not implemented for test.";
    return false;
  }

  base::flat_set<std::string> GetPersistedTrialsForOrigin(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      base::Time current_time) override {
    DCHECK(false) << "Method not implemented for test.";
    return base::flat_set<std::string>();
  }

  void ClearPersistedTokens() override { persisted_tokens_.clear(); }

  base::flat_map<url::Origin, std::vector<std::string>> persisted_tokens_;
};

}  // namespace

class PersistentOriginTrialNavigationRequestTest
    : public NavigationRequestTest {
 public:
  PersistentOriginTrialNavigationRequestTest()
      : delegate_mock_(std::make_unique<OriginTrialsControllerDelegateMock>()) {
  }
  ~PersistentOriginTrialNavigationRequestTest() override = default;

  std::vector<std::string> GetPersistedTokens(const url::Origin& origin) {
    return delegate_mock_->persisted_tokens_[origin];
  }

 protected:
  std::unique_ptr<BrowserContext> CreateBrowserContext() override {
    std::unique_ptr<TestBrowserContext> context =
        std::make_unique<TestBrowserContext>();
    context->SetOriginTrialsControllerDelegate(delegate_mock_.get());
    return context;
  }

 private:
  std::unique_ptr<OriginTrialsControllerDelegateMock> delegate_mock_;
};

// Ensure that navigations with a valid Origin-Trial header with a persistent
// origin trial token results in the trial being marked as enabled.
// Then check that subsequent navigations without headers trigger an update
// that clears out stored trials.
TEST_F(PersistentOriginTrialNavigationRequestTest,
       NavigationCommitsPersistentOriginTrials) {
  // Generated with:
  // tools/origin_trials/generate_token.py https://example.com
  // FrobulatePersistent
  // --expire-timestamp=2000000000
  const char kPersistentOriginTrialToken[] =
      "AzZfd1vKZ0SSGRGk/"
      "8nIszQSlHYjbuYVE3jwaNZG3X4t11zRhzPWWJwTZ+JJDS3JJsyEZcpz+y20pAP6/"
      "6upOQ4AAABdeyJvcmlnaW4iOiAiaHR0cHM6Ly9leGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZ"
      "SI"
      "6ICJGcm9idWxhdGVQZXJzaXN0ZW50IiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9";

  blink::ScopedTestOriginTrialPolicy origin_trial_policy_;

  const GURL kUrl = GURL("https://example.com");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());

  auto response_headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
  response_headers->SetHeader("Origin-Trial", kPersistentOriginTrialToken);
  navigation->SetResponseHeaders(response_headers);

  navigation->Commit();

  url::Origin origin = url::Origin::Create(kUrl);
  EXPECT_EQ(std::vector<std::string>{kPersistentOriginTrialToken},
            GetPersistedTokens(origin));

  // Navigate again without response headers to assert the trial information is
  // still updated and cleared.
  NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh())->Commit();
  EXPECT_EQ(std::vector<std::string>(), GetPersistedTokens(origin));
}

namespace {

// Test version of a NavigationThrottle that requests the response body.
class ResponseBodyNavigationThrottle : public NavigationThrottle {
 public:
  using ResponseBodyCallback = base::OnceCallback<void(const std::string&)>;

  ResponseBodyNavigationThrottle(NavigationThrottleRegistry& registry,
                                 ResponseBodyCallback callback)
      : NavigationThrottle(registry), callback_(std::move(callback)) {}
  ResponseBodyNavigationThrottle(const ResponseBodyNavigationThrottle&) =
      delete;
  ResponseBodyNavigationThrottle& operator=(
      const ResponseBodyNavigationThrottle&) = delete;
  ~ResponseBodyNavigationThrottle() override = default;

  NavigationThrottle::ThrottleCheckResult WillProcessResponse() override {
    navigation_handle()->GetResponseBody(
        base::BindOnce(&ResponseBodyNavigationThrottle::OnResponseBodyReady,
                       base::Unretained(this)));
    return NavigationThrottle::DEFER;
  }

  const char* GetNameForLogging() override {
    return "ResponseBodyNavigationThrottle";
  }

 private:
  void OnResponseBodyReady(const std::string& response_body) {
    std::move(callback_).Run(response_body);
    NavigationRequest::From(navigation_handle())
        ->GetNavigationThrottleRegistryForTesting()
        ->ResumeProcessingNavigationEvent(this);
  }

  ResponseBodyCallback callback_;
};

}  // namespace

// Tests response body.
class NavigationRequestResponseBodyTest : public NavigationRequestTest {
 public:
  std::unique_ptr<NavigationSimulator> CreateNavigationSimulator() {
    auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
        GURL("http://example.test"), main_rfh());
    navigation->SetAutoAdvance(false);
    navigation->Start();
    // It is safe to use base::Unretained as the NavigationThrottle will not be
    // destroyed before the callback is called.
    auto& registry = navigation->GetNavigationThrottleRegistry();
    auto throttle = std::make_unique<ResponseBodyNavigationThrottle>(
        registry,
        base::BindOnce(&NavigationRequestResponseBodyTest::UpdateResponseBody,
                       base::Unretained(this)));
    registry.AddThrottle(std::move(throttle));
    return navigation;
  }

  void UpdateResponseBody(const std::string& response_body) {
    response_body_ = response_body;
    was_callback_called_ = true;
  }

  bool was_callback_called() const { return was_callback_called_; }

  const std::string& response_body() const { return response_body_; }

 protected:
  mojo::ScopedDataPipeProducerHandle producer_handle_;
  mojo::ScopedDataPipeConsumerHandle consumer_handle_;

 private:
  bool was_callback_called_ = false;
  std::string response_body_;
};

TEST_F(NavigationRequestResponseBodyTest, Received) {
  auto navigation = CreateNavigationSimulator();
  std::string response = "response-body-content";
  ASSERT_EQ(MOJO_RESULT_OK,
            mojo::CreateDataPipe(response.size(), producer_handle_,
                                 consumer_handle_));
  navigation->SetResponseBody(std::move(consumer_handle_));

  navigation->ReadyToCommit();
  EXPECT_EQ(
      NavigationRequest::WILL_PROCESS_RESPONSE,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_EQ(std::string(), response_body());

  size_t actually_written_bytes = 0;
  ASSERT_EQ(MOJO_RESULT_OK,
            producer_handle_->WriteData(base::as_byte_span(response),
                                        MOJO_WRITE_DATA_FLAG_NONE,
                                        actually_written_bytes));
  EXPECT_EQ(actually_written_bytes, response.size());

  navigation->Wait();
  EXPECT_EQ(
      NavigationRequest::READY_TO_COMMIT,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(response, response_body());
}

TEST_F(NavigationRequestResponseBodyTest, PartiallyReceived) {
  auto navigation = CreateNavigationSimulator();

  // The data pipe size is smaller than the response body size.
  uint32_t pipe_size = 8u;
  ASSERT_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(pipe_size, producer_handle_,
                                                 consumer_handle_));
  navigation->SetResponseBody(std::move(consumer_handle_));

  navigation->ReadyToCommit();
  EXPECT_EQ(
      NavigationRequest::WILL_PROCESS_RESPONSE,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_EQ(std::string(), response_body());

  std::string response = "response-body-content";
  size_t actually_written_bytes = 0;
  ASSERT_EQ(MOJO_RESULT_OK,
            producer_handle_->WriteData(base::as_byte_span(response),
                                        MOJO_WRITE_DATA_FLAG_NONE,
                                        actually_written_bytes));
  EXPECT_EQ(actually_written_bytes, pipe_size);

  navigation->Wait();
  EXPECT_EQ(
      NavigationRequest::READY_TO_COMMIT,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_TRUE(was_callback_called());
  // Only the first part of the response body that fits in the pipe is received.
  EXPECT_EQ("response", response_body());
}

TEST_F(NavigationRequestResponseBodyTest, PipeClosed) {
  auto navigation = CreateNavigationSimulator();
  ASSERT_EQ(MOJO_RESULT_OK,
            mojo::CreateDataPipe(10u, producer_handle_, consumer_handle_));
  navigation->SetResponseBody(std::move(consumer_handle_));
  navigation->ReadyToCommit();
  EXPECT_EQ(
      NavigationRequest::WILL_PROCESS_RESPONSE,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_FALSE(was_callback_called());
  EXPECT_EQ(std::string(), response_body());

  // Close the pipe before any data is sent.
  producer_handle_.reset();
  navigation->Wait();
  EXPECT_EQ(
      NavigationRequest::READY_TO_COMMIT,
      NavigationRequest::From(navigation->GetNavigationHandle())->state());
  EXPECT_TRUE(was_callback_called());
  EXPECT_EQ(std::string(), response_body());
}

// Verifies that a subframe NavigationRequest inherits its parent
// SiteInstance's unique-instance EmbedderIsolationInfo rather than producing
// a fresh id from the subframe's own `navigation_id_`.
TEST_F(NavigationRequestTest, SubframeInheritsParentMimeHandlerIsolationId) {
  constexpr int64_t kParentIsolationId = 1234567;
  const GURL kParentUrl("https://example.com/handler.html");
  main_test_rfh()->GetSiteInstance()->SetSite(
      UrlInfo(UrlInfoInit(kParentUrl)
                  .WithEmbedderIsolationInfo(
                      EmbedderIsolationInfo::CreateForUniqueInstance(
                          kParentIsolationId))));

  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
  std::unique_ptr<NavigationRequest> request = CreateSubframeNavigationRequest(
      child_frame->frame_tree_node(), GURL("https://example.com/sub"));
  ASSERT_TRUE(request);
  ASSERT_NE(request->GetNavigationId(), kParentIsolationId);

  EXPECT_EQ(
      kParentIsolationId,
      request->GetUrlInfo().embedder_isolation_info.instance_id().value());
}

// Verifies that a subframe of a non-MIME-handler parent does not pick up a
// unique-instance EmbedderIsolationInfo.
TEST_F(NavigationRequestTest, SubframeWithoutMimeHandlerParentDoesNotInherit) {
  main_test_rfh()->GetSiteInstance()->SetSite(
      UrlInfo::CreateForTesting(GURL("https://parent.example.com")));

  auto* child_frame = static_cast<TestRenderFrameHost*>(
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
  std::unique_ptr<NavigationRequest> request = CreateSubframeNavigationRequest(
      child_frame->frame_tree_node(), GURL("https://example.com/sub"));
  ASSERT_TRUE(request);

  EXPECT_FALSE(
      request->GetUrlInfo().embedder_isolation_info.is_unique_instance());
}

namespace {

// A throttle that accesses request headers before modifying them.
// This is used to verify that modifications made after an initial access
// (which triggers caching in NavigationRequest::request_headers_) are still
// correctly reflected.
class HeaderModifyingThrottle : public NavigationThrottle {
 public:
  explicit HeaderModifyingThrottle(NavigationThrottleRegistry& registry)
      : NavigationThrottle(registry) {}

  NavigationThrottle::ThrottleCheckResult WillStartRequest() override {
    navigation_handle()->GetRequestHeaders();
    navigation_handle()->SetRequestHeader("X-Test-Header", "Value");
    return PROCEED;
  }

  const char* GetNameForLogging() override { return "HeaderModifyingThrottle"; }
};

class HeaderTestContentBrowserClient : public TestContentBrowserClient {
 public:
  HeaderTestContentBrowserClient() = default;

  void CreateThrottlesForNavigation(
      NavigationThrottleRegistry& registry) override {
    registry.AddThrottle(std::make_unique<HeaderModifyingThrottle>(registry));
  }
};

}  // namespace

// Verifies that request headers modified during navigation start (e.g. via
// SetRequestHeader in a throttle) are correctly reflected in
// GetRequestHeaders().
TEST_F(NavigationRequestTest, GetRequestHeadersReflectsLaterModifications) {
  HeaderTestContentBrowserClient client;
  ScopedContentBrowserClientSetting setting(&client);

  const GURL kUrl = GURL("http://chromium.org");
  auto navigation =
      NavigationSimulatorImpl::CreateRendererInitiated(kUrl, main_rfh());
  navigation->Start();

  NavigationRequest* request =
      NavigationRequest::From(navigation->GetNavigationHandle());

  EXPECT_TRUE(request->GetRequestHeaders().HasHeader("X-Test-Header"));

  // Commit the navigation to ensure the NavigationRequest is destroyed while
  // the ScopedContentBrowserClientSetting (and the local browser client) is
  // still in scope.
  navigation->Commit();
}

}  // namespace content
