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

#include <string_view>
#include <tuple>

#include "base/base_paths.h"
#include "base/files/file_util.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/metrics/statistics_recorder.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/clear_site_data_utils.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.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/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.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/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "net/base/hash_value.h"
#include "net/base/schemeful_site.h"
#include "net/base/url_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/extras/shared_dictionary/shared_dictionary_usage_info.h"
#include "net/shared_dictionary/shared_dictionary_constants.h"
#include "net/shared_dictionary/shared_dictionary_isolation_key.h"
#include "net/ssl/client_cert_identity.h"
#include "net/ssl/client_cert_identity_test_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_private_key.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/test_data_directory.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 "services/network/public/mojom/shared_dictionary_access_observer.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"

using ::testing::UnorderedElementsAreArray;

namespace content {

namespace {

// The Structured Field sf-binary hash of sha256 of dictionary.
// (content/test/data/shared_dictionary/test.dict and test_dict.html).
constexpr std::string_view kExpectedDictionaryHashBase64 =
    ":U5abz16WDg7b8KS93msLPpOB4Vbef1uRzoORYkJw9BY=:";
constexpr net::SHA256HashValue kExpectedDictionaryHashValue = {
    {0x53, 0x96, 0x9b, 0xcf, 0x5e, 0x96, 0x0e, 0x0e, 0xdb, 0xf0, 0xa4,
     0xbd, 0xde, 0x6b, 0x0b, 0x3e, 0x93, 0x81, 0xe1, 0x56, 0xde, 0x7f,
     0x5b, 0x91, 0xce, 0x83, 0x91, 0x62, 0x42, 0x70, 0xf4, 0x16}};

constexpr std::string_view kUncompressedDataString =
    "test(\"This is uncompressed.\");";
constexpr std::string_view kErrorInvalidHashString =
    "test(\"Invalid dictionary hash.\");";
constexpr std::string_view kErrorNoSharedDictionaryAcceptEncodingString =
    "test(\"dcb or dcz is not set in accept-encoding header.\");";

constexpr std::string_view kCompressedDataOriginalString =
    "test(\"This is compressed test data using a test dictionary\");";

// kBrotliCompressedData is generated using the following commands:
// $ echo "This is a test dictionary." > /tmp/dict
// $ echo -n "test(\"This is compressed test data using a test dictionary\");" \
//     > /tmp/data
// $ echo -en '\xffDCB' > /tmp/out.dcb
// $ openssl dgst -sha256 -binary /tmp/dict >> /tmp/out.dcb
// $ brotli --stdout -D /tmp/dict /tmp/data >> /tmp/out.dcb
// $ xxd -i /tmp/out.dcb
constexpr uint8_t kBrotliCompressedData[] = {
    0xff, 0x44, 0x43, 0x42, 0x53, 0x96, 0x9b, 0xcf, 0x5e, 0x96, 0x0e, 0x0e,
    0xdb, 0xf0, 0xa4, 0xbd, 0xde, 0x6b, 0x0b, 0x3e, 0x93, 0x81, 0xe1, 0x56,
    0xde, 0x7f, 0x5b, 0x91, 0xce, 0x83, 0x91, 0x62, 0x42, 0x70, 0xf4, 0x16,
    0xa1, 0xe0, 0x01, 0x00, 0x64, 0x9c, 0xa4, 0xaa, 0xd7, 0x47, 0xe0, 0x26,
    0x4b, 0x95, 0x91, 0xb4, 0x46, 0x36, 0x09, 0xc9, 0xc7, 0x0e, 0x38, 0xe4,
    0x44, 0xe8, 0x72, 0x0d, 0x3c, 0x6e, 0xab, 0x35, 0x9b, 0x0f, 0x4b, 0xd1,
    0x67, 0x0c, 0xec, 0x7f, 0x9d, 0x1e, 0x99, 0x10, 0xf5, 0x1e, 0x57, 0x2f};
const std::string kBrotliCompressedDataString =
    std::string(reinterpret_cast<const char*>(kBrotliCompressedData),
                sizeof(kBrotliCompressedData));

// kZstdCompressedData is generated using the following commands:
// $ echo "This is a test dictionary." > /tmp/dict
// $ echo -n "test(\"This is compressed test data using a test dictionary\");" \
//     > /tmp/data
// $ echo -en '\x5e\x2a\x4d\x18\x20\x00\x00\x00' > /tmp/out.dcz
// $ openssl dgst -sha256 -binary /tmp/dict >> /tmp/out.dcz
// $ zstd -D /tmp/dict -f -o /tmp/tmp.zstd /tmp/data
// $ cat /tmp/tmp.zstd >> /tmp/out.dcz
// $ xxd -i /tmp/out.dcz
constexpr uint8_t kZstdCompressedData[] = {
    0x5e, 0x2a, 0x4d, 0x18, 0x20, 0x00, 0x00, 0x00, 0x53, 0x96, 0x9b, 0xcf,
    0x5e, 0x96, 0x0e, 0x0e, 0xdb, 0xf0, 0xa4, 0xbd, 0xde, 0x6b, 0x0b, 0x3e,
    0x93, 0x81, 0xe1, 0x56, 0xde, 0x7f, 0x5b, 0x91, 0xce, 0x83, 0x91, 0x62,
    0x42, 0x70, 0xf4, 0x16, 0x28, 0xb5, 0x2f, 0xfd, 0x24, 0x3d, 0x35, 0x01,
    0x00, 0xe0, 0x74, 0x65, 0x73, 0x74, 0x28, 0x22, 0x63, 0x6f, 0x6d, 0x70,
    0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x61, 0x74, 0x61, 0x20, 0x75, 0x73,
    0x69, 0x6e, 0x67, 0x22, 0x29, 0x3b, 0x03, 0x10, 0x05, 0xdf, 0x9f, 0x96,
    0x11, 0x21, 0x8a, 0x48, 0x20, 0xef, 0xeb};
const std::string kZstdCompressedDataString =
    std::string(reinterpret_cast<const char*>(kZstdCompressedData),
                sizeof(kZstdCompressedData));

constexpr std::string_view kUncompressedDataResultString =
    "This is uncompressed.";
constexpr std::string_view kCompressedDataResultString =
    "This is compressed test data using a test dictionary";

constexpr std::string_view kHttpAuthPath = "/shared_dictionary/path/http_auth";
const std::string kTestPath = "/shared_dictionary/path/test";

class SharedDictionaryAccessObserver : public WebContentsObserver {
 public:
  SharedDictionaryAccessObserver(WebContents* web_contents,
                                 base::RepeatingClosure on_accessed_callback)
      : WebContentsObserver(web_contents),
        on_accessed_callback_(std::move(on_accessed_callback)) {}

  const network::mojom::SharedDictionaryAccessDetailsPtr& details() const {
    return details_;
  }

 private:
  // WebContentsObserver overrides:
  void OnSharedDictionaryAccessed(
      NavigationHandle* navigation,
      const network::mojom::SharedDictionaryAccessDetails& details) override {
    details_ = details.Clone();
    on_accessed_callback_.Run();
  }
  void OnSharedDictionaryAccessed(
      RenderFrameHost* rfh,
      const network::mojom::SharedDictionaryAccessDetails& details) override {
    details_ = details.Clone();
    on_accessed_callback_.Run();
  }

  base::RepeatingClosure on_accessed_callback_;
  network::mojom::SharedDictionaryAccessDetailsPtr details_;
};

bool WaitForHistogram(const std::string& histogram_name,
                      std::optional<base::TimeDelta> timeout = std::nullopt) {
  // Need the polling of histogram because ScopedHistogramSampleObserver doesn't
  // support cross process metrics.
  base::Time start_time = base::Time::Now();
  while (!base::StatisticsRecorder::FindHistogram(histogram_name)) {
    content::FetchHistogramsFromChildProcesses();
    base::PlatformThread::Sleep(base::Milliseconds(5));
    if (timeout && base::Time::Now() > start_time + *timeout) {
      return false;
    }
  }
  return true;
}

enum class FeatureState {
  kDisabled,
  kBackendOnly,
  kFullyEnabled,
  kFullyEnabledWithZstd
};

enum class BrowserType { kNormal, kOffTheRecord };
std::string ToString(BrowserType browser_type) {
  switch (browser_type) {
    case BrowserType::kNormal:
      return "Normal";
    case BrowserType::kOffTheRecord:
      return "OffTheRecord";
  }
}

enum class FetchType {
  kLinkRelCompressionDictionary,
  kLinkRelCompressionDictionaryDocumentHeader,
  kLinkRelCompressionDictionarySubresourceHeader,
  kFetchApi,
  kFetchApiWithSameOriginMode,
  kFetchApiWithNoCorsMode,
  kFetchApiFromDedicatedWorker,
  kFetchApiFromSharedWorker,
  kFetchApiFromServiceWorker,
  kIframeNavigation,
};

std::string LinkRelCompressionDictionaryScript(const GURL& dictionary_url) {
  return JsReplace(R"(
              (()=>{
                const link = document.createElement('link');
                link.rel = 'compression-dictionary';
                link.href = $1;
                document.body.appendChild(link);
              })();
            )",
                   dictionary_url);
}

std::string LinkRelCompressionDictionaryDocumentHeaderScript(
    const GURL& dictionary_url) {
  return JsReplace(R"(
              (()=>{
                const iframe = document.createElement('iframe');
                iframe.src = new URL('with_dict_header.html', $1);
                document.body.appendChild(iframe);
              })();
            )",
                   dictionary_url);
}

std::string LinkRelCompressionDictionarySubresourceHeaderScript(
    const GURL& dictionary_url) {
  return JsReplace(R"(
              (()=>{
                fetch(new URL('with_dict_header.html', $1));
              })();
            )",
                   dictionary_url);
}

std::string FetchDictionaryScript(const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            try {
              await fetch($1);
            } catch (e) {
            }
          })();
        )",
                   dictionary_url);
}

std::string FetchDictionaryWithSameOriginModeScript(
    const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            try {
              await fetch($1, {mode: 'same-origin'});
            } catch (e) {
            }
          })();
        )",
                   dictionary_url);
}

std::string FetchDictionaryWithNoCorsModeScript(const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            try {
              await fetch($1, {mode: 'no-cors'});
            } catch (e) {
            }
          })();
        )",
                   dictionary_url);
}

std::string StartTestDedicatedWorkerScript(const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            const script = '/shared_dictionary/fetch_dictionary.js';
            const worker = new Worker(script);
            worker.postMessage($1);
          })();
        )",
                   dictionary_url);
}

std::string StartTestSharedWorkerScript(const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            const script =
                new URL(location).searchParams.has('otworker') ?
                  '/shared_dictionary/fetch_dictionary.js?ot=enabled' :
                  '/shared_dictionary/fetch_dictionary.js';
            const worker = new SharedWorker(script);
            worker.port.start();
            worker.port.postMessage($1);
          })();
        )",
                   dictionary_url);
}

std::string RegisterTestServiceWorkerScript(const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            const script =
                new URL(location).searchParams.has('otworker') ?
                  '/shared_dictionary/fetch_dictionary.js?ot=enabled' :
                  '/shared_dictionary/fetch_dictionary.js';
            const registration = await navigator.serviceWorker.register(
                script,
                {scope: '/shared_dictionary/'});
            registration.installing.postMessage($1);
          })();
        )",
                   dictionary_url);
}

std::string FetchTargetDataScript(const GURL& dictionary_url) {
  return JsReplace(R"(
          (async () => {
            const url = new URL('./path/test' ,$1);
            const response = await fetch(url);
            return response.text();
          })();
        )",
                   dictionary_url);
}

std::string IframeLoadScript(const GURL& url) {
  return JsReplace(R"(
  (async () => {
    const iframe = document.createElement('iframe');
    iframe.src = $1;
    const promise =
        new Promise(resolve => { iframe.addEventListener('load', resolve); });
    document.body.appendChild(iframe);
    await promise;
    try {
      return iframe.contentDocument.body.innerText;
    } catch {
      return 'failed to access iframe';
    }
  })()
                  )",
                   url);
}
std::optional<std::string> GetAvailableDictionary(
    const net::test_server::HttpRequest::HeaderMap& headers) {
      auto it = headers.find("available-dictionary");
      return it == headers.end() ? std::nullopt
                                 : std::make_optional(it->second);
}

bool HasSharedDictionaryAcceptEncoding(
    const net::test_server::HttpRequest::HeaderMap& headers) {
  auto it = headers.find(net::HttpRequestHeaders::kAcceptEncoding);
  if (it == headers.end()) {
    return false;
  }
    return it->second == "dcb, dcz" || base::EndsWith(it->second, ", dcb, dcz");
}

// A dummy ContentBrowserClient for testing HTTP Auth.
class DummyAuthContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  DummyAuthContentBrowserClient() = default;
  ~DummyAuthContentBrowserClient() override = default;
  DummyAuthContentBrowserClient(const DummyAuthContentBrowserClient&) = delete;
  DummyAuthContentBrowserClient& operator=(
      const DummyAuthContentBrowserClient&) = delete;

  // ContentBrowserClient method:
  std::unique_ptr<LoginDelegate> CreateLoginDelegate(
      const net::AuthChallengeInfo& auth_info,
      WebContents* web_contents,
      BrowserContext* browser_context,
      const GlobalRequestID& request_id,
      bool is_request_for_primary_main_frame_navigation,
      bool is_request_for_navigation,
      const GURL& url,
      scoped_refptr<net::HttpResponseHeaders> response_headers,
      bool first_auth_attempt,
      GuestPageHolder* guest,
      LoginDelegate::LoginAuthRequiredCallback auth_required_callback)
      override {
    create_login_delegate_called_ = true;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(auth_required_callback),
                       net::AuthCredentials(u"username", u"password")));
    return std::make_unique<LoginDelegate>();
  }

  bool create_login_delegate_called() const {
    return create_login_delegate_called_;
  }

 private:
  bool create_login_delegate_called_ = false;
};

// A dummy ContentBrowserClient for allowing all certificate errors.
class CertificateErrorAllowingContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  CertificateErrorAllowingContentBrowserClient() = default;
  ~CertificateErrorAllowingContentBrowserClient() override = default;
  CertificateErrorAllowingContentBrowserClient(
      const CertificateErrorAllowingContentBrowserClient&) = delete;
  CertificateErrorAllowingContentBrowserClient& operator=(
      const CertificateErrorAllowingContentBrowserClient&) = delete;

  // ContentBrowserClient method:
  void AllowCertificateError(
      WebContents* web_contents,
      int cert_error,
      const net::SSLInfo& ssl_info,
      const GURL& request_url,
      bool is_primary_main_frame_request,
      bool strict_enforcement
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
      , const GURL& origin_url,
      const std::string& referrer,
#endif
      base::OnceCallback<void(CertificateRequestResultType)> callback)
      override {
    allow_certificate_error_called_ = true;
    std::move(callback).Run(CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE);
  }

  bool allow_certificate_error_called() const {
    return allow_certificate_error_called_;
  }

 private:
  bool allow_certificate_error_called_ = false;
};

// A dummy ContentBrowserClient for setting client certificate.
class DummyClientCertStoreContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  DummyClientCertStoreContentBrowserClient() = default;
  ~DummyClientCertStoreContentBrowserClient() override = default;
  DummyClientCertStoreContentBrowserClient(
      const DummyClientCertStoreContentBrowserClient&) = delete;
  DummyClientCertStoreContentBrowserClient& operator=(
      const DummyClientCertStoreContentBrowserClient&) = delete;

  // ContentBrowserClient methods:
  std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
      BrowserContext* browser_context) override {
    net::ClientCertIdentityList cert_identity_list;
    {
      base::ScopedAllowBlockingForTesting allow_blocking;
      std::unique_ptr<net::FakeClientCertIdentity> cert_identity =
          net::FakeClientCertIdentity::CreateFromCertAndKeyFiles(
              net::GetTestCertsDirectory(), "client_1.pem", "client_1.pk8");
      EXPECT_TRUE(cert_identity.get());
      cert_identity_list.push_back(std::move(cert_identity));
    }
    return std::make_unique<DummyClientCertStore>(
        std::move(cert_identity_list));
  }
  base::OnceClosure SelectClientCertificate(
      BrowserContext* browser_context,
      int process_id,
      WebContents* web_contents,
      net::SSLCertRequestInfo* cert_request_info,
      net::ClientCertIdentityList client_certs,
      std::unique_ptr<ClientCertificateDelegate> delegate) override {
    select_client_certificate_called_ = true;
    CHECK_EQ(1u, client_certs.size());
    scoped_refptr<net::X509Certificate> cert(client_certs[0]->certificate());
    client_certs[0]->AcquirePrivateKey(base::BindOnce(
        [](std::unique_ptr<ClientCertificateDelegate> delegate,
           scoped_refptr<net::X509Certificate> cert,
           scoped_refptr<net::SSLPrivateKey> key) {
          delegate->ContinueWithCertificate(std::move(cert), std::move(key));
        },
        std::move(delegate), std::move(cert)));
    return base::OnceClosure();
  }

  bool select_client_certificate_called() const {
    return select_client_certificate_called_;
  }

 private:
  class DummyClientCertStore : public net::ClientCertStore {
   public:
    explicit DummyClientCertStore(net::ClientCertIdentityList list)
        : list_(std::move(list)) {}
    ~DummyClientCertStore() override = default;

    // net::ClientCertStore:
    void GetClientCerts(
        scoped_refptr<const net::SSLCertRequestInfo> cert_request_info,
        ClientCertListCallback callback) override {
      std::move(callback).Run(std::move(list_));
    }

   private:
    net::ClientCertIdentityList list_;
  };
  bool select_client_certificate_called_ = false;
};

class SharedDictionaryBrowserTestBase : public ContentBrowserTest {
 public:
  SharedDictionaryBrowserTestBase() = default;

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

 protected:
  int64_t GetTestDataFileSize(const std::string& name) {
    base::FilePath file_path;
    CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &file_path));
    {
      base::ScopedAllowBlockingForTesting allow_blocking;
      std::optional<int64_t> size_result = base::GetFileSize(
          file_path.Append(GetTestDataFilePath()).AppendASCII(name));
      CHECK(size_result.has_value());
      return size_result.value();
    }
  }
  std::string GetTestDataFile(const std::string& name) {
    base::FilePath file_path;
    CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &file_path));
    std::string contents;
    {
      base::ScopedAllowBlockingForTesting allow_blocking;
      CHECK(base::ReadFileToString(
          file_path.Append(GetTestDataFilePath()).AppendASCII(name),
          &contents));
    }
    return contents;
  }

  void RunWriteDictionaryTestImpl(Shell* shell,
                                  FetchType fetch_type,
                                  const GURL& page_url,
                                  const GURL& dictionary_url,
                                  const std::string& histogram_name,
                                  bool expect_success,
                                  bool navigate_to_page_url = true) {
    if (navigate_to_page_url) {
      EXPECT_TRUE(NavigateToURL(shell, page_url));
    }
    base::RunLoop write_loop;
    auto write_observer = std::make_unique<SharedDictionaryAccessObserver>(
        shell->web_contents(), write_loop.QuitClosure());

    base::HistogramTester histogram_tester;
    std::string script;
    switch (fetch_type) {
      case FetchType::kLinkRelCompressionDictionary:
        script = LinkRelCompressionDictionaryScript(dictionary_url);
        break;
      case FetchType::kLinkRelCompressionDictionaryDocumentHeader:
        script =
            LinkRelCompressionDictionaryDocumentHeaderScript(dictionary_url);
        break;
      case FetchType::kLinkRelCompressionDictionarySubresourceHeader:
        script =
            LinkRelCompressionDictionarySubresourceHeaderScript(dictionary_url);
        break;
      case FetchType::kFetchApi:
        script = FetchDictionaryScript(dictionary_url);
        break;
      case FetchType::kFetchApiWithSameOriginMode:
        script = FetchDictionaryWithSameOriginModeScript(dictionary_url);
        break;
      case FetchType::kFetchApiWithNoCorsMode:
        script = FetchDictionaryWithNoCorsModeScript(dictionary_url);
        break;
      case FetchType::kFetchApiFromDedicatedWorker:
        script = StartTestDedicatedWorkerScript(dictionary_url);
        break;
      case FetchType::kFetchApiFromSharedWorker:
        script = StartTestSharedWorkerScript(dictionary_url);
        break;
      case FetchType::kFetchApiFromServiceWorker:
        script = RegisterTestServiceWorkerScript(dictionary_url);
        break;
      case FetchType::kIframeNavigation:
        script = IframeLoadScript(dictionary_url);
        break;
    }
    EXPECT_TRUE(ExecJs(shell->web_contents()->GetPrimaryMainFrame(), script));
    if (expect_success) {
      write_loop.Run();
      ASSERT_TRUE(write_observer->details());
      EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kWrite,
                write_observer->details()->type);
      EXPECT_EQ(dictionary_url, write_observer->details()->url);
      EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(page_url),
                                                  net::SchemefulSite(page_url)),
                write_observer->details()->isolation_key);
      EXPECT_FALSE(write_observer->details()->is_blocked);
    }

    if (!expect_success) {
      EXPECT_FALSE(WaitForHistogram(histogram_name, base::Milliseconds(100)));
      EXPECT_FALSE(write_observer->details());

      EXPECT_EQ(kUncompressedDataString,
                EvalJs(shell->web_contents()->GetPrimaryMainFrame(),
                       FetchTargetDataScript(dictionary_url))
                    .ExtractString());
      return;
    }
    EXPECT_TRUE(WaitForHistogram(histogram_name));
    histogram_tester.ExpectBucketCount(
        histogram_name, GetTestDataFileSize("shared_dictionary/test.dict"),
        /*expected_count=*/1);
    base::RunLoop read_loop;

    auto read_observer = std::make_unique<SharedDictionaryAccessObserver>(
        shell->web_contents(), read_loop.QuitClosure());
    EXPECT_EQ(kCompressedDataOriginalString,
              EvalJs(shell->web_contents()->GetPrimaryMainFrame(),
                     FetchTargetDataScript(dictionary_url))
                  .ExtractString());
    read_loop.Run();
    ASSERT_TRUE(read_observer->details());
    EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
              read_observer->details()->type);
    EXPECT_EQ(dictionary_url.Resolve("path/test"),
              read_observer->details()->url);
    EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(page_url),
                                                net::SchemefulSite(page_url)),
              read_observer->details()->isolation_key);
    EXPECT_FALSE(read_observer->details()->is_blocked);
  }

  void RegisterTestRequestHandler(net::EmbeddedTestServer& server) {
    server.RegisterRequestHandler(
        base::BindRepeating(&SharedDictionaryBrowserTestBase::RequestHandler,
                            base::Unretained(this)));
  }

  std::vector<net::SharedDictionaryUsageInfo> GetSharedDictionaryUsageInfo(
      Shell* shell) {
    base::test::TestFuture<const std::vector<net::SharedDictionaryUsageInfo>&>
        result;
    shell->web_contents()
        ->GetBrowserContext()
        ->GetDefaultStoragePartition()
        ->GetNetworkContext()
        ->GetSharedDictionaryUsageInfo(result.GetCallback());
    return result.Get();
  }

  std::vector<url::Origin> GetOriginsBetween(Shell* shell,
                                             base::Time start_time,
                                             base::Time end_time) {
    base::test::TestFuture<const std::vector<url::Origin>&> result;
    shell->web_contents()
        ->GetBrowserContext()
        ->GetDefaultStoragePartition()
        ->GetNetworkContext()
        ->GetSharedDictionaryOriginsBetween(start_time, end_time,
                                            result.GetCallback());
    return result.Get();
  }

  network::mojom::NetworkContext* GetNetworkContext() {
    return shell()
        ->web_contents()
        ->GetBrowserContext()
        ->GetDefaultStoragePartition()
        ->GetNetworkContext();
  }

 private:
  std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
      const net::test_server::HttpRequest& request) {
    if (!base::StartsWith(request.relative_url, kTestPath)) {
      return nullptr;
    }
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    response->set_code(net::HTTP_OK);

    if (request.GetURL().GetQuery() == "html") {
      response->set_content_type("text/html");
    } else {
      response->set_content_type("application/javascript");
    }

    if (request.GetURL().GetQuery() != "no_acao" &&
        request.headers.find("origin") != request.headers.end()) {
      response->AddCustomHeader("Access-Control-Allow-Credentials", "true");
      response->AddCustomHeader("Access-Control-Allow-Origin",
                                request.headers.at("origin"));
    }
    std::optional<std::string> dict_hash =
        GetAvailableDictionary(request.headers);
    if (dict_hash) {
      if (*dict_hash == kExpectedDictionaryHashBase64) {
        if (HasSharedDictionaryAcceptEncoding(request.headers)) {
            response->AddCustomHeader(
                "content-encoding",
                net::shared_dictionary::kSharedZstdContentEncodingName);
            response->set_content(kZstdCompressedDataString);
        } else {
          response->set_content(kErrorNoSharedDictionaryAcceptEncodingString);
        }
      } else {
        response->set_content(kErrorInvalidHashString);
      }
    } else {
      response->set_content(kUncompressedDataString);
    }

    return response;
  }
};

// Tests end to end functionality of "compression dictionary transport" feature
// with fully enabled features.
class SharedDictionaryBrowserTest
    : public SharedDictionaryBrowserTestBase,
      public ::testing::WithParamInterface<BrowserType> {
 public:
  SharedDictionaryBrowserTest() {}
  SharedDictionaryBrowserTest(const SharedDictionaryBrowserTest&) = delete;
  SharedDictionaryBrowserTest& operator=(const SharedDictionaryBrowserTest&) =
      delete;

  // ContentBrowserTest implementation:
  void SetUpOnMainThread() override {
    RegisterTestRequestHandler(*embedded_test_server());
    RegisterRedirectRequestHandler(*embedded_test_server());
    RegisterClearSiteDataRequestHandler(*embedded_test_server());
    RegisterHttpAuthRequestHandler(*embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());

    cross_origin_server_ = std::make_unique<net::EmbeddedTestServer>();
    cross_origin_server()->AddDefaultHandlers(GetTestDataFilePath());
    RegisterTestRequestHandler(*cross_origin_server());
    RegisterRedirectRequestHandler(*cross_origin_server());
    RegisterClearSiteDataRequestHandler(*cross_origin_server());
    ASSERT_TRUE(cross_origin_server()->Start());

    host_resolver()->AddRule("*", "127.0.0.1");
  }
  void TearDownOnMainThread() override { off_the_record_shell_ = nullptr; }

  std::string FetchSameOriginRequest(const GURL& url) {
    return EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                  JsReplace(R"(
          (async () => {
            try {
              const res = await fetch($1, {mode: 'same-origin'});
              return await res.text();
            } catch {
              return 'failed to fetch';
            }
          })();
        )",
                            url))
        .ExtractString();
  }

  std::string LoadTestScript(const GURL& url) {
    return EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                  JsReplace(R"(
          (async () => {
            return await new Promise(resolve => {
              window.test = resolve;
              const script = document.createElement('script');
              script.src = $1;
              document.body.appendChild(script);
            });
          })();
        )",
                            url))
        .ExtractString();
  }
  std::string LoadTestScriptWithCrossOriginAnonymous(const GURL& url) {
    return EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                  JsReplace(R"(
          (async () => {
            return await new Promise(resolve => {
              window.test = resolve;
              const script = document.createElement('script');
              script.src = $1;
              script.addEventListener('error', () => {resolve('load failed');});
              script.crossOrigin = 'anonymous';
              document.body.appendChild(script);
            });
          })();
        )",
                            url))
        .ExtractString();
  }
  std::string LoadTestScriptWithCrossOriginUseCredentials(const GURL& url) {
    return EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                  JsReplace(R"(
          (async () => {
            return await new Promise(resolve => {
              window.test = resolve;
              const script = document.createElement('script');
              script.src = $1;
              script.addEventListener('error', () => {resolve('load failed');});
              script.crossOrigin = 'use-credentials';
              document.body.appendChild(script);
            });
          })();
        )",
                            url))
        .ExtractString();
  }
  std::string LoadTestIframe(const GURL& url) {
    return EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                  IframeLoadScript(url))
        .ExtractString();
  }

 protected:
  BrowserType GetBrowserType() const { return GetParam(); }
  net::EmbeddedTestServer* cross_origin_server() const {
    return cross_origin_server_.get();
  }

  Shell* GetTargetShell() {
    if (GetBrowserType() == BrowserType::kNormal) {
      return shell();
    }
    if (!off_the_record_shell_) {
      off_the_record_shell_ = CreateOffTheRecordBrowser();
    }
    return off_the_record_shell_;
  }
  network::mojom::NetworkContext* GetTargetNetworkContext() {
    return GetTargetShell()
        ->web_contents()
        ->GetBrowserContext()
        ->GetDefaultStoragePartition()
        ->GetNetworkContext();
  }

  void RunWriteDictionaryTest(FetchType fetch_type,
                              const GURL& page_url,
                              const GURL& dictionary_url,
                              bool expect_success = true) {
    RunWriteDictionaryTestImpl(
        GetTargetShell(), fetch_type, page_url, dictionary_url,
        GetBrowserType() == BrowserType::kNormal
            ? "Net.SharedDictionaryManagerOnDisk.DictionarySize"
            : "Net.SharedDictionaryWriterInMemory.DictionarySize",
        expect_success);
  }

  GURL GetURL(std::string_view relative_url) const {
    return embedded_test_server()->GetURL(relative_url);
  }
  GURL GetURL(std::string_view hostname, std::string_view relative_url) const {
    return embedded_test_server()->GetURL(hostname, relative_url);
  }
  GURL GetCrossOriginURL(std::string_view relative_url) const {
    return cross_origin_server()->GetURL(relative_url);
  }

  bool HasPreloadedSharedDictionaryInfo() {
    base::test::TestFuture<bool> future;
    GetTargetNetworkContext()->HasPreloadedSharedDictionaryInfoForTesting(
        future.GetCallback());
    return future.Get();
  }

  void SendMemoryPressureToNetworkService() {
    content::GetNetworkService()->OnMemoryPressure(
        base::MEMORY_PRESSURE_LEVEL_CRITICAL);
    // To make sure that OnMemoryPressure has been received by the network
    // service, send a GetNetworkList IPC and wait for the result.
    base::RunLoop run_loop;
    content::GetNetworkService()->GetNetworkList(
        net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES,
        base::BindLambdaForTesting(
            [&](const std::optional<net::NetworkInterfaceList>&
                    interface_list) { run_loop.Quit(); }));
    run_loop.Run();
  }

 private:
  void RegisterRedirectRequestHandler(net::EmbeddedTestServer& server) {
    server.RegisterRequestHandler(base::BindRepeating(
        &SharedDictionaryBrowserTest::RedirectRequestHandler,
        base::Unretained(this)));
  }
  std::unique_ptr<net::test_server::HttpResponse> RedirectRequestHandler(
      const net::test_server::HttpRequest& request) {
    if (!base::StartsWith(request.relative_url, "/redirect")) {
      return nullptr;
    }
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    response->set_code(net::HTTP_MOVED_PERMANENTLY);
    const std::string location = request.GetURL().GetQuery();
    response->AddCustomHeader("Location", location);
    if (request.headers.find("origin") != request.headers.end()) {
      response->AddCustomHeader("Access-Control-Allow-Credentials", "true");
      response->AddCustomHeader("Access-Control-Allow-Origin",
                                request.headers.at("origin"));
    }
    return response;
  }

  void RegisterClearSiteDataRequestHandler(net::EmbeddedTestServer& server) {
    server.RegisterRequestHandler(base::BindRepeating(
        &SharedDictionaryBrowserTest::ClearSiteDataRequestHandler,
        base::Unretained(this)));
  }
  std::unique_ptr<net::test_server::HttpResponse> ClearSiteDataRequestHandler(
      const net::test_server::HttpRequest& request) {
    if (!base::StartsWith(request.relative_url,
                          "/shared_dictionary/clear_site_data")) {
      return nullptr;
    }
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    response->set_code(net::HTTP_OK);

    // We need these Access-Control-Allow-* headers for cross origin tests.
    if (request.headers.find("origin") != request.headers.end()) {
      response->AddCustomHeader("Access-Control-Allow-Credentials", "true");
      response->AddCustomHeader("Access-Control-Allow-Origin",
                                request.headers.at("origin"));
    }

    if (request.GetURL().GetQuery() == "cache") {
      response->AddCustomHeader("Clear-Site-Data", "\"cache\"");
    } else if (request.GetURL().GetQuery() == "cookies") {
      response->AddCustomHeader("Clear-Site-Data", "\"cookies\"");
    } else if (request.GetURL().GetQuery() == "storage") {
      response->AddCustomHeader("Clear-Site-Data", "\"storage\"");
    }
    response->set_content("");
    return response;
  }
  void RegisterHttpAuthRequestHandler(net::EmbeddedTestServer& server) {
    server.RegisterRequestHandler(base::BindRepeating(
        &SharedDictionaryBrowserTest::HttpAuthRequestHandler,
        base::Unretained(this)));
  }
  std::unique_ptr<net::test_server::HttpResponse> HttpAuthRequestHandler(
      const net::test_server::HttpRequest& request) {
    if (!base::StartsWith(request.relative_url, kHttpAuthPath)) {
      return nullptr;
    }
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    if (base::Contains(request.headers, "Authorization")) {
      response->set_code(net::HTTP_OK);
      std::optional<std::string> dict_hash =
          GetAvailableDictionary(request.headers);
      if (dict_hash) {
        if (*dict_hash == kExpectedDictionaryHashBase64) {
          if (HasSharedDictionaryAcceptEncoding(request.headers)) {
            response->AddCustomHeader("content-encoding", "dcb");
            response->set_content(kBrotliCompressedDataString);
          } else {
            response->set_content(kErrorNoSharedDictionaryAcceptEncodingString);
          }
        } else {
          response->set_content(kErrorInvalidHashString);
        }
      } else {
        response->set_content(kUncompressedDataString);
      }
    } else {
      response->set_code(net::HTTP_UNAUTHORIZED);
      response->AddCustomHeader("WWW-Authenticate",
                                "Basic realm=\"test realm\"");
    }
    return response;
  }

  raw_ptr<Shell> off_the_record_shell_ = nullptr;
  std::unique_ptr<net::EmbeddedTestServer> cross_origin_server_;
};

bool WaitUntilHasPreloadSharedDictionaryInfo(
    network::mojom::NetworkContext* context,
    bool expected_value) {
  static constexpr auto kMaximumWaitTime = base::Seconds(3);
  static constexpr auto kPollInterval = base::Milliseconds(10);
  base::ElapsedTimer elapsed_timer;

  while (true) {
    base::test::TestFuture<bool> result_future;
    context->HasPreloadedSharedDictionaryInfoForTesting(
        result_future.GetCallback());
    if (result_future.Get() == expected_value) {
      return true;
    }
    if (elapsed_timer.Elapsed() > kMaximumWaitTime) {
      return false;
    }
    base::OneShotTimer one_shot_timer;
    base::test::TestFuture<void> timer_future;
    one_shot_timer.Start(FROM_HERE, kPollInterval, timer_future.GetCallback());
    timer_future.Get();
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         SharedDictionaryBrowserTest,
                         testing::Values(BrowserType::kNormal,
                                         BrowserType::kOffTheRecord),
                         [](const testing::TestParamInfo<BrowserType>& info) {
                           return ToString(info.param);
                         });

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       LinkRelCompressionDictionarySecureContext) {
  // http://127.0.0.1:PORT/ is secure context, so the dictionary should be
  // written.
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionarySecureContext) {
  // http://127.0.0.1:PORT/ is secure context, so the dictionary should be
  // written.
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       LinkRelCompressionDictionaryDocumentHeader) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionaryDocumentHeader,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       LinkRelCompressionDictionarySubresourceHeader) {
  RunWriteDictionaryTest(
      FetchType::kLinkRelCompressionDictionarySubresourceHeader,
      GetURL("/shared_dictionary/blank.html"),
      GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryWithSameOriginMode) {
  RunWriteDictionaryTest(FetchType::kFetchApiWithSameOriginMode,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryWithNoCorsMode) {
  RunWriteDictionaryTest(FetchType::kFetchApiWithNoCorsMode,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       LinkRelCompressionDictionaryInsecureContext) {
  // http://www.test/ is insecure context, so the dictionary should not be
  // written.
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("www.test", "/shared_dictionary/blank.html"),
                         GetURL("www.test", "/shared_dictionary/test.dict"),
                         /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryInsecureContext) {
  // http://www.test/ is insecure context, so the dictionary should not be
  // written.
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("www.test", "/shared_dictionary/blank.html"),
                         GetURL("www.test", "/shared_dictionary/test.dict"),
                         /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryFromDedicatedWorker) {
  RunWriteDictionaryTest(FetchType::kFetchApiFromDedicatedWorker,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

#if !BUILDFLAG(IS_ANDROID)
// Shared workers are not supported on Android.
IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryFromSharedWorker) {
  RunWriteDictionaryTest(FetchType::kFetchApiFromSharedWorker,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}
#endif  // !BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryFromServiceWorker) {
  RunWriteDictionaryTest(FetchType::kFetchApiFromServiceWorker,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchDictionaryUingIframeNavigation) {
  RunWriteDictionaryTest(FetchType::kIframeNavigation,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test_dict.html"),
                         /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CrossOriginLinkRelCompressionDictionary) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CrossOriginFetchDictionary) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       FetchCompressedDictionarySecureContext) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict.gz"));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CrossOriginLinkRelCompressionDictionaryWithoutACAO) {
  RunWriteDictionaryTest(
      FetchType::kLinkRelCompressionDictionary,
      GetURL("/shared_dictionary/blank.html"),
      GetCrossOriginURL("/shared_dictionary/test_no_acao.dict"),
      /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CrossOriginFetchDictionaryWithoutACAO) {
  RunWriteDictionaryTest(
      FetchType::kFetchApi, GetURL("/shared_dictionary/blank.html"),
      GetCrossOriginURL("/shared_dictionary/test_no_acao.dict"),
      /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CrossOriginFetchDictionaryWithNoCorsMode) {
  RunWriteDictionaryTest(FetchType::kFetchApiWithNoCorsMode,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"),
                         /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CrossOriginFetchDictionaryUingIframeNavigation) {
  RunWriteDictionaryTest(FetchType::kIframeNavigation,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test_dict.html"),
                         /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       SameOriginModeRequestSameOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(kCompressedDataOriginalString,
            FetchSameOriginRequest(GetURL(kTestPath)));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       NoCorsModeRequestSameOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScript(GetURL(kTestPath + "?1")))
      << "Same origin resource";

  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScript(GURL(GetURL("/redirect?").spec() +
                                GetURL(kTestPath + "?2").spec())))
      << "Redirected from same origin to same origin resource";

  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GURL(GetCrossOriginURL("/redirect?").spec() +
                                GetURL(kTestPath + "?3").spec())))
      << "Redirected from cross origin to same origin resource";

  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GURL(GetURL("/redirect?").spec() +
                                GetCrossOriginURL("/redirect?").spec() +
                                GetURL(kTestPath + "?4").spec())))
      << "Redirected from same origin via cross origin to same origin resource";
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       NoCorsModeRequestCrossOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GetCrossOriginURL(kTestPath + "?1")))
      << "Cross origin resource";

  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GURL(GetURL("/redirect?").spec() +
                                GetCrossOriginURL(kTestPath + "?2").spec())))
      << "Redirected from same origin to cross origin resource";

  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GURL(GetCrossOriginURL("/redirect?").spec() +
                                GetCrossOriginURL(kTestPath + "?3").spec())))
      << "Redirected from cross origin to cross origin resource";

  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GURL(GetCrossOriginURL("/redirect?").spec() +
                                GetURL("/redirect?").spec() +
                                GetCrossOriginURL(kTestPath + "?4").spec())))
      << "Redirected from cross origin via same origin to cross origin "
         "resource";
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CorsModeRequestSameOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginAnonymous(GetURL(kTestPath + "?1")))
      << "Same origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginAnonymous(GURL(
                GetURL("/redirect?").spec() + GetURL(kTestPath + "?2").spec())))
      << "Redirected from same origin to same origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginAnonymous(
                GURL(GetCrossOriginURL("/redirect?").spec() +
                     GetURL(kTestPath + "?3").spec())))
      << "Redirected from cross origin to same origin resource";
  EXPECT_EQ(
      kCompressedDataResultString,
      LoadTestScriptWithCrossOriginAnonymous(GURL(
          GetURL("/redirect?").spec() + GetCrossOriginURL("/redirect?").spec() +
          GetURL(kTestPath + "?4").spec())))
      << "Redirected from same origin via cross origin to same origin resource";
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CorModeRequestCrossOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginAnonymous(
                GetCrossOriginURL(kTestPath + "?1")))
      << "Cross origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginAnonymous(
                GURL(GetURL("/redirect?").spec() +
                     GetCrossOriginURL(kTestPath + "?2").spec())))
      << "Redirected from same origin to cross origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginAnonymous(
                GURL(GetCrossOriginURL("/redirect?").spec() +
                     GetCrossOriginURL(kTestPath + "?3").spec())))
      << "Redirected from cross origin to cross origin resource";
  EXPECT_EQ(
      kCompressedDataResultString,
      LoadTestScriptWithCrossOriginAnonymous(GURL(
          GetCrossOriginURL("/redirect?").spec() + GetURL("/redirect?").spec() +
          GetCrossOriginURL(kTestPath + "?4").spec())))
      << "Redirected from cross origin via same origin to cross origin "
         "resource";
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CorsModeRequestWithCredentialsSameOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(
      kCompressedDataResultString,
      LoadTestScriptWithCrossOriginUseCredentials(GetURL(kTestPath + "?1")))
      << "Same origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginUseCredentials(GURL(
                GetURL("/redirect?").spec() + GetURL(kTestPath + "?2").spec())))
      << "Redirected from same origin to same origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginUseCredentials(
                GURL(GetCrossOriginURL("/redirect?").spec() +
                     GetURL(kTestPath + "?3").spec())))
      << "Redirected from cross origin to same origin resource";
  EXPECT_EQ(
      kCompressedDataResultString,
      LoadTestScriptWithCrossOriginUseCredentials(GURL(
          GetURL("/redirect?").spec() + GetCrossOriginURL("/redirect?").spec() +
          GetURL(kTestPath + "?4").spec())))
      << "Redirected from same origin via cross origin to same origin resource";
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       CorModeRequestWithCredentialsCrossOriginResource) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginUseCredentials(
                GetCrossOriginURL(kTestPath + "?1")))
      << "Cross origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginUseCredentials(
                GURL(GetURL("/redirect?").spec() +
                     GetCrossOriginURL(kTestPath + "?2").spec())))
      << "Redirected from same origin to cross origin resource";
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScriptWithCrossOriginUseCredentials(
                GURL(GetCrossOriginURL("/redirect?").spec() +
                     GetCrossOriginURL(kTestPath + "?3").spec())))
      << "Redirected from cross origin to cross origin resource";
  EXPECT_EQ(
      kCompressedDataResultString,
      LoadTestScriptWithCrossOriginUseCredentials(GURL(
          GetCrossOriginURL("/redirect?").spec() + GetURL("/redirect?").spec() +
          GetCrossOriginURL(kTestPath + "?4").spec())))
      << "Redirected from cross origin via same origin to cross origin "
         "resource";
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, Navigation) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  const GURL target_url = GetURL(kTestPath + "?html");

  base::RunLoop loop;
  auto observer = std::make_unique<SharedDictionaryAccessObserver>(
      GetTargetShell()->web_contents(), loop.QuitClosure());
  EXPECT_TRUE(NavigateToURL(GetTargetShell(), target_url));
  loop.Run();

  ASSERT_TRUE(observer->details());
  EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
            observer->details()->type);
  EXPECT_EQ(target_url, observer->details()->url);
  EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(target_url),
                                              net::SchemefulSite(target_url)),
            observer->details()->isolation_key);
  EXPECT_FALSE(observer->details()->is_blocked);

  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   "document.body.innerText")
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       NavigationAfterSameOriginRedirect) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  const GURL target_url = GetURL(kTestPath + "?html");
  const GURL redirect_url =
      GURL(GetURL("/redirect?").spec() + GetURL(kTestPath + "?html").spec());

  base::RunLoop loop;
  auto observer = std::make_unique<SharedDictionaryAccessObserver>(
      GetTargetShell()->web_contents(), loop.QuitClosure());
  EXPECT_TRUE(NavigateToURL(GetTargetShell(), redirect_url, target_url));
  loop.Run();

  ASSERT_TRUE(observer->details());
  EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
            observer->details()->type);
  EXPECT_EQ(target_url, observer->details()->url);
  EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(target_url),
                                              net::SchemefulSite(target_url)),
            observer->details()->isolation_key);
  EXPECT_FALSE(observer->details()->is_blocked);

  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   "document.body.innerText")
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       NavigationAfterCrossOriginRedirect) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  const GURL target_url = GetURL(kTestPath + "?html");
  const GURL redirect_url = GURL(GetCrossOriginURL("/redirect?").spec() +
                                 GetURL(kTestPath + "?html").spec());

  base::RunLoop loop;
  auto observer = std::make_unique<SharedDictionaryAccessObserver>(
      GetTargetShell()->web_contents(), loop.QuitClosure());
  EXPECT_TRUE(NavigateToURL(GetTargetShell(), redirect_url, target_url));
  loop.Run();

  ASSERT_TRUE(observer->details());
  EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
            observer->details()->type);
  EXPECT_EQ(target_url, observer->details()->url);
  EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(target_url),
                                              net::SchemefulSite(target_url)),
            observer->details()->isolation_key);
  EXPECT_FALSE(observer->details()->is_blocked);

  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   "document.body.innerText")
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, IframeNavigation) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  const GURL target_url = GetURL(kTestPath + "?html");

  base::RunLoop loop;
  auto observer = std::make_unique<SharedDictionaryAccessObserver>(
      GetTargetShell()->web_contents(), loop.QuitClosure());

  EXPECT_EQ(kCompressedDataOriginalString, LoadTestIframe(target_url));
  loop.Run();

  ASSERT_TRUE(observer->details());
  EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
            observer->details()->type);
  EXPECT_EQ(target_url, observer->details()->url);
  EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(target_url),
                                              net::SchemefulSite(target_url)),
            observer->details()->isolation_key);
  EXPECT_FALSE(observer->details()->is_blocked);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       IframeNavigationAfterSameOriginRedirect) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  const GURL target_url = GetURL(kTestPath + "?html");
  const GURL redirect_url =
      GURL(GetURL("/redirect?").spec() + GetURL(kTestPath + "?html").spec());

  base::RunLoop loop;
  auto observer = std::make_unique<SharedDictionaryAccessObserver>(
      GetTargetShell()->web_contents(), loop.QuitClosure());

  EXPECT_EQ(kCompressedDataOriginalString, LoadTestIframe(redirect_url));
  loop.Run();

  ASSERT_TRUE(observer->details());
  EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
            observer->details()->type);
  EXPECT_EQ(target_url, observer->details()->url);
  EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(target_url),
                                              net::SchemefulSite(target_url)),
            observer->details()->isolation_key);
  EXPECT_FALSE(observer->details()->is_blocked);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       IframeNavigationAfterCrossOriginRedirect) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  const GURL target_url = GetURL(kTestPath + "?html");
  const GURL redirect_url = GURL(GetCrossOriginURL("/redirect?").spec() +
                                 GetURL(kTestPath + "?html").spec());

  base::RunLoop loop;
  auto observer = std::make_unique<SharedDictionaryAccessObserver>(
      GetTargetShell()->web_contents(), loop.QuitClosure());

  EXPECT_EQ(kCompressedDataOriginalString, LoadTestIframe(redirect_url));
  loop.Run();

  ASSERT_TRUE(observer->details());
  EXPECT_EQ(network::mojom::SharedDictionaryAccessDetails::Type::kRead,
            observer->details()->type);
  EXPECT_EQ(target_url, observer->details()->url);
  EXPECT_EQ(net::SharedDictionaryIsolationKey(url::Origin::Create(target_url),
                                              net::SchemefulSite(target_url)),
            observer->details()->isolation_key);
  EXPECT_FALSE(observer->details()->is_blocked);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, MatchDestEmptyString) {
  Shell* shell = GetTargetShell();
  EXPECT_TRUE(NavigateToURL(shell, GetURL("/shared_dictionary/blank.html")));

  // The response header contains `match-dest=("")` in Use-As-Dictionary header.
  const GURL dictionary_url = GetURL("/shared_dictionary/test.empty_dest.dict");
  EXPECT_TRUE(ExecJs(shell->web_contents()->GetPrimaryMainFrame(),
                     LinkRelCompressionDictionaryScript(dictionary_url)));

  // Wait for the dictionary to be registered.
  EXPECT_TRUE(WaitForHistogram(
      GetBrowserType() == BrowserType::kNormal
          ? "Net.SharedDictionaryManagerOnDisk.DictionarySize"
          : "Net.SharedDictionaryWriterInMemory.DictionarySize"));

  // Check that Chrome uses the dictionary while fetching the resource using
  // Fetch API.
  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(shell->web_contents()->GetPrimaryMainFrame(),
                   FetchTargetDataScript(dictionary_url))
                .ExtractString());

  // Check that Chrome doesn't use the dictionary while fetching a script.
  EXPECT_EQ(kUncompressedDataResultString,
            LoadTestScript(GetURL(kTestPath + "?for_script")));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, MatchDestScript) {
  Shell* shell = GetTargetShell();
  EXPECT_TRUE(NavigateToURL(shell, GetURL("/shared_dictionary/blank.html")));

  // The response header contains `match-dest=("script")` in Use-As-Dictionary
  // header.
  const GURL dictionary_url =
      GetURL("/shared_dictionary/test.script_dest.dict");
  EXPECT_TRUE(ExecJs(shell->web_contents()->GetPrimaryMainFrame(),
                     LinkRelCompressionDictionaryScript(dictionary_url)));

  // Wait for the dictionary to be registered.
  EXPECT_TRUE(WaitForHistogram(
      GetBrowserType() == BrowserType::kNormal
          ? "Net.SharedDictionaryManagerOnDisk.DictionarySize"
          : "Net.SharedDictionaryWriterInMemory.DictionarySize"));

  // Check that Chrome uses the dictionary while fetching a script.
  EXPECT_EQ(kCompressedDataResultString,
            LoadTestScript(GetURL(kTestPath + "?for_script")));

  // Check that Chrome doesn't use the dictionary while fetching the resource
  // using Fetch API.
  EXPECT_EQ(kUncompressedDataString,
            EvalJs(shell->web_contents()->GetPrimaryMainFrame(),
                   FetchTargetDataScript(dictionary_url))
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(
    SharedDictionaryBrowserTest,
    GetUsageInfoAndClearSharedDictionaryCacheForIsolationKey) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  net::SharedDictionaryIsolationKey isolation_key =
      net::SharedDictionaryIsolationKey(url::Origin::Create(GetURL("/")),
                                        net::SchemefulSite(GetURL("/")));

  EXPECT_THAT(GetSharedDictionaryUsageInfo(GetTargetShell()),
              UnorderedElementsAreArray({net::SharedDictionaryUsageInfo{
                  .isolation_key = isolation_key,
                  .total_size_bytes = static_cast<uint64_t>(
                      GetTestDataFileSize("shared_dictionary/test.dict"))}}));
  {
    base::RunLoop loop;
    GetTargetNetworkContext()->ClearSharedDictionaryCacheForIsolationKey(
        isolation_key, loop.QuitClosure());
    loop.Run();
  }
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, GetTotalSizeAndOrigins) {
  base::Time time1 = base::Time::Now();
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  base::Time time2 = base::Time::Now();

  EXPECT_TRUE(
      GetOriginsBetween(GetTargetShell(), time1 - base::Seconds(1), time1)
          .empty());
  EXPECT_THAT(GetOriginsBetween(GetTargetShell(), time1, time2),
              testing::ElementsAreArray({url::Origin::Create(GetURL("/"))}));
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, GetSharedDictionaryInfo) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  net::SharedDictionaryIsolationKey isolation_key =
      net::SharedDictionaryIsolationKey(url::Origin::Create(GetURL("/")),
                                        net::SchemefulSite(GetURL("/")));
  {
    base::RunLoop loop;
    GetTargetNetworkContext()->GetSharedDictionaryInfo(
        isolation_key,
        base::BindLambdaForTesting(
            [&](std::vector<network::mojom::SharedDictionaryInfoPtr> result) {
              ASSERT_EQ(1u, result.size());

              EXPECT_EQ("/shared_dictionary/path/*", result[0]->match);
              EXPECT_EQ(GetURL("/shared_dictionary/test.dict"),
                        result[0]->dictionary_url);
              EXPECT_EQ(static_cast<uint64_t>(
                            GetTestDataFileSize("shared_dictionary/test.dict")),
                        result[0]->size);
              EXPECT_EQ(kExpectedDictionaryHashValue, result[0]->hash);
              loop.Quit();
            }));
    loop.Run();
  }
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, ClearSiteData) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  base::RunLoop loop;
  content::ClearSiteData(
      GetTargetShell()->web_contents()->GetBrowserContext()->GetWeakPtr(),
      /*storage_partition_config=*/std::nullopt,
      /*origin=*/url::Origin::Create(GetURL("/")),
      content::ClearSiteDataTypeSet::All(),
      /*storage_buckets_to_remove=*/{},
      /*avoid_closing_connections=*/true,
      /*cookie_partition_key=*/std::nullopt,
      /*storage_key=*/std::nullopt,
      /*partitioned_state_allowed_only=*/false,
      /*callback=*/loop.QuitClosure());
  loop.Run();

  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataNavigationCacheDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  EXPECT_TRUE(NavigateToURL(
      GetTargetShell(), GetURL("/shared_dictionary/clear_site_data?cache")));
  // Navigation to a page which HTTP response header contains
  // `Clear-Site-Data: "cache"` should clear the shared dictionary.
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataNavigationCookiesDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  EXPECT_TRUE(NavigateToURL(
      GetTargetShell(), GetURL("/shared_dictionary/clear_site_data?cookies")));
  // Navigation to a page which HTTP response header contains
  // `Clear-Site-Data: "cookies"` should clear the shared dictionary.
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataNavigationStorageDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  EXPECT_TRUE(NavigateToURL(
      GetTargetShell(), GetURL("/shared_dictionary/clear_site_data?storage")));
  // Navigation to a page which HTTP response header contains
  // `Clear-Site-Data: "storage"` should NOT clear the shared dictionary.
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataFetchCacheDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  EXPECT_TRUE(
      ExecJs(GetTargetShell(),
             JsReplace("fetch($1);",
                       GetURL("/shared_dictionary/clear_site_data?cache"))));
  // Fetching a resource which HTTP response header contains
  // `Clear-Site-Data: "cache"` should clear the shared dictionary.
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataFetchCookiesDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  EXPECT_TRUE(
      ExecJs(GetTargetShell(),
             JsReplace("fetch($1);",
                       GetURL("/shared_dictionary/clear_site_data?cookies"))));
  // Fetching a resource which HTTP response header contains
  // `Clear-Site-Data: "cookies"` should clear the shared dictionary.
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataFetchStorageDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  EXPECT_TRUE(
      ExecJs(GetTargetShell(),
             JsReplace("fetch($1);",
                       GetURL("/shared_dictionary/clear_site_data?storage"))));
  // Fetching a resource which HTTP response header contains
  // `Clear-Site-Data: "storage"` should NOT clear the shared dictionary.
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataCrossOriginFetchCacheDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  // Setting the credentials flag because Clear-Site-Data header handling works
  // only when credentials flag is set.
  EXPECT_TRUE(ExecJs(
      GetTargetShell(),
      JsReplace(
          "fetch($1, {credentials: 'include'});",
          GetCrossOriginURL("/shared_dictionary/clear_site_data?cache"))));
  // Fetching a cross origin resource which HTTP response header contains
  // `Clear-Site-Data: "cache"` should clear the shared dictionary.
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataCrossOriginFetchCookiesDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  // Setting the credentials flag because Clear-Site-Data header handling works
  // only when credentials flag is set.
  EXPECT_TRUE(ExecJs(
      GetTargetShell(),
      JsReplace(
          "fetch($1, {credentials: 'include'});",
          GetCrossOriginURL("/shared_dictionary/clear_site_data?cookies"))));
  // Fetching a cross origin resource which HTTP response header contains
  // `Clear-Site-Data: "cookies"` should clear the shared dictionary.
  EXPECT_TRUE(GetSharedDictionaryUsageInfo(GetTargetShell()).empty());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       ClearSiteDataCrossOriginFetchStorageDirective) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetCrossOriginURL("/shared_dictionary/test.dict"));
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
  // Setting the credentials flag because Clear-Site-Data header handling works
  // only when credentials flag is set.
  EXPECT_TRUE(ExecJs(
      GetTargetShell(),
      JsReplace(
          "fetch($1, {credentials: 'include'});",
          GetCrossOriginURL("/shared_dictionary/clear_site_data?storage"))));
  // Fetching a cross origin resource which HTTP response header contains
  // `Clear-Site-Data: "storage"` should NOT clear the shared dictionary.
  EXPECT_EQ(1u, GetSharedDictionaryUsageInfo(GetTargetShell()).size());
}

// TODO(crbug.com/40599527): When we support wildcard directive
// `Clear-Site-Data: "*"", add test for it.

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       DictionaryReadCountForNavigation) {
  if (GetBrowserType() == BrowserType::kOffTheRecord) {
    // We want to test the behavior of SharedDictionaryStorageOnDisk.
    return;
  }
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(NavigateToURL(shell(), GetURL("/shared_dictionary/blank.html")));
  const std::string histogram_name =
      "Net.SharedDictionaryStorageOnDisk.MetadataReadTime.Empty";

  EXPECT_TRUE(WaitForHistogram(histogram_name));
  histogram_tester.ExpectTotalCount(histogram_name,
                                    /*expected_count=*/1);
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest, RestartWithAuth) {
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  DummyAuthContentBrowserClient browser_client;

  EXPECT_FALSE(browser_client.create_login_delegate_called());
  ASSERT_TRUE(NavigateToURL(GetTargetShell(), GetURL(kHttpAuthPath)));
  EXPECT_TRUE(browser_client.create_login_delegate_called());
  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   "document.body.innerText")
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       RestartedAfterCertErrorPageUseSbr) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
  RegisterTestRequestHandler(https_server);
  ASSERT_TRUE(https_server.Start());
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         https_server.GetURL("/shared_dictionary/blank.html"),
                         https_server.GetURL("/shared_dictionary/test.dict"));

  // Resetting the SSL config of the server to trigger a certificate error.
  ASSERT_TRUE(https_server.ResetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED,
                                          net::SSLServerConfig()));

  CertificateErrorAllowingContentBrowserClient browser_client;
  EXPECT_FALSE(browser_client.allow_certificate_error_called());
  EXPECT_TRUE(NavigateToURL(GetTargetShell(),
                            https_server.GetURL(kTestPath + "?html")));
  EXPECT_TRUE(browser_client.allow_certificate_error_called());

  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   "document.body.innerText")
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       RestartWithCertificatePageUseSbr) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
  RegisterTestRequestHandler(https_server);
  ASSERT_TRUE(https_server.Start());
  RunWriteDictionaryTest(FetchType::kFetchApi,
                         https_server.GetURL("/shared_dictionary/blank.html"),
                         https_server.GetURL("/shared_dictionary/test.dict"));

  // Resetting the SSL config of the server to require a client certificate.
  net::SSLServerConfig server_config;
  server_config.client_cert_type = net::SSLServerConfig::REQUIRE_CLIENT_CERT;
  ASSERT_TRUE(https_server.ResetSSLConfig(net::EmbeddedTestServer::CERT_OK,
                                          server_config));

  DummyClientCertStoreContentBrowserClient browser_client;
  EXPECT_FALSE(browser_client.select_client_certificate_called());
  EXPECT_TRUE(NavigateToURL(GetTargetShell(),
                            https_server.GetURL(kTestPath + "?html")));
  EXPECT_TRUE(browser_client.select_client_certificate_called());
  EXPECT_EQ(kCompressedDataOriginalString,
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   "document.body.innerText")
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       EncodedBodySizeAndDecodedBodySize) {
  RunWriteDictionaryTest(FetchType::kLinkRelCompressionDictionary,
                         GetURL("/shared_dictionary/blank.html"),
                         GetURL("/shared_dictionary/test.dict"));

  EXPECT_EQ(base::StringPrintf("%zu, %zu", kZstdCompressedDataString.size(),
                               kCompressedDataOriginalString.size()),
            EvalJs(GetTargetShell()->web_contents()->GetPrimaryMainFrame(),
                   JsReplace(R"(
          (async () => {
            const targetUrl = $1;
            const promise = new Promise((resolve) => {
              const observer = new PerformanceObserver((list) => {
                list.getEntries().forEach((entry) => {
                  if (entry.name == targetUrl) {
                    resolve(entry);
                  }
                });
              });
              observer.observe({ type: 'resource', buffered: true });
            });
            fetch(targetUrl);
            const entry = await promise;
            return entry.encodedBodySize + ', ' + entry.decodedBodySize;
          })();
        )",
                             GetURL("/shared_dictionary/path/test?")))
                .ExtractString());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       PreloadSharedDictionaryInfo) {
  mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
      preloaded_shared_dictionaries_handle;
  GetTargetNetworkContext()->PreloadSharedDictionaryInfoForDocument(
      {GetURL("/")},
      preloaded_shared_dictionaries_handle.InitWithNewPipeAndPassReceiver());
  EXPECT_TRUE(HasPreloadedSharedDictionaryInfo());
  preloaded_shared_dictionaries_handle.reset();
  EXPECT_FALSE(HasPreloadedSharedDictionaryInfo());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       DoNotPreloadDictionayUnderMemoryPressure) {
  SendMemoryPressureToNetworkService();
  mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
      preloaded_shared_dictionaries_handle;
  GetTargetNetworkContext()->PreloadSharedDictionaryInfoForDocument(
      {GetURL("/")},
      preloaded_shared_dictionaries_handle.InitWithNewPipeAndPassReceiver());
  FlushNetworkServiceInstanceForTesting();
  EXPECT_FALSE(HasPreloadedSharedDictionaryInfo());
}

IN_PROC_BROWSER_TEST_P(SharedDictionaryBrowserTest,
                       PreloadedDictionayDiscardedByMemoryPressure) {
  mojo::PendingRemote<network::mojom::PreloadedSharedDictionaryInfoHandle>
      preloaded_shared_dictionaries_handle;
  GetTargetNetworkContext()->PreloadSharedDictionaryInfoForDocument(
      {GetURL("/")},
      preloaded_shared_dictionaries_handle.InitWithNewPipeAndPassReceiver());
  EXPECT_TRUE(HasPreloadedSharedDictionaryInfo());
  SendMemoryPressureToNetworkService();
  FlushNetworkServiceInstanceForTesting();
  EXPECT_TRUE(WaitUntilHasPreloadSharedDictionaryInfo(GetTargetNetworkContext(),
                                                      false));
}

}  // namespace

}  // namespace content