// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <tuple>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/loader/prefetch_url_loader_service_context.h"
#include "content/browser/loader/subresource_proxying_url_loader_service.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
#include "content/browser/web_package/signed_exchange_handler.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/common/content_constants_internal.h"
#include "content/public/browser/back_forward_cache.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/download_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/web_contents.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_paths.h"
#include "content/public/common/page_type.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_cert_verifier_browser_test.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/signed_exchange_browser_test_helper.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_download_manager_delegate.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/mock_reduce_accept_language_controller_delegate.h"
#include "media/media_buildflags.h"
#include "net/base/features.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/ct_policy_status.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/test_root_certs.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.h"
#include "net/http/transport_security_state.h"
#include "net/http/transport_security_state_test_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/test_data_directory.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/public/common/features.h"

namespace content {

namespace {

constexpr char kLoadResultHistogram[] = "SignedExchange.LoadResult2";
constexpr char kPrefetchResultHistogram[] =
    "SignedExchange.Prefetch.LoadResult2";
constexpr char kRedirectLoopHistogram[] = "SignedExchange.FallbackRedirectLoop";

class RedirectObserver : public WebContentsObserver {
 public:
  explicit RedirectObserver(WebContents* web_contents)
      : WebContentsObserver(web_contents) {}

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

  ~RedirectObserver() override = default;

  void DidRedirectNavigation(NavigationHandle* handle) override {
    const net::HttpResponseHeaders* response = handle->GetResponseHeaders();
    if (response)
      response_code_ = response->response_code();
  }

  const std::optional<int>& response_code() const { return response_code_; }

 private:
  std::optional<int> response_code_;
};

class AssertNavigationHandleFlagObserver : public WebContentsObserver {
 public:
  explicit AssertNavigationHandleFlagObserver(WebContents* web_contents)
      : WebContentsObserver(web_contents) {}

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

  ~AssertNavigationHandleFlagObserver() override = default;

  void DidFinishNavigation(NavigationHandle* handle) override {
    EXPECT_TRUE(handle->IsSignedExchangeInnerResponse());
  }
};

class FinishNavigationObserver : public WebContentsObserver {
 public:
  FinishNavigationObserver(WebContents* contents,
                           base::OnceClosure done_closure)
      : WebContentsObserver(contents), done_closure_(std::move(done_closure)) {}

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

  void DidFinishNavigation(NavigationHandle* navigation_handle) override {
    error_code_ = navigation_handle->GetNetErrorCode();
    std::move(done_closure_).Run();
  }

  const std::optional<net::Error>& error_code() const { return error_code_; }

 private:
  base::OnceClosure done_closure_;
  std::optional<net::Error> error_code_;
};

class MockContentBrowserClient final
    : public ContentBrowserTestContentBrowserClient {
 public:
  std::string GetAcceptLangs(BrowserContext* context) override {
    return accept_langs_;
  }

  void SetAcceptLangs(const std::string& langs) { accept_langs_ = langs; }

 private:
  std::string accept_langs_ = "en";
};

}  // namespace

class SignedExchangeRequestHandlerBrowserTestBase
    : public CertVerifierBrowserTest {
 public:
  explicit SignedExchangeRequestHandlerBrowserTestBase(
      bool use_prefetch = false)
      : use_prefetch_(use_prefetch) {
    // Enable BackForwardCache for now as some tests are flaky when the previous
    // RenderFrameHost doesn't change on navigation (the histograms are not
    // recoded correctly).
    // TODO(crbug.com/40242189): Figure out why and fix.
    feature_list_.InitAndEnableFeature(features::kBackForwardCache);
  }

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

  void SetUp() override {
    sxg_test_helper_.SetUp();
    CertVerifierBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    CertVerifierBrowserTest::SetUpOnMainThread();

    inactive_rfh_deletion_observer_ =
        std::make_unique<InactiveRenderFrameHostDeletionObserver>(
            shell()->web_contents());
    client_ = std::make_unique<MockContentBrowserClient>();
  }

  void TearDownOnMainThread() override {
    sxg_test_helper_.TearDownOnMainThread();
    client_.reset();
  }

 protected:
  bool UsePrefetch() const { return use_prefetch_; }

  void MaybeTriggerPrefetchSXG(const GURL& url, bool expect_success) {
    if (!UsePrefetch()) {
      return;
    }
    const GURL prefetch_html_url = embedded_test_server()->GetURL(
        std::string("/sxg/prefetch.html#") + url.spec());
    std::u16string expected_title =
        base::ASCIIToUTF16(expect_success ? "OK" : "FAIL");
    TitleWatcher title_watcher(shell()->web_contents(), expected_title);
    EXPECT_TRUE(NavigateToURL(shell(), prefetch_html_url));
    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());

    if (expect_success) {
      WaitUntilSXGIsCached(url);
    }
  }

  void RunSimpleTest(std::string_view sxg_path);

  void InstallUrlInterceptor(const GURL& url, const std::string& data_path) {
    sxg_test_helper_.InstallUrlInterceptor(url, data_path);
  }

  void InstallMockCert() {
    sxg_test_helper_.InstallMockCert(mock_cert_verifier());
  }

  void InstallMockCertChainInterceptor() {
    sxg_test_helper_.InstallMockCertChainInterceptor();
  }

  // Make the MockCertVerifier treat the signed exchange's certificate
  // "prime256v1-sha256.public.pem" as valid for "test.example.org", but
  // issued by a known root.
  void InstallMockCertByKnownRoot() {
    scoped_refptr<net::X509Certificate> original_cert =
        SignedExchangeBrowserTestHelper::LoadCertificate();
    net::CertVerifyResult dummy_result;
    dummy_result.verified_cert = original_cert;
    dummy_result.cert_status = net::OK;
    dummy_result.ocsp_result.response_status = bssl::OCSPVerifyResult::PROVIDED;
    dummy_result.ocsp_result.revocation_status =
        bssl::OCSPRevocationStatus::GOOD;
    dummy_result.is_issued_by_known_root = true;
    mock_cert_verifier()->AddResultForCertAndHost(
        original_cert, "test.example.org", dummy_result, net::OK);
  }

  void SetAcceptLangs(const std::string& langs) {
    client_->SetAcceptLangs(langs);
    StoragePartitionImpl* partition =
        static_cast<StoragePartitionImpl*>(shell()
                                               ->web_contents()
                                               ->GetBrowserContext()
                                               ->GetDefaultStoragePartition());
    partition->GetSubresourceProxyingURLLoaderService()
        ->prefetch_url_loader_service_context_for_testing()
        .SetAcceptLanguages(langs);
    // Set the Accept-Language for delegate in order to get correct
    // Accept-Language in navigation requests instead of always using the
    // default shell Accept-Language.
    MockReduceAcceptLanguageControllerDelegate* delegate =
        static_cast<MockReduceAcceptLanguageControllerDelegate*>(
            shell()
                ->web_contents()
                ->GetBrowserContext()
                ->GetReduceAcceptLanguageControllerDelegate());
    delegate->SetUserAcceptLanguages(langs);
  }

  std::unique_ptr<InactiveRenderFrameHostDeletionObserver>
      inactive_rfh_deletion_observer_;

  const base::HistogramTester histogram_tester_;

  std::unique_ptr<MockContentBrowserClient> client_;

 private:
  class CacheObserver : public PrefetchedSignedExchangeCache::TestObserver {
   public:
    CacheObserver(const GURL& outer_url, base::OnceClosure quit_closure)
        : outer_url_(outer_url), quit_closure_(std::move(quit_closure)) {}

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

    ~CacheObserver() override = default;

    void OnStored(PrefetchedSignedExchangeCache* cache,
                  const GURL& outer_url) override {
      if (quit_closure_ && (outer_url_ == outer_url)) {
        std::move(quit_closure_).Run();
      }
    }

   private:
    const GURL outer_url_;
    base::OnceClosure quit_closure_;
  };

  void WaitUntilSXGIsCached(const GURL& url) {
    scoped_refptr<PrefetchedSignedExchangeCache> cache =
        static_cast<RenderFrameHostImpl*>(
            shell()->web_contents()->GetPrimaryMainFrame())
            ->EnsurePrefetchedSignedExchangeCache();

    if (cache->GetExchanges().find(url) != cache->GetExchanges().end()) {
      return;
    }
    base::RunLoop run_loop;
    auto observer =
        std::make_unique<CacheObserver>(url, run_loop.QuitClosure());
    cache->AddObserverForTesting(observer.get());
    run_loop.Run();
    cache->RemoveObserverForTesting(observer.get());
  }

  const bool use_prefetch_ = false;
  base::test::ScopedFeatureList feature_list_;
  SignedExchangeBrowserTestHelper sxg_test_helper_;
};

void SignedExchangeRequestHandlerBrowserTestBase::RunSimpleTest(
    std::string_view sxg_path) {
  InstallMockCert();
  InstallMockCertChainInterceptor();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL(sxg_path);

  MaybeTriggerPrefetchSXG(url, true);

  if (!UsePrefetch()) {
    // Need to be in a page to execute JavaScript to trigger renderer initiated
    // navigation.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
  }

  std::u16string title = u"https://test.example.org/test/";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  RedirectObserver redirect_observer(shell()->web_contents());
  AssertNavigationHandleFlagObserver assert_navigation_handle_flag_observer(
      shell()->web_contents());

  // PrefetchedSignedExchangeCache is used only for renderer initiated
  // navigation. So use JavaScript to trigger renderer initiated navigation.
  EXPECT_TRUE(
      ExecJs(shell()->web_contents(),
             base::StringPrintf("location.href = '%s';", url.spec().c_str())));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  EXPECT_EQ(303, redirect_observer.response_code());

  NavigationEntry* entry =
      shell()->web_contents()->GetController().GetVisibleEntry();
  EXPECT_TRUE(entry->GetSSL().initialized);
  EXPECT_FALSE(!!(entry->GetSSL().content_status &
                  SSLStatus::DISPLAYED_INSECURE_CONTENT));
  ASSERT_TRUE(entry->GetSSL().certificate);

  // "test.example.org.public.pem.cbor" is generated from
  // "prime256v1-sha256.public.pem". So the SHA256 of the certificates must
  // match.
  const net::SHA256HashValue fingerprint =
      net::X509Certificate::CalculateFingerprint256(
          entry->GetSSL().certificate->cert_buffer());
  scoped_refptr<net::X509Certificate> original_cert =
      SignedExchangeBrowserTestHelper::LoadCertificate();
  const net::SHA256HashValue original_fingerprint =
      net::X509Certificate::CalculateFingerprint256(
          original_cert->cert_buffer());
  EXPECT_EQ(original_fingerprint, fingerprint);

  // Wait for the previous page's RFH to be deleted (if it changed) so that the
  // histograms will get updated.
  inactive_rfh_deletion_observer_->Wait();
  histogram_tester_.ExpectUniqueSample(kLoadResultHistogram,
                                       SignedExchangeLoadResult::kSuccess, 1);
  histogram_tester_.ExpectTotalCount(
      "SignedExchange.Time.CertificateFetch.Success", 1);
  if (UsePrefetch()) {
    histogram_tester_.ExpectUniqueSample(kPrefetchResultHistogram,
                                         SignedExchangeLoadResult::kSuccess, 1);
    histogram_tester_.ExpectTotalCount("PrefetchedSignedExchangeCache.Count",
                                       1);
  }
}

class SignedExchangeRequestHandlerBrowserTest
    : public testing::WithParamInterface<bool>,
      public SignedExchangeRequestHandlerBrowserTestBase {
 public:
  SignedExchangeRequestHandlerBrowserTest()
      : SignedExchangeRequestHandlerBrowserTestBase(GetParam()) {}
  ~SignedExchangeRequestHandlerBrowserTest() override = default;
  static std::string DescribeParams(
      const testing::TestParamInfo<ParamType>& info) {
    return info.param ? "WithPrefetch" : "WithoutPrefetch";
  }
};

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest, Simple) {
  RunSimpleTest("/sxg/test.example.org_test.sxg");
}

class SignedExchangeRendererSideContentDecodingBrowserTest
    : public testing::WithParamInterface<std::tuple<bool, bool>>,
      public SignedExchangeRequestHandlerBrowserTestBase {
 public:
  SignedExchangeRendererSideContentDecodingBrowserTest()
      : SignedExchangeRequestHandlerBrowserTestBase(std::get<0>(GetParam())) {
    if (std::get<1>(GetParam())) {
      features_.InitWithFeatures(
          {network::features::kRendererSideContentDecoding}, {});
    } else {
      features_.InitWithFeatures(
          {}, {network::features::kRendererSideContentDecoding});
    }
  }
  ~SignedExchangeRendererSideContentDecodingBrowserTest() override = default;
  static std::string DescribeParams(
      const testing::TestParamInfo<ParamType>& info) {
    return base::StrCat({
        std::get<0>(info.param) ? "WithPrefetch" : "WithoutPrefetch",
        std::get<1>(info.param) ? "FeatureEnabled" : "FeatureDisabled",
    });
  }

 private:
  base::test::ScopedFeatureList features_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    SignedExchangeRendererSideContentDecodingBrowserTest,
    ::testing::Combine(::testing::Bool(), ::testing::Bool()),
    &SignedExchangeRendererSideContentDecodingBrowserTest::DescribeParams);

IN_PROC_BROWSER_TEST_P(SignedExchangeRendererSideContentDecodingBrowserTest,
                       Compressed) {
  RunSimpleTest("/sxg/test.example.org_test.sxg.gz");
}

class SignedExchangeRendererSideContentDecodingFailureBrowserTest
    : public SignedExchangeRequestHandlerBrowserTestBase {
 public:
  SignedExchangeRendererSideContentDecodingFailureBrowserTest() {
    features_.InitWithFeaturesAndParameters(
        {{network::features::kRendererSideContentDecoding,
          {{"RendererSideContentDecodingForceMojoFailureForTesting", "true"}}}},
        {});
  }
  ~SignedExchangeRendererSideContentDecodingFailureBrowserTest() override =
      default;

 private:
  base::test::ScopedFeatureList features_;
};

IN_PROC_BROWSER_TEST_F(
    SignedExchangeRendererSideContentDecodingFailureBrowserTest,
    Compressed) {
  std::string_view sxg_path = "/sxg/test.example.org_test.sxg.gz";
  InstallMockCert();
  InstallMockCertChainInterceptor();
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL(sxg_path);
  base::RunLoop run_loop;
  FinishNavigationObserver finish_navigation_observer(shell()->web_contents(),
                                                      run_loop.QuitClosure());
  EXPECT_FALSE(NavigateToURL(shell()->web_contents(), url));
  run_loop.Run();
  EXPECT_THAT(finish_navigation_observer.error_code(),
              net::ERR_INSUFFICIENT_RESOURCES);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest, VariantMatch) {
  SetAcceptLangs("fr,en-US");
  InstallUrlInterceptor(
      GURL("https://cert.example.org/cert.msg"),
      "content/test/data/sxg/test.example.org.public.pem.cbor");
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url =
      embedded_test_server()->GetURL("/sxg/test.example.org_fr_variant.sxg");
  MaybeTriggerPrefetchSXG(url, true);

  if (!UsePrefetch()) {
    // Need to be in a page to execute JavaScript to trigger renderer initiated
    // navigation.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
  }

  std::u16string title = u"https://test.example.org/test/";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  // PrefetchedSignedExchangeCache is used only for renderer initiated
  // navigation. So use JavaScript to trigger renderer initiated navigation.
  EXPECT_TRUE(
      ExecJs(shell()->web_contents(),
             base::StringPrintf("location.href = '%s';", url.spec().c_str())));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());

  // Wait for the previous page's RFH to be deleted (if it changed) so that the
  // histograms will get updated.
  inactive_rfh_deletion_observer_->Wait();
  histogram_tester_.ExpectUniqueSample(kLoadResultHistogram,
                                       SignedExchangeLoadResult::kSuccess, 1);
  if (UsePrefetch()) {
    histogram_tester_.ExpectTotalCount("PrefetchedSignedExchangeCache.Count",
                                       1);
  }
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       VariantMismatch) {
  SetAcceptLangs("en-US,ja");
  InstallUrlInterceptor(
      GURL("https://cert.example.org/cert.msg"),
      "content/test/data/sxg/test.example.org.public.pem.cbor");
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url =
      embedded_test_server()->GetURL("/sxg/test.example.org_fr_variant.sxg");
  MaybeTriggerPrefetchSXG(url, false);

  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  histogram_tester_.ExpectUniqueSample(
      kLoadResultHistogram, SignedExchangeLoadResult::kVariantMismatch,
      UsePrefetch() ? 2 : 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       MissingNosniff) {
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL(
      "/sxg/test.example.org_test_missing_nosniff.sxg");
  MaybeTriggerPrefetchSXG(url, false);

  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  RedirectObserver redirect_observer(shell()->web_contents());
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  EXPECT_EQ(303, redirect_observer.response_code());
  histogram_tester_.ExpectUniqueSample(
      kLoadResultHistogram, SignedExchangeLoadResult::kSXGServedWithoutNosniff,
      UsePrefetch() ? 2 : 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       InvalidContentType) {
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");
  InstallMockCert();
  InstallMockCertChainInterceptor();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL(
      "/sxg/test.example.org_test_invalid_content_type.sxg");
  MaybeTriggerPrefetchSXG(url, false);

  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  RedirectObserver redirect_observer(shell()->web_contents());
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  EXPECT_EQ(303, redirect_observer.response_code());
  histogram_tester_.ExpectUniqueSample(
      kLoadResultHistogram, SignedExchangeLoadResult::kVersionMismatch,
      UsePrefetch() ? 2 : 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest, Expired) {
  signed_exchange_utils::SetVerificationTimeForTesting(
      base::Time::UnixEpoch() +
      base::Seconds(SignedExchangeBrowserTestHelper::kSignatureHeaderExpires +
                    1));

  InstallMockCertChainInterceptor();
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL("/sxg/test.example.org_test.sxg");

  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  RedirectObserver redirect_observer(shell()->web_contents());
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  EXPECT_EQ(303, redirect_observer.response_code());
  histogram_tester_.ExpectUniqueSample(
      kLoadResultHistogram,
      SignedExchangeLoadResult::kSignatureVerificationError, 1);
  histogram_tester_.ExpectUniqueSample(
      "SignedExchange.SignatureVerificationResult",
      SignedExchangeSignatureVerifier::Result::kErrExpired, 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       RedirectBrokenSignedExchanges) {
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  constexpr const char* kBrokenExchanges[] = {
      "/sxg/test.example.org_test_invalid_magic_string.sxg",
      "/sxg/test.example.org_test_invalid_cbor_header.sxg",
  };

  for (const auto* broken_exchange : kBrokenExchanges) {
    SCOPED_TRACE(testing::Message()
                 << "testing broken exchange: " << broken_exchange);

    GURL url = embedded_test_server()->GetURL(broken_exchange);

    MaybeTriggerPrefetchSXG(url, false);

    std::u16string title = u"Fallback URL response";
    TitleWatcher title_watcher(shell()->web_contents(), title);
    GURL expected_commit_url = GURL("https://test.example.org/test/");
    EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
    EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  }
  histogram_tester_.ExpectTotalCount(kLoadResultHistogram,
                                     UsePrefetch() ? 4 : 2);
  histogram_tester_.ExpectBucketCount(
      kLoadResultHistogram, SignedExchangeLoadResult::kVersionMismatch,
      UsePrefetch() ? 2 : 1);
  histogram_tester_.ExpectBucketCount(
      kLoadResultHistogram, SignedExchangeLoadResult::kHeaderParseError,
      UsePrefetch() ? 2 : 1);
}

// TODO(crbug.com/41460883): Fails pretty often on Android.
// TODO(crbug.com/40201215): Fails flakily on all platforms with Synchronous
// HTML Parsing enabled.
IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       DISABLED_BadMICE) {
  InstallMockCertChainInterceptor();
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url =
      embedded_test_server()->GetURL("/sxg/test.example.org_test_bad_mice.sxg");

  MaybeTriggerPrefetchSXG(url, false);

  const std::u16string title_good = u"Reached End: false";
  const std::u16string title_bad = u"Reached End: true";
  TitleWatcher title_watcher(shell()->web_contents(), title_good);
  title_watcher.AlsoWaitForTitle(title_bad);
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title_good, title_watcher.WaitAndGetTitle());

  histogram_tester_.ExpectTotalCount(kLoadResultHistogram,
                                     UsePrefetch() ? 2 : 1);
  {
    SCOPED_TRACE(testing::Message()
                 << "testing SignedExchangeLoadResult::kMerkleIntegrityError");
    histogram_tester_.ExpectBucketCount(
        kLoadResultHistogram, SignedExchangeLoadResult::kMerkleIntegrityError,
        UsePrefetch() ? 2 : 1);
  }
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest, BadMICESmall) {
  InstallMockCertChainInterceptor();
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url = embedded_test_server()->GetURL(
      "/sxg/test.example.org_test_bad_mice_small.sxg");

  MaybeTriggerPrefetchSXG(url, false);

  // Note: TitleWatcher is not needed. NavigateToURL waits until navigation
  // complete.
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));

  histogram_tester_.ExpectTotalCount(kLoadResultHistogram,
                                     UsePrefetch() ? 2 : 1);
  {
    SCOPED_TRACE(testing::Message()
                 << "testing SignedExchangeLoadResult::kMerkleIntegrityError");
    histogram_tester_.ExpectBucketCount(
        kLoadResultHistogram, SignedExchangeLoadResult::kMerkleIntegrityError,
        UsePrefetch() ? 2 : 1);
  }
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest, CertNotFound) {
  InstallUrlInterceptor(GURL("https://cert.example.org/cert.msg"),
                        "content/test/data/sxg/404.msg");
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL("/sxg/test.example.org_test.sxg");

  MaybeTriggerPrefetchSXG(url, false);

  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  histogram_tester_.ExpectUniqueSample(
      kLoadResultHistogram, SignedExchangeLoadResult::kCertFetchError,
      UsePrefetch() ? 2 : 1);
  histogram_tester_.ExpectTotalCount(
      "SignedExchange.Time.CertificateFetch.Failure", UsePrefetch() ? 2 : 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       VaryCookieSxgUsed) {
  InstallUrlInterceptor(
      GURL("https://cert.example.org/cert.msg"),
      "content/test/data/sxg/test.example.org.public.pem.cbor");
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url =
      embedded_test_server()->GetURL("/sxg/test.example.org_vary_cookie.sxg");
  MaybeTriggerPrefetchSXG(url, true);

  if (!UsePrefetch()) {
    // Need to be in a page to execute JavaScript to trigger renderer initiated
    // navigation.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
  }

  std::u16string title = u"https://test.example.org/test/";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  // PrefetchedSignedExchangeCache is used only for renderer initiated
  // navigation. So use JavaScript to trigger renderer initiated navigation.
  EXPECT_TRUE(
      ExecJs(shell()->web_contents(),
             base::StringPrintf("location.href = '%s';", url.spec().c_str())));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());

  histogram_tester_.ExpectUniqueSample(kLoadResultHistogram,
                                       SignedExchangeLoadResult::kSuccess, 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       VaryCookieSxgNotUsed) {
  InstallUrlInterceptor(
      GURL("https://cert.example.org/cert.msg"),
      "content/test/data/sxg/test.example.org.public.pem.cbor");
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");
  InstallMockCert();

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        GURL("https://test.example.org/test/"), "milk=1"));

  GURL url =
      embedded_test_server()->GetURL("/sxg/test.example.org_vary_cookie.sxg");
  MaybeTriggerPrefetchSXG(url, true);

  if (!UsePrefetch()) {
    // Need to be in a page to execute JavaScript to trigger renderer initiated
    // navigation.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
  }

  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  // PrefetchedSignedExchangeCache is used only for renderer initiated
  // navigation. So use JavaScript to trigger renderer initiated navigation.
  EXPECT_TRUE(
      ExecJs(shell()->web_contents(),
             base::StringPrintf("location.href = '%s';", url.spec().c_str())));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());

  if (UsePrefetch()) {
    // kSuccess is recorded for successful prefetch, and
    // kHadCookieForCookielessOnlySXG is recorded by not being used for actual
    // navigation.
    histogram_tester_.ExpectBucketCount(kLoadResultHistogram,
                                        SignedExchangeLoadResult::kSuccess, 1);
    histogram_tester_.ExpectBucketCount(
        kLoadResultHistogram,
        SignedExchangeLoadResult::kHadCookieForCookielessOnlySXG, 1);
    histogram_tester_.ExpectTotalCount(kLoadResultHistogram, 2);
  } else {
    histogram_tester_.ExpectUniqueSample(
        kLoadResultHistogram,
        SignedExchangeLoadResult::kHadCookieForCookielessOnlySXG, 1);
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    SignedExchangeRequestHandlerBrowserTest,
    ::testing::Bool(),
    &SignedExchangeRequestHandlerBrowserTest::DescribeParams);

class SignedExchangeRequestHandlerDownloadBrowserTest
    : public SignedExchangeRequestHandlerBrowserTestBase {
 public:
  SignedExchangeRequestHandlerDownloadBrowserTest() = default;
  ~SignedExchangeRequestHandlerDownloadBrowserTest() override = default;

 protected:
  class DownloadObserver : public DownloadManager::Observer {
   public:
    DownloadObserver(DownloadManager* manager) : manager_(manager) {
      manager_->AddObserver(this);
    }
    ~DownloadObserver() override { manager_->RemoveObserver(this); }

    void WaitUntilDownloadCreated() { run_loop_.Run(); }

    const GURL& observed_url() const { return url_; }
    const std::string& observed_content_disposition() const {
      return content_disposition_;
    }

    // content::DownloadManager::Observer implementation.
    void OnDownloadCreated(content::DownloadManager* manager,
                           download::DownloadItem* item) override {
      url_ = item->GetURL();
      content_disposition_ = item->GetContentDisposition();
      run_loop_.Quit();
    }

   private:
    raw_ptr<DownloadManager> manager_;
    base::RunLoop run_loop_;
    GURL url_;
    std::string content_disposition_;
  };

  void SetUpOnMainThread() override {
    SignedExchangeRequestHandlerBrowserTestBase::SetUpOnMainThread();
    ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
    ShellDownloadManagerDelegate* delegate =
        static_cast<ShellDownloadManagerDelegate*>(
            shell()
                ->web_contents()
                ->GetBrowserContext()
                ->GetDownloadManagerDelegate());
    delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
  }

 private:
  base::ScopedTempDir downloads_directory_;
};

IN_PROC_BROWSER_TEST_F(SignedExchangeRequestHandlerDownloadBrowserTest,
                       Download) {
  std::unique_ptr<DownloadObserver> observer =
      std::make_unique<DownloadObserver>(
          shell()->web_contents()->GetBrowserContext()->GetDownloadManager());

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));

  const std::string load_sxg =
      "const iframe = document.createElement('iframe');"
      "iframe.src = './sxg/test.example.org_test_download.sxg';"
      "document.body.appendChild(iframe);";
  EXPECT_TRUE(ExecJs(shell()->web_contents(), load_sxg));
  observer->WaitUntilDownloadCreated();
  EXPECT_EQ(
      embedded_test_server()->GetURL("/sxg/test.example.org_test_download.sxg"),
      observer->observed_url());
  EXPECT_EQ("attachment; filename=test.sxg",
            observer->observed_content_disposition());
}

IN_PROC_BROWSER_TEST_F(SignedExchangeRequestHandlerDownloadBrowserTest,
                       DownloadInnerResponse) {
  InstallMockCert();
  InstallMockCertChainInterceptor();
  std::unique_ptr<DownloadObserver> observer =
      std::make_unique<DownloadObserver>(
          shell()->web_contents()->GetBrowserContext()->GetDownloadManager());

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));

  const std::string load_sxg =
      "const iframe = document.createElement('iframe');"
      "iframe.src = './sxg/test.example.org_bad_content_type.sxg';"
      "document.body.appendChild(iframe);";
  // Since the inner response has an invalid Content-Type and MIME sniffing
  // is disabled for Signed Exchange inner response, this should download the
  // inner response of the exchange.
  EXPECT_TRUE(ExecJs(shell()->web_contents(), load_sxg));
  observer->WaitUntilDownloadCreated();
  EXPECT_EQ(GURL("https://test.example.org/test/"), observer->observed_url());
}

IN_PROC_BROWSER_TEST_F(SignedExchangeRequestHandlerDownloadBrowserTest,
                       DataURLDownload) {
  const GURL sxg_url = GURL("data:application/signed-exchange,");
  std::unique_ptr<DownloadObserver> observer =
      std::make_unique<DownloadObserver>(
          shell()->web_contents()->GetBrowserContext()->GetDownloadManager());

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));

  const std::string load_sxg = base::StringPrintf(
      "const iframe = document.createElement('iframe');"
      "iframe.src = '%s';"
      "document.body.appendChild(iframe);",
      sxg_url.spec().c_str());
  EXPECT_TRUE(ExecJs(shell()->web_contents(), load_sxg));
  observer->WaitUntilDownloadCreated();
  EXPECT_EQ(sxg_url, observer->observed_url());
}

class SignedExchangeRequestHandlerRealCertVerifierBrowserTest
    : public SignedExchangeRequestHandlerBrowserTestBase {
 public:
  SignedExchangeRequestHandlerRealCertVerifierBrowserTest() {
    // Use "real" CertVerifier.
    disable_mock_cert_verifier();
    // This installs "root_ca_cert.pem" from which our test certificates are
    // created. (Needed for the tests that use real certificate, i.e.
    // RealCertVerifier)
    scoped_test_root_ = net::EmbeddedTestServer::RegisterTestCerts();
  }
  void SetUp() override {
    SignedExchangeHandler::SetShouldIgnoreCertValidityPeriodErrorForTesting(
        true);
    SignedExchangeRequestHandlerBrowserTestBase::SetUp();
  }
  void TearDown() override {
    SignedExchangeRequestHandlerBrowserTestBase::TearDown();
    SignedExchangeHandler::SetShouldIgnoreCertValidityPeriodErrorForTesting(
        false);
  }

 private:
  net::ScopedTestRoot scoped_test_root_;
};

// If this fails with ERR_CERT_DATE_INVALID, try to regenerate test data
// by running generate-test-certs.sh and generate-test-sxgs.sh in
// src/content/test/data/sxg. See https://crbug.com/1279652.
IN_PROC_BROWSER_TEST_F(SignedExchangeRequestHandlerRealCertVerifierBrowserTest,
                       Basic) {
  InstallUrlInterceptor(
      GURL("https://cert.example.org/cert.msg"),
      "content/test/data/sxg/test.example.org-long-validity.public.pem.cbor");
  InstallUrlInterceptor(GURL("https://test.example.org/test/"),
                        "content/test/data/sxg/fallback.html");

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL(
      "/sxg/test.example.org_long_cert_validity.sxg");

  // This signed exchange should pass CertVerifier::Verify() and then fail at
  // SignedExchangeHandler::CheckOCSPStatus() because of the dummy OCSP
  // response.
  // TODO(crbug.com/40564303): Make this test pass the OCSP check. We'll
  // need to either generate an OCSP response on the fly, or override the OCSP
  // verification time.
  std::u16string title = u"Fallback URL response";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  // Verify that it failed at the OCSP check step.
  histogram_tester_.ExpectUniqueSample(kLoadResultHistogram,
                                       SignedExchangeLoadResult::kOCSPError, 1);
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       LogicalUrlInServiceWorkerScope) {
  // SW-scope: https://test.example.org/test/
  // SXG physical URL: http://127.0.0.1:PORT/sxg/test.example.org_test.sxg
  // SXG logical URL: https://test.example.org/test/
  InstallMockCert();
  InstallMockCertChainInterceptor();

  const GURL install_sw_url =
      GURL("https://test.example.org/test/publisher-service-worker.html");

  InstallUrlInterceptor(install_sw_url,
                        "content/test/data/sxg/publisher-service-worker.html");
  InstallUrlInterceptor(
      GURL("https://test.example.org/test/publisher-service-worker.js"),
      "content/test/data/sxg/publisher-service-worker.js");
  {
    std::u16string title = u"Done";
    TitleWatcher title_watcher(shell()->web_contents(), title);
    EXPECT_TRUE(NavigateToURL(shell(), install_sw_url));
    EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  }
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url = embedded_test_server()->GetURL("/sxg/test.example.org_test.sxg");

  std::u16string title = u"https://test.example.org/test/";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  title_watcher.AlsoWaitForTitle(u"Generated");
  GURL expected_commit_url = GURL("https://test.example.org/test/");
  EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
  // The page content shoud be served from the signed exchange.
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
}

// TODO(crbug.com/40890897): Re-enable this test when de-flaked.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_NotControlledByDistributorsSW \
  DISABLED_NotControlledByDistributorsSW
#else
#define MAYBE_NotControlledByDistributorsSW NotControlledByDistributorsSW
#endif
IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       MAYBE_NotControlledByDistributorsSW) {
  // SW-scope: http://127.0.0.1:PORT/sxg/
  // SXG physical URL: http://127.0.0.1:PORT/sxg/test.example.org_test.sxg
  // SXG logical URL: https://test.example.org/test/
  InstallMockCert();
  InstallMockCertChainInterceptor();
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  const GURL install_sw_url = embedded_test_server()->GetURL(
      "/sxg/no-respond-with-service-worker.html");

  {
    std::u16string title = u"Done";
    TitleWatcher title_watcher(shell()->web_contents(), title);
    EXPECT_TRUE(NavigateToURL(shell(), install_sw_url));
    EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  }

  std::u16string title = u"https://test.example.org/test/";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/sxg/test.example.org_test.sxg"),
      GURL("https://test.example.org/test/") /* expected_commit_url */));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());

  // The page must not be controlled by the service worker of the physical URL.
  EXPECT_EQ(false, EvalJs(shell(), "!!navigator.serviceWorker.controller"));
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       NotControlledBySameOriginDistributorsSW) {
  // SW-scope: https://test.example.org/scope/
  // SXG physical URL: https://test.example.org/scope/test.example.org_test.sxg
  // SXG logical URL: https://test.example.org/test/
  InstallMockCert();
  InstallMockCertChainInterceptor();

  InstallUrlInterceptor(GURL("https://test.example.org/scope/test.sxg"),
                        "content/test/data/sxg/test.example.org_test.sxg");

  const GURL install_sw_url = GURL(
      "https://test.example.org/scope/no-respond-with-service-worker.html");

  InstallUrlInterceptor(
      install_sw_url,
      "content/test/data/sxg/no-respond-with-service-worker.html");
  InstallUrlInterceptor(
      GURL("https://test.example.org/scope/no-respond-with-service-worker.js"),
      "content/test/data/sxg/no-respond-with-service-worker.js");

  {
    std::u16string title = u"Done";
    TitleWatcher title_watcher(shell()->web_contents(), title);
    EXPECT_TRUE(NavigateToURL(shell(), install_sw_url));
    EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  }

  std::u16string title = u"https://test.example.org/test/";
  TitleWatcher title_watcher(shell()->web_contents(), title);
  EXPECT_TRUE(NavigateToURL(
      shell(), GURL("https://test.example.org/scope/test.sxg"),
      GURL("https://test.example.org/test/") /* expected_commit_url */));
  EXPECT_EQ(title, title_watcher.WaitAndGetTitle());

  // The page must not be controlled by the service worker of the physical URL.
  EXPECT_EQ(false, EvalJs(shell(), "!!navigator.serviceWorker.controller"));
}

IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest,
                       RegisterServiceWorkerFromSignedExchange) {
  // SXG physical URL: http://127.0.0.1:PORT/sxg/test.example.org_test.sxg
  // SXG logical URL: https://test.example.org/test/
  InstallMockCert();
  InstallMockCertChainInterceptor();

  InstallUrlInterceptor(
      GURL("https://test.example.org/test/publisher-service-worker.js"),
      "content/test/data/sxg/publisher-service-worker.js");

  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url = embedded_test_server()->GetURL("/sxg/test.example.org_test.sxg");
  GURL expected_commit_url = GURL("https://test.example.org/test/");

  {
    std::u16string title = u"https://test.example.org/test/";
    TitleWatcher title_watcher(shell()->web_contents(), title);
    EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
    EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
  }

  const std::string register_sw_script =
      "(async function() {"
      "  try {"
      "    const registration = await navigator.serviceWorker.register("
      "        'publisher-service-worker.js', {scope: './'});"
      "    return true;"
      "  } catch (e) {"
      "    return false;"
      "  }"
      "})();";
  // serviceWorker.register() fails because the document URL of
  // ServiceWorkerHost is empty.
  EXPECT_EQ(false, EvalJs(shell()->web_contents(), register_sw_script));
}

class SignedExchangeAcceptHeaderBrowserTest : public ContentBrowserTest {
 public:
  using self = SignedExchangeAcceptHeaderBrowserTest;
  SignedExchangeAcceptHeaderBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
  ~SignedExchangeAcceptHeaderBrowserTest() override = default;

 protected:
  void SetUp() override {
    https_server_.ServeFilesFromSourceDirectory("content/test/data");
    https_server_.RegisterRequestHandler(
        base::BindRepeating(&self::RedirectResponseHandler));
    https_server_.RegisterRequestHandler(base::BindRepeating(
        &self::FallbackSxgResponseHandler, base::Unretained(this)));
    https_server_.RegisterRequestMonitor(
        base::BindRepeating(&self::MonitorRequest, base::Unretained(this)));
    ASSERT_TRUE(https_server_.Start());

    ContentBrowserTest::SetUp();
  }

  void NavigateAndWaitForTitle(const GURL& url, const std::string& title) {
    std::u16string expected_title = base::ASCIIToUTF16(title);
    TitleWatcher title_watcher(shell()->web_contents(), expected_title);
    EXPECT_TRUE(NavigateToURL(shell(), url));
    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  }

  void NavigateWithRedirectAndWaitForTitle(const GURL& url,
                                           const GURL& expected_commit_url,
                                           const std::string& title) {
    std::u16string expected_title = base::ASCIIToUTF16(title);
    TitleWatcher title_watcher(shell()->web_contents(), expected_title);
    EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));
    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  }

  void CheckAcceptHeader(const GURL& url,
                         bool is_navigation,
                         bool is_fallback) {
    const auto accept_header = GetInterceptedAcceptHeader(url);
    ASSERT_TRUE(accept_header);
    EXPECT_EQ(*accept_header,
              !is_fallback
                  ? base::StrCat({kFrameAcceptHeaderValue,
                                  kAcceptHeaderSignedExchangeSuffix})
                  : (is_navigation
                         ? std::string(kFrameAcceptHeaderValue)
                         : std::string(network::kDefaultAcceptHeaderValue)));
  }

  void CheckNavigationAcceptHeader(const std::vector<GURL>& urls) {
    for (const auto& url : urls) {
      SCOPED_TRACE(url);
      CheckAcceptHeader(url, true /* is_navigation */, false /* is_fallback */);
    }
  }

  void CheckPrefetchAcceptHeader(const std::vector<GURL>& urls) {
    for (const auto& url : urls) {
      SCOPED_TRACE(url);
      CheckAcceptHeader(url, false /* is_navigation */,
                        false /* is_fallback */);
    }
  }

  void CheckFallbackAcceptHeader(const std::vector<GURL>& urls) {
    for (const auto& url : urls) {
      SCOPED_TRACE(url);
      CheckAcceptHeader(url, true /* is_navigation */, true /* is_fallback */);
    }
  }

  std::optional<std::string> GetInterceptedAcceptHeader(const GURL& url) const {
    base::AutoLock lock(url_accept_header_map_lock_);
    const auto it = url_accept_header_map_.find(url);
    if (it == url_accept_header_map_.end())
      return std::nullopt;
    return it->second;
  }

  void ClearInterceptedAcceptHeaders() {
    base::AutoLock lock(url_accept_header_map_lock_);
    url_accept_header_map_.clear();
  }

  net::EmbeddedTestServer https_server_;

 private:
  static std::unique_ptr<net::test_server::HttpResponse>
  RedirectResponseHandler(const net::test_server::HttpRequest& request) {
    if (!base::StartsWith(request.relative_url, "/r?",
                          base::CompareCase::SENSITIVE)) {
      return nullptr;
    }
    std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
    http_response->AddCustomHeader("Location", request.relative_url.substr(3));
    http_response->AddCustomHeader("Cache-Control", "no-cache");
    return std::move(http_response);
  }

  // Responds with a prologue-only signed exchange that triggers a fallback
  // redirect.
  std::unique_ptr<net::test_server::HttpResponse> FallbackSxgResponseHandler(
      const net::test_server::HttpRequest& request) {
    const std::string prefix = "/fallback_sxg?";
    if (!base::StartsWith(request.relative_url, prefix,
                          base::CompareCase::SENSITIVE)) {
      return nullptr;
    }
    std::string fallback_url(request.relative_url.substr(prefix.length()));
    if (fallback_url.empty()) {
      // If fallback URL is not specified, fallback to itself.
      fallback_url = https_server_.GetURL(prefix).spec();
    }

    std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_OK);
    http_response->set_content_type("application/signed-exchange;v=b3");

    std::string sxg("sxg1-b3", 8);
    sxg.push_back(fallback_url.length() >> 8);
    sxg.push_back(fallback_url.length() & 0xff);
    sxg += fallback_url;
    // FallbackUrlAndAfter() requires 6 more bytes for sizes of next fields.
    sxg.resize(sxg.length() + 6);

    http_response->set_content(sxg);
    return std::move(http_response);
  }

  void MonitorRequest(const net::test_server::HttpRequest& request) {
    const auto it = request.headers.find(net::HttpRequestHeaders::kAccept);
    if (it == request.headers.end())
      return;
    // Note this method is called on the EmbeddedTestServer's background thread.
    base::AutoLock lock(url_accept_header_map_lock_);
    url_accept_header_map_[request.base_url.Resolve(request.relative_url)] =
        it->second;
  }

  base::test::ScopedFeatureList feature_list_;
  base::test::ScopedFeatureList feature_list_for_accept_header_;

  // url_accept_header_map_ is accessed both on the main thread and on the
  // EmbeddedTestServer's background thread via MonitorRequest(), so it must be
  // locked.
  mutable base::Lock url_accept_header_map_lock_;
  std::map<GURL, std::string> url_accept_header_map_;
};

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest, Simple) {
  const GURL test_url = https_server_.GetURL("/sxg/test.html");
  NavigateAndWaitForTitle(test_url, test_url.spec());
  CheckNavigationAcceptHeader({test_url});
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest, Redirect) {
  const GURL test_url = https_server_.GetURL("/sxg/test.html");
  const GURL redirect_url = https_server_.GetURL("/r?" + test_url.spec());
  const GURL redirect_redirect_url =
      https_server_.GetURL("/r?" + redirect_url.spec());
  NavigateWithRedirectAndWaitForTitle(redirect_redirect_url, test_url,
                                      test_url.spec());

  CheckNavigationAcceptHeader({redirect_redirect_url, redirect_url, test_url});
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest,
                       FallbackRedirect) {
  const GURL fallback_url = https_server_.GetURL("/sxg/test.html");
  const GURL test_url =
      https_server_.GetURL("/fallback_sxg?" + fallback_url.spec());
  NavigateWithRedirectAndWaitForTitle(test_url, fallback_url,
                                      fallback_url.spec());

  CheckNavigationAcceptHeader({test_url});
  CheckFallbackAcceptHeader({fallback_url});
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest,
                       FallbackRedirectLoop) {
  const base::HistogramTester histogram_tester;
  base::RunLoop run_loop;
  FinishNavigationObserver finish_navigation_observer(shell()->web_contents(),
                                                      run_loop.QuitClosure());
  const GURL test_url = https_server_.GetURL("/fallback_sxg?");
  EXPECT_FALSE(NavigateToURL(shell()->web_contents(), test_url));
  run_loop.Run();
  ASSERT_TRUE(finish_navigation_observer.error_code())
      << "Unexpected navigation success: " << test_url;
  EXPECT_EQ(net::ERR_TOO_MANY_REDIRECTS,
            *finish_navigation_observer.error_code());
  histogram_tester.ExpectUniqueSample(kRedirectLoopHistogram, true, 1);
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest,
                       PrefetchEnabledPageEnabledTarget) {
  const GURL target = https_server_.GetURL("/sxg/hello.txt");
  const GURL page_url =
      https_server_.GetURL(std::string("/sxg/prefetch.html#") + target.spec());
  NavigateAndWaitForTitle(page_url, "OK");
  CheckPrefetchAcceptHeader({target});
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest,
                       PrefetchRedirect) {
  const GURL target = https_server_.GetURL("/sxg/hello.txt");
  const GURL redirect_url = https_server_.GetURL("/r?" + target.spec());
  const GURL redirect_redirect_url =
      https_server_.GetURL("/r?" + redirect_url.spec());

  const GURL page_url = https_server_.GetURL(
      std::string("/sxg/prefetch.html#") + redirect_redirect_url.spec());

  NavigateAndWaitForTitle(page_url, "OK");

  CheckPrefetchAcceptHeader({redirect_redirect_url, redirect_url, target});
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest, ServiceWorker) {
  NavigateAndWaitForTitle(https_server_.GetURL("/sxg/service-worker.html"),
                          "Done");

  const std::string frame_accept = kFrameAcceptHeaderValue;
  const std::string frame_accept_with_sxg =
      frame_accept + std::string(kAcceptHeaderSignedExchangeSuffix);
  const std::vector<std::string> scopes = {"/sxg/sw-scope-generated/",
                                           "/sxg/sw-scope-navigation-preload/",
                                           "/sxg/sw-scope-no-respond-with/"};
  for (const auto& scope : scopes) {
    SCOPED_TRACE(scope);
    const bool is_generated_scope =
        scope == std::string("/sxg/sw-scope-generated/");
    const GURL target_url = https_server_.GetURL(scope + "test.html");
    const GURL redirect_target_url =
        https_server_.GetURL("/r?" + target_url.spec());
    const GURL redirect_redirect_target_url =
        https_server_.GetURL("/r?" + redirect_target_url.spec());

    const std::string expected_title =
        is_generated_scope ? frame_accept_with_sxg : "Done";
    const std::optional<std::string> expected_target_accept_header =
        is_generated_scope ? std::nullopt
                           : std::optional<std::string>(frame_accept_with_sxg);

    NavigateAndWaitForTitle(target_url, expected_title);
    EXPECT_EQ(expected_target_accept_header,
              GetInterceptedAcceptHeader(target_url));
    ClearInterceptedAcceptHeaders();

    NavigateWithRedirectAndWaitForTitle(redirect_target_url, target_url,
                                        expected_title);
    CheckNavigationAcceptHeader({redirect_target_url});
    EXPECT_EQ(expected_target_accept_header,
              GetInterceptedAcceptHeader(target_url));
    ClearInterceptedAcceptHeaders();

    NavigateWithRedirectAndWaitForTitle(redirect_redirect_target_url,
                                        target_url, expected_title);
    CheckNavigationAcceptHeader(
        {redirect_redirect_target_url, redirect_target_url});
    EXPECT_EQ(expected_target_accept_header,
              GetInterceptedAcceptHeader(target_url));
    ClearInterceptedAcceptHeaders();
  }
}

IN_PROC_BROWSER_TEST_F(SignedExchangeAcceptHeaderBrowserTest,
                       ServiceWorkerPrefetch) {
  NavigateAndWaitForTitle(
      https_server_.GetURL("/sxg/service-worker-prefetch.html"), "Done");
  const std::string scope = "/sxg/sw-prefetch-scope/";
  const GURL target_url = https_server_.GetURL(scope + "test.html");

  const GURL prefetch_target =
      https_server_.GetURL(std::string("/sxg/hello.txt"));
  const std::string load_prefetch_script = base::StringPrintf(
      "(function loadPrefetch(urls) {"
      "  for (let url of urls) {"
      "    let link = document.createElement('link');"
      "    link.rel = 'prefetch';"
      "    link.href = url;"
      "    document.body.appendChild(link);"
      "  }"
      "  async function check() {"
      "    while (true) {"
      "      const entries = performance.getEntriesByType('resource');"
      "      const url_set = new Set(urls);"
      "      for (let entry of entries) {"
      "        url_set.delete(entry.name);"
      "      }"
      "      if (!url_set.size) {"
      "        return true;"
      "      } else {"
      "        await new Promise(resolve => setTimeout(resolve, 100));"
      "      }"
      "    }"
      "  }"
      "  return check();"
      "})(['%s'])",
      prefetch_target.spec().c_str());

  NavigateAndWaitForTitle(target_url, "Done");
  EXPECT_EQ(true, EvalJs(shell()->web_contents(), load_prefetch_script));
  CheckPrefetchAcceptHeader({prefetch_target});
  ClearInterceptedAcceptHeaders();
}

#if BUILDFLAG(ENABLE_REPORTING)

class SignedExchangeReportingBrowserTest
    : public SignedExchangeRequestHandlerBrowserTest {
 public:
  SignedExchangeReportingBrowserTest() {
    feature_list_.InitAndEnableFeature(
        net::features::kPartitionConnectionsByNetworkIsolationKey);
  }

  ~SignedExchangeReportingBrowserTest() override = default;

  void SetUpOnMainThread() override {
    SignedExchangeRequestHandlerBrowserTestBase::SetUpOnMainThread();

    // Make all attempts to connect to the domain the SXG is for fail with a DNS
    // error. Without this, they're fail with ERR_NOT_IMPLEMENTED. Making
    // requests fail with ERR_NAME_NOT_RESOLVED instead better matches what
    // happens in production.
    host_resolver()->AddSimulatedFailure("test.example.org");

    host_resolver()->AddRule("prefetch-origin.test", "127.0.0.1");

    // Set up certificate for the report server.
    net::CertVerifyResult ssl_server_result;
    ssl_server_result.verified_cert = ssl_server_.GetCertificate();
    ssl_server_result.is_issued_by_known_root = false;
    ssl_server_result.policy_compliance =
        net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
    mock_cert_verifier()->AddResultForCert(ssl_server_.GetCertificate(),
                                           ssl_server_result, net::OK);

    // Make the MockCertVerifier treat the signed exchange's certificate
    // "prime256v1-sha256.public.pem" as invalid for "test.example.org", which
    // should generate a report.
    scoped_refptr<net::X509Certificate> original_cert =
        SignedExchangeBrowserTestHelper::LoadCertificate();
    net::CertVerifyResult dummy_result;
    dummy_result.cert_status = net::CERT_STATUS_AUTHORITY_INVALID;
    dummy_result.verified_cert = original_cert;
    mock_cert_verifier()->AddResultForCertAndHost(
        original_cert, "test.example.org", dummy_result, net::OK);
    InstallMockCertChainInterceptor();

    learn_report_to_response_ =
        std::make_unique<net::test_server::ControllableHttpResponse>(
            &ssl_server_, kLearnReportToPath);
    report_response_ =
        std::make_unique<net::test_server::ControllableHttpResponse>(
            &ssl_server_, kReportPath);

    // Set up server used to serve the signed exchange.
    ssl_server_.ServeFilesFromSourceDirectory("content/test/data");
    EXPECT_TRUE(ssl_server_.Start());
  }

  const GURL sxg_url() const {
    return ssl_server_.GetURL("/sxg/test.example.org_test.sxg");
  }

  // The URL the SXG resource claims to be for.
  static GURL sxg_validity_url() {
    return GURL("https://test.example.org/test/");
  }

 protected:
  const char* kLearnReportToPath = "/report-to";
  const char* kReportPath = "/report";

  base::test::ScopedFeatureList feature_list_;

  // This server both serves the signed exchange and receives the report for the
  // certificate error.
  net::test_server::EmbeddedTestServer ssl_server_{
      net::test_server::EmbeddedTestServer::TYPE_HTTPS};

  std::unique_ptr<net::test_server::ControllableHttpResponse>
      learn_report_to_response_;
  std::unique_ptr<net::test_server::ControllableHttpResponse> report_response_;
};

IN_PROC_BROWSER_TEST_P(SignedExchangeReportingBrowserTest,
                       CertErrorSendsReport) {
  GURL report_url = ssl_server_.GetURL(kReportPath);

  // Get Report-To and NEL information for the server that serves the signed
  // exchange. The same site is also used for the NetworkAnonymizationKey. This
  // should result in sending a report to that server a request for that signed
  // exchange fails with a certificate error.
  {
    GURL learn_report_to_url = ssl_server_.GetURL(kLearnReportToPath);
    TestNavigationObserver same_tab_observer(shell()->web_contents(),
                                             1 /* number_of_navigations */);
    NavigationController::LoadURLParams params(learn_report_to_url);
    params.transition_type = ui::PageTransitionFromInt(
        ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
    shell()->web_contents()->GetController().LoadURLWithParams(params);
    learn_report_to_response_->WaitForRequest();
    learn_report_to_response_->Send("HTTP/1.1 200 OK\r\n");
    learn_report_to_response_->Send("Report-To: {\"endpoints\":[{\"url\":\"" +
                                    report_url.spec() +
                                    "\"}],\"max_age\":86400}\r\n");
    learn_report_to_response_->Send(
        "NEL: {\"report_to\":\"default\",\"max_age\":86400}\r\n");
    learn_report_to_response_->Send("\r\n");
    learn_report_to_response_->Done();
    same_tab_observer.Wait();
  }

  if (UsePrefetch()) {
    // Matches MaybeTriggerPrefetchSXG(), but uses |ssl_server_| instead of
    // embedded_test_server().
    const GURL prefetch_html_url = ssl_server_.GetURL(
        std::string("/sxg/prefetch.html#") + sxg_url().spec());
    std::u16string expected_title = u"FAIL";
    TitleWatcher title_watcher(shell()->web_contents(), expected_title);
    EXPECT_TRUE(NavigateToURL(shell(), prefetch_html_url));
    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  } else {
    // Try to navigate to the signed exchange. The signed exchange fails due
    // to the bad certificate. That results in trying to load the
    // resource the SXG refers to directly, which should fail with
    // ERR_NAME_NOT_RESOLVED.
    NavigationHandleObserver observer(shell()->web_contents(), sxg_url());
    EXPECT_FALSE(NavigateToURL(shell(), sxg_url()));
    EXPECT_EQ(sxg_validity_url(),
              shell()->web_contents()->GetLastCommittedURL());
    EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, observer.net_error_code());
  }

  // Check that a report was received.
  report_response_->WaitForRequest();
  EXPECT_EQ(report_url, report_response_->http_request()->GetURL());
  EXPECT_EQ(net::test_server::METHOD_POST,
            report_response_->http_request()->method);
}

INSTANTIATE_TEST_SUITE_P(All,
                         SignedExchangeReportingBrowserTest,
                         ::testing::Bool());

#endif  // BUILDFLAG(ENABLE_REPORTING)

class SignedExchangePKPBrowserTest
    : public SignedExchangeRequestHandlerBrowserTest {
 public:
  SignedExchangePKPBrowserTest() {
    scoped_feature_list_.InitAndEnableFeature(
        net::features::kStaticKeyPinningEnforcement);
  }
  void SetUpOnMainThread() override {
    SignedExchangeRequestHandlerBrowserTest::SetUpOnMainThread();

    // Make all attempts to connect to the domain the SXG is for fail with a DNS
    // error. Without this, they're fail with ERR_NOT_IMPLEMENTED. Making
    // requests fail with ERR_NAME_NOT_RESOLVED instead better matches what
    // happens in production.
    host_resolver()->AddSimulatedFailure("test.example.org");

    // Use a mock cert issued by a known root, so that PKP violation will not be
    // bypassed. It also causes a CT failure, but PKP error takes precedence.
    InstallMockCertByKnownRoot();
    InstallMockCertChainInterceptor();

    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
    EXPECT_TRUE(embedded_test_server()->Start());
    EnableStaticPins(embedded_test_server()->port());
  }

  void TearDownOnMainThread() override {
    if (IsOutOfProcessNetworkService()) {
      mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
      GetNetworkService()->BindTestInterfaceForTesting(
          network_service_test.BindNewPipeAndPassReceiver());
      base::test::TestFuture<void> future;
      network_service_test->SetTransportSecurityStateTestSource(
          false, future.GetCallback());
      EXPECT_TRUE(future.Wait());
    } else {
      RunOnNetworkThreadBlocking(
          base::BindOnce(&SignedExchangePKPBrowserTest::CleanUpOnNetworkThread,
                         base::Unretained(this)));
    }
    SignedExchangeRequestHandlerBrowserTest::TearDownOnMainThread();
  }

  void EnableStaticPins(int reporting_port) {
    StoragePartition* partition = shell()
                                      ->web_contents()
                                      ->GetBrowserContext()
                                      ->GetDefaultStoragePartition();
    {
      base::test::TestFuture<void> future;
      partition->GetNetworkContext()->EnableStaticKeyPinningForTesting(
          future.GetCallback());
      EXPECT_TRUE(future.Wait());
    }
    partition->FlushNetworkInterfaceForTesting();

    if (IsOutOfProcessNetworkService()) {
      mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
      GetNetworkService()->BindTestInterfaceForTesting(
          network_service_test.BindNewPipeAndPassReceiver());
      {
        base::test::TestFuture<void> future;
        network_service_test->SetTransportSecurityStateTestSource(
            true, future.GetCallback());
        EXPECT_TRUE(future.Wait());
      }
    } else {
      // TODO(crbug.com/40649862): Ideally, the NetworkServiceTest object
      // would be set up in-process on the network service's thread, and this
      // path would be removed.
      RunOnNetworkThreadBlocking(
          base::BindOnce(&SignedExchangePKPBrowserTest::
                             SetTransportSecurityStateSourceOnNetwork,
                         base::Unretained(this), reporting_port));
    }
  }

 private:
  void RunOnNetworkThreadBlocking(base::OnceClosure task) {
    base::RunLoop run_loop;
    content::GetNetworkTaskRunner()->PostTaskAndReply(
        FROM_HERE, std::move(task), run_loop.QuitClosure());
    run_loop.Run();
  }

  void SetTransportSecurityStateSourceOnNetwork(int reporting_port) {
    transport_security_state_source_ =
        std::make_unique<net::ScopedTransportSecurityStateSource>();
  }

  void CleanUpOnNetworkThread() { transport_security_state_source_.reset(); }

  // Only used when NetworkService is disabled. Accessed on network thread.
  std::unique_ptr<net::ScopedTransportSecurityStateSource>
      transport_security_state_source_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_P(SignedExchangePKPBrowserTest, PKPViolation) {
  GURL sxg_url =
      embedded_test_server()->GetURL("/sxg/test.example.org_test.sxg");
  GURL fallback_url("https://test.example.org/test/");

  MaybeTriggerPrefetchSXG(sxg_url, false);

  NavigationHandleObserver observer(shell()->web_contents(), sxg_url);
  EXPECT_FALSE(NavigateToURL(shell(), sxg_url));
  EXPECT_EQ(fallback_url, shell()->web_contents()->GetLastCommittedURL());
  EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, observer.net_error_code());
  histogram_tester_.ExpectUniqueSample(
      kLoadResultHistogram, SignedExchangeLoadResult::kPKPViolationError,
      UsePrefetch() ? 2 : 1);
}

INSTANTIATE_TEST_SUITE_P(All, SignedExchangePKPBrowserTest, ::testing::Bool());

}  // namespace content
