910e62b5创建于 1月15日历史提交
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.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_utils.h"
#include "content/public/test/frame_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "net/base/features.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {
namespace {

using ::testing::IsEmpty;
using ::testing::Key;
using ::testing::UnorderedElementsAre;

// This file contains tests for cookie access via HTTP requests.
// See also (tests for cookie access via JavaScript):
// //content/browser/renderer_host/cookie_browsertest.cc

constexpr std::string_view kHostA = "a.test";
constexpr std::string_view kHostB = "b.test";
constexpr std::string_view kHostC = "c.test";
constexpr std::string_view kSameSiteNoneCookieName = "samesite_none_cookie";
constexpr std::string_view kSameSiteStrictCookieName = "samesite_strict_cookie";
constexpr std::string_view kSameSiteLaxCookieName = "samesite_lax_cookie";
constexpr std::string_view kSameSiteUnspecifiedCookieName =
    "samesite_unspecified_cookie";
constexpr std::string_view kHostPrefixCookieName = "__Host-prefixed_cookie";
constexpr std::string_view kSecurePrefixCookieName = "__Secure-prefixed_cookie";
constexpr std::string_view kEchoCookiesWithCorsPath = "/echocookieswithcors";

std::string FrameTreeForHostAndUrl(std::string_view host, const GURL& url) {
  return base::StrCat({host, "(", url.spec(), ")"});
}

std::string FrameTreeForUrl(const GURL& url) {
  return FrameTreeForHostAndUrl(kHostA, url);
}

GURL RedirectUrl(net::EmbeddedTestServer* test_server,
                 std::string_view host,
                 const GURL& target_url) {
  return test_server->GetURL(host, "/server-redirect?" + target_url.spec());
}

class HttpCookieBrowserTest : public ContentBrowserTest,
                              public ::testing::WithParamInterface<bool> {
 public:
  HttpCookieBrowserTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    feature_list_.InitWithFeatureState(
        net::features::kCookieSameSiteConsidersRedirectChain,
        DoesSameSiteConsiderRedirectChain());
  }

  ~HttpCookieBrowserTest() override = default;

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server()->AddDefaultHandlers(GetTestDataFilePath());
    embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
    ASSERT_TRUE(https_server()->Start());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  bool DoesSameSiteConsiderRedirectChain() { return GetParam(); }

  const std::string kSetSameSiteCookiesPath = base::StrCat({
      "/set-cookie?",
      kSameSiteStrictCookieName,
      "=1;SameSite=Strict&",
      kSameSiteLaxCookieName,
      "=1;SameSite=Lax&",
      kSameSiteUnspecifiedCookieName,
      "=1&",
      kSameSiteNoneCookieName,
      "=1;Secure;SameSite=None",
  });

  void SetSameSiteCookies(std::string_view host) {
    BrowserContext* context = shell()->web_contents()->GetBrowserContext();
    EXPECT_TRUE(SetCookie(
        context, https_server()->GetURL(host, "/"),
        base::StrCat({kSameSiteStrictCookieName, "=1; samesite=strict"})));
    EXPECT_TRUE(
        SetCookie(context, https_server()->GetURL(host, "/"),
                  base::StrCat({kSameSiteLaxCookieName, "=1; samesite=lax"})));
    EXPECT_TRUE(SetCookie(
        context, https_server()->GetURL(host, "/"),
        base::StrCat({kSameSiteNoneCookieName, "=1; samesite=none; secure"})));
    EXPECT_TRUE(
        SetCookie(context, https_server()->GetURL(host, "/"),
                  base::StrCat({kSameSiteUnspecifiedCookieName, "=1"})));
  }

  // Gets a path that causes the EmbeddedTestServer to attempt to set prefixed
  // cookies (some valid, some not).
  std::string GetSetPrefixedCookiesPath(std::string_view host) {
    return base::StrCat({
        "/set-cookie?",
        kSecurePrefixCookieName,
        "=1;Secure&",
        kHostPrefixCookieName,
        "=1;Secure;Path=/&",
        "__Secure-missing-attr=1&",
        "__Host-wrong-path=1;Secure&",
        "__Host-wrong-domain=1;Secure;Domain=",
        host,
        "&",
        "__Host-wrong-secure=1;Path=/&",
    });
  }

  // Sets some (valid) prefixed cookies.
  void SetPrefixedCookies(std::string_view host,
                          net::EmbeddedTestServer* test_server = nullptr) {
    if (!test_server) {
      test_server = https_server();
    }
    BrowserContext* context = shell()->web_contents()->GetBrowserContext();
    ASSERT_TRUE(
        SetCookie(context, test_server->GetURL(host, "/"),
                  base::StrCat({kHostPrefixCookieName, "=1; Secure; Path=/"})));
    ASSERT_TRUE(
        SetCookie(context, test_server->GetURL(host, "/"),
                  base::StrCat({kSecurePrefixCookieName, "=1; Secure"})));
  }

  GURL EchoCookiesUrl(net::EmbeddedTestServer* test_server,
                      std::string_view host) {
    return test_server->GetURL(host, "/echoheader?Cookie");
  }

  GURL SetSameSiteCookiesUrl(net::EmbeddedTestServer* test_server,
                             std::string_view host) {
    return test_server->GetURL(host, kSetSameSiteCookiesPath);
  }

  std::string ExtractFrameContent(RenderFrameHost* frame) const {
    return EvalJs(frame, "document.body.textContent").ExtractString();
  }

  uint32_t ClearCookies() {
    return DeleteCookies(shell()->web_contents()->GetBrowserContext(),
                         network::mojom::CookieDeletionFilter());
  }

  net::EmbeddedTestServer* https_server() { return &https_server_; }

  WebContents* web_contents() { return shell()->web_contents(); }

  // is_user_initiated_navigation - true if user initiates navigation to 404,
  //                              - false script initiates this navigation
  // is_cross_site_navigation - true if testing in a cross-site context,
  //                          - false if testing in a same-site context
  // is_user_initiated_reload - true if user initates the reload,
  //                            false if the reload via script
  std::string Test404ReloadCookie(bool is_user_initiated_navigation,
                                  bool is_cross_site_navigation,
                                  bool is_user_initiated_reload) {
    // Set target as A or B and cookies based on cross_site param
    std::string_view target = is_cross_site_navigation ? kHostB : kHostA;
    GURL target_URL =
        https_server()->GetURL(target, "/echo-cookie-with-status?status=404");
    SetSameSiteCookies(target);

    // Start at a website A
    EXPECT_TRUE(
        NavigateToURL(web_contents(), EchoCookiesUrl(https_server(), kHostA)));

    // Navigate method based on whether user or script initiated
    if (is_user_initiated_navigation) {
      EXPECT_TRUE(NavigateToURL(web_contents(), target_URL));
    } else {
      EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), target_URL));
    }

    // Trigger either user or script reload
    TestNavigationObserver nav_observer(web_contents());
    if (is_user_initiated_reload) {
      shell()->Reload();
    } else {
      ExecuteScriptAsync(
          web_contents()->GetPrimaryMainFrame(),
          content::JsReplace("window.location.reload($1);", true));
    }
    nav_observer.Wait();

    // Return the cookies rendered on frame
    return ExtractFrameContent(web_contents()->GetPrimaryMainFrame());
  }

 private:
  net::test_server::EmbeddedTestServer https_server_;
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendSameSiteCookies) {
  SetSameSiteCookies(kHostA);
  SetSameSiteCookies(kHostB);

  // Main frame browser-initiated navigation sends all SameSite cookies.
  ASSERT_TRUE(
      NavigateToURL(web_contents(), EchoCookiesUrl(https_server(), kHostA)));
  EXPECT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));

  // Main frame same-site (A => A) navigation sends all SameSite cookies.
  ASSERT_TRUE(
      NavigateToURLFromRenderer(web_contents()->GetPrimaryMainFrame(),
                                EchoCookiesUrl(https_server(), kHostA)));
  EXPECT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));

  // Main frame cross-site (A => B) navigation sends all but Strict cookies.
  ASSERT_TRUE(
      NavigateToURLFromRenderer(web_contents()->GetPrimaryMainFrame(),
                                EchoCookiesUrl(https_server(), kHostB)));
  EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
              net::CookieStringIs(UnorderedElementsAre(
                  Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
                  Key(kSameSiteUnspecifiedCookieName))));

  // Same-site iframe (A embedded in A) sends all SameSite cookies.
  EXPECT_THAT(
      content::ArrangeFramesAndGetContentFromLeaf(
          web_contents(), https_server(),
          FrameTreeForUrl(EchoCookiesUrl(https_server(), kHostA)), {0}),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));

  // Cross-site iframe (B embedded in A) sends only None cookies.
  EXPECT_THAT(
      content::ArrangeFramesAndGetContentFromLeaf(
          web_contents(), https_server(),
          FrameTreeForUrl(EchoCookiesUrl(https_server(), kHostB)), {0}),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendSameSiteCookies_Redirect) {
  SetSameSiteCookies(kHostA);

  // Main frame same-site redirect (A->A) sends all SameSite cookies.
  ASSERT_TRUE(NavigateToURL(
      web_contents(),
      RedirectUrl(https_server(), kHostA,
                  EchoCookiesUrl(https_server(), kHostA)),
      /*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
  EXPECT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));

  if (DoesSameSiteConsiderRedirectChain()) {
    // Main frame cross-site redirect (B->A) sends Lax but not Strict SameSite
    // cookies...
    ASSERT_TRUE(NavigateToURL(
        web_contents(),
        RedirectUrl(https_server(), kHostB,
                    EchoCookiesUrl(https_server(), kHostA)),
        /*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
    EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
                net::CookieStringIs(UnorderedElementsAre(
                    Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
                    Key(kSameSiteUnspecifiedCookieName))));

    // ... even if the first URL is same-site. (A->B->A)
    ASSERT_TRUE(NavigateToURL(
        web_contents(),
        RedirectUrl(https_server(), kHostA,
                    RedirectUrl(https_server(), kHostB,
                                EchoCookiesUrl(https_server(), kHostA))),
        /*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
    EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
                net::CookieStringIs(UnorderedElementsAre(
                    Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
                    Key(kSameSiteUnspecifiedCookieName))));
  } else {
    // If redirect chains are not considered, then cross-site redirects do not
    // make the request cross-site.
    ASSERT_TRUE(NavigateToURL(
        web_contents(),
        RedirectUrl(https_server(), kHostB,
                    EchoCookiesUrl(https_server(), kHostA)),
        /*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
    EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
                net::CookieStringIs(UnorderedElementsAre(
                    Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
                    Key(kSameSiteNoneCookieName),
                    Key(kSameSiteUnspecifiedCookieName))));

    ASSERT_TRUE(NavigateToURL(
        web_contents(),
        RedirectUrl(https_server(), kHostA,
                    RedirectUrl(https_server(), kHostB,
                                EchoCookiesUrl(https_server(), kHostA))),
        /*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
    EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
                net::CookieStringIs(UnorderedElementsAre(
                    Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
                    Key(kSameSiteNoneCookieName),
                    Key(kSameSiteUnspecifiedCookieName))));
  }

  // A same-site redirected iframe (A->A embedded in A) sends all SameSite
  // cookies.
  EXPECT_THAT(
      content::ArrangeFramesAndGetContentFromLeaf(
          web_contents(), https_server(),
          FrameTreeForUrl(RedirectUrl(https_server(), kHostA,
                                      EchoCookiesUrl(https_server(), kHostA))),
          {0}),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));

  if (DoesSameSiteConsiderRedirectChain()) {
    // A cross-site redirected iframe in a same-site context (B->A embedded in
    // A) does not send SameSite cookies...
    EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                    web_contents(), https_server(),
                    FrameTreeForUrl(
                        RedirectUrl(https_server(), kHostB,
                                    EchoCookiesUrl(https_server(), kHostA))),
                    {0}),
                net::CookieStringIs(
                    UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

    // ... even if the first URL is same-site. (A->B->A embedded in A)
    EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                    web_contents(), https_server(),
                    FrameTreeForUrl(RedirectUrl(
                        https_server(), kHostA,
                        RedirectUrl(https_server(), kHostB,
                                    EchoCookiesUrl(https_server(), kHostA)))),
                    {0}),
                net::CookieStringIs(
                    UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
  } else {
    // If redirect chains are not considered, then cross-site redirects do not
    // make the request cross-site.
    EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                    web_contents(), https_server(),
                    FrameTreeForUrl(
                        RedirectUrl(https_server(), kHostB,
                                    EchoCookiesUrl(https_server(), kHostA))),
                    {0}),
                net::CookieStringIs(UnorderedElementsAre(
                    Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
                    Key(kSameSiteNoneCookieName),
                    Key(kSameSiteUnspecifiedCookieName))));

    EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                    web_contents(), https_server(),
                    FrameTreeForUrl(
                        RedirectUrl(https_server(), kHostB,
                                    EchoCookiesUrl(https_server(), kHostA))),
                    {0}),
                net::CookieStringIs(UnorderedElementsAre(
                    Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
                    Key(kSameSiteNoneCookieName),
                    Key(kSameSiteUnspecifiedCookieName))));
  }
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SetSameSiteCookies) {
  // Main frame can set all SameSite cookies.
  ASSERT_TRUE(NavigateToURL(
      web_contents(), https_server()->GetURL(kHostA, kSetSameSiteCookiesPath)));
  EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
                                  https_server()->GetURL(kHostA, "/")),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kSameSiteStrictCookieName),
                  net::MatchesCookieWithName(kSameSiteLaxCookieName),
                  net::MatchesCookieWithName(kSameSiteNoneCookieName),
                  net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
  ASSERT_EQ(4U, ClearCookies());

  // Same-site iframe (A embedded in A) sets all SameSite cookies.
  const GURL url_a = SetSameSiteCookiesUrl(https_server(), kHostA);
  EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
                  web_contents(), https_server(), FrameTreeForUrl(url_a),
                  url::Origin::Create(url_a).GetURL()),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kSameSiteStrictCookieName),
                  net::MatchesCookieWithName(kSameSiteLaxCookieName),
                  net::MatchesCookieWithName(kSameSiteNoneCookieName),
                  net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
  ASSERT_EQ(4U, ClearCookies());

  // Cross-site iframe (B embedded in A) sets only None cookies.
  const GURL url_b = SetSameSiteCookiesUrl(https_server(), kHostB);
  EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
                  web_contents(), https_server(), FrameTreeForUrl(url_b),
                  url::Origin::Create(url_b).GetURL()),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kSameSiteNoneCookieName)));
  ASSERT_EQ(1U, ClearCookies());
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SetSameSiteCookies_Redirect) {
  // Same-site redirected main frame navigation can set all SameSite cookies.
  ASSERT_TRUE(NavigateToURL(
      web_contents(),
      RedirectUrl(https_server(), kHostA,
                  SetSameSiteCookiesUrl(https_server(), kHostA)),
      /*expected_commit_url=*/SetSameSiteCookiesUrl(https_server(), kHostA)));
  EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
                                  https_server()->GetURL(kHostA, "/")),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kSameSiteStrictCookieName),
                  net::MatchesCookieWithName(kSameSiteLaxCookieName),
                  net::MatchesCookieWithName(kSameSiteNoneCookieName),
                  net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
  ASSERT_EQ(4U, ClearCookies());

  // Cross-site redirected main frame navigation can set all SameSite cookies.
  ASSERT_TRUE(NavigateToURL(
      web_contents(),
      RedirectUrl(https_server(), kHostB,
                  SetSameSiteCookiesUrl(https_server(), kHostA)),
      /*expected_commit_url=*/SetSameSiteCookiesUrl(https_server(), kHostA)));
  EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
                                  https_server()->GetURL(kHostA, "/")),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kSameSiteStrictCookieName),
                  net::MatchesCookieWithName(kSameSiteLaxCookieName),
                  net::MatchesCookieWithName(kSameSiteNoneCookieName),
                  net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
  ASSERT_EQ(4U, ClearCookies());

  // A same-site redirected iframe sets all SameSite cookies.
  EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
                  web_contents(), https_server(),
                  FrameTreeForUrl(RedirectUrl(
                      https_server(), kHostA,
                      SetSameSiteCookiesUrl(https_server(), kHostA))),
                  https_server()->GetURL(kHostA, "/")),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kSameSiteStrictCookieName),
                  net::MatchesCookieWithName(kSameSiteLaxCookieName),
                  net::MatchesCookieWithName(kSameSiteNoneCookieName),
                  net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
  ASSERT_EQ(4U, ClearCookies());

  if (DoesSameSiteConsiderRedirectChain()) {
    // A cross-site redirected iframe only sets SameSite=None cookies.
    EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
                    web_contents(), https_server(),
                    FrameTreeForUrl(RedirectUrl(
                        https_server(), kHostB,
                        SetSameSiteCookiesUrl(https_server(), kHostA))),
                    https_server()->GetURL(kHostA, "/")),
                UnorderedElementsAre(
                    net::MatchesCookieWithName(kSameSiteNoneCookieName)));
    ASSERT_EQ(1U, ClearCookies());
  } else {
    EXPECT_THAT(
        content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
            web_contents(), https_server(),
            FrameTreeForUrl(
                RedirectUrl(https_server(), kHostB,
                            SetSameSiteCookiesUrl(https_server(), kHostA))),
            https_server()->GetURL(kHostA, "/")),
        UnorderedElementsAre(
            net::MatchesCookieWithName(kSameSiteStrictCookieName),
            net::MatchesCookieWithName(kSameSiteLaxCookieName),
            net::MatchesCookieWithName(kSameSiteNoneCookieName),
            net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
    ASSERT_EQ(4U, ClearCookies());
  }
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendPrefixedCookies) {
  SetPrefixedCookies(kHostA);

  // Main frame browser-initiated navigation sends all prefixed cookies.
  ASSERT_TRUE(
      NavigateToURL(web_contents(), EchoCookiesUrl(https_server(), kHostA)));
  EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
              net::CookieStringIs(UnorderedElementsAre(
                  Key(kSecurePrefixCookieName), Key(kHostPrefixCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       SendPrefixedCookies_OmitsIfInsecure) {
  SetPrefixedCookies(kHostA);

  // Main frame browser-initiated navigation omits all prefixed cookies on
  // insecure connections.
  ASSERT_TRUE(NavigateToURL(web_contents(),
                            EchoCookiesUrl(embedded_test_server(), kHostA)));
  EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
              net::CookieStringIs(IsEmpty()));
}

// embedded_test_server() uses http, which is insecure, but localhost is
// allowed to set prefixed cookies anyway.
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendPrefixedCookiesLocalhost) {
  SetPrefixedCookies("localhost", embedded_test_server());

  // Main frame browser-initiated navigation sends all prefixed cookies.
  ASSERT_TRUE(NavigateToURL(
      web_contents(), EchoCookiesUrl(embedded_test_server(), "localhost")));
  EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
              net::CookieStringIs(UnorderedElementsAre(
                  Key(kSecurePrefixCookieName), Key(kHostPrefixCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SetPrefixedCookies) {
  // Main frame can set cookies with all prefixes.
  ASSERT_TRUE(NavigateToURL(
      web_contents(),
      https_server()->GetURL(kHostA, GetSetPrefixedCookiesPath(kHostA))));
  EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
                                  https_server()->GetURL(kHostA, "/")),
              UnorderedElementsAre(
                  net::MatchesCookieWithName(kHostPrefixCookieName),
                  net::MatchesCookieWithName(kSecurePrefixCookieName)));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       SetPrefixedCookies_DisallowedIfInsecure) {
  // Main frame cannot set cookies with any prefix over an insecure connection.
  ASSERT_TRUE(NavigateToURL(web_contents(),
                            embedded_test_server()->GetURL(
                                kHostA, GetSetPrefixedCookiesPath(kHostA))));
  EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
                                  embedded_test_server()->GetURL(kHostA, "/")),
              IsEmpty());
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       ScriptNavigationSameSite404ScriptReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
                          /* is_cross_site_navigation= */ false,
                          /* is_user_initiated_reload= */ false),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       ScriptNavigationSameSite404UserReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
                          /* is_cross_site_navigation= */ false,
                          /* is_user_initiated_reload= */ true),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       ScriptNavigationCrossSite404ScriptReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
                          /* is_cross_site_navigation= */ true,
                          /* is_user_initiated_reload= */ false),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       ScriptNavigationCrossSite404UserReload) {
  EXPECT_THAT(Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
                                  /* is_cross_site_navigation= */ true,
                                  /* is_user_initiated_reload= */ true),
              net::CookieStringIs(UnorderedElementsAre(
                  Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
                  Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       UserNavigationSameSite404ScriptReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
                          /* is_cross_site_navigation= */ false,
                          /* is_user_initiated_reload= */ false),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       UserNavigationSameSite404UserReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
                          /* is_cross_site_navigation= */ false,
                          /* is_user_initiated_reload= */ true),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       UserNavigationCrossSite404ScriptReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
                          /* is_cross_site_navigation= */ true,
                          /* is_user_initiated_reload= */ false),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
                       UserNavigationCrossSite404UserReload) {
  EXPECT_THAT(
      Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
                          /* is_cross_site_navigation= */ true,
                          /* is_user_initiated_reload= */ true),
      net::CookieStringIs(UnorderedElementsAre(
          Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
          Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}

// Responds to a request to /echocookieswithcors with the cookies that were sent
// with the request. We can't use the default handler /echoheader?Cookie here,
// because it doesn't send the appropriate Access-Control-Allow-Origin and
// Access-Control-Allow-Credentials headers (which are required for this to
// work for cross-origin requests in the tests).
std::unique_ptr<net::test_server::HttpResponse>
HandleEchoCookiesWithCorsRequest(const net::test_server::HttpRequest& request) {
  if (request.relative_url != kEchoCookiesWithCorsPath) {
    return nullptr;
  }

  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  std::string content;

  // Get the 'Cookie' header that was sent in the request.
  if (auto it = request.headers.find(net::HttpRequestHeaders::kCookie);
      it != request.headers.end()) {
    content = it->second;
  }

  http_response->set_code(net::HTTP_OK);
  http_response->set_content_type("text/plain");
  // Set the cors enabled headers.
  if (auto it = request.headers.find(net::HttpRequestHeaders::kOrigin);
      it != request.headers.end()) {
    http_response->AddCustomHeader("Access-Control-Allow-Headers",
                                   "credentials");
    http_response->AddCustomHeader("Access-Control-Allow-Origin", it->second);
    http_response->AddCustomHeader("Origin", it->second);
    http_response->AddCustomHeader("Vary", "Origin");
    http_response->AddCustomHeader("Access-Control-Allow-Methods", "POST");
    http_response->AddCustomHeader("Access-Control-Allow-Credentials", "true");
  }
  http_response->set_content(content);

  return http_response;
}

class ThirdPartyCookiesHttpCookieBrowserTest : public ContentBrowserTest {
 public:
  ThirdPartyCookiesHttpCookieBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  ~ThirdPartyCookiesHttpCookieBrowserTest() override = default;

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server()->AddDefaultHandlers(GetTestDataFilePath());
    https_server()->RegisterRequestHandler(
        base::BindRepeating(&HandleEchoCookiesWithCorsRequest));
    ASSERT_TRUE(https_server()->Start());
  }

  WebContents* web_contents() const { return shell()->web_contents(); }

  net::EmbeddedTestServer* https_server() { return &https_server_; }

  GURL EchoCookiesUrl(std::string_view host) {
    return https_server()->GetURL(host, "/echoheader?Cookie");
  }

  std::string ExtractFrameContent(RenderFrameHost* frame) const {
    return EvalJs(frame, "document.body.textContent").ExtractString();
  }

  std::string ExtractCookieFromDocument(RenderFrameHost* frame) const {
    return EvalJs(frame, "document.cookie").ExtractString();
  }

  std::string PostWithCredentials(RenderFrameHost* frame, const GURL& url) {
    constexpr char script[] = R"JS(
      fetch($1, {method: 'POST', 'credentials' : 'include'}
      ).then((result) => result.text());
      )JS";
    return EvalJs(frame, JsReplace(script, url)).ExtractString();
  }

  EvalJsResult Fetch(RenderFrameHost* frame,
                     const GURL& url,
                     std::string_view mode,
                     std::string_view credentials) {
    constexpr char script[] = R"JS(
      fetch($1, {mode: $2, credentials: $3}).then(result => result.text());
    )JS";
    return EvalJs(frame, JsReplace(script, url, mode, credentials));
  }

  bool CookieStoreEmpty(RenderFrameHost* frame) {
    constexpr char script[] = R"JS(
          (async () => {
            let cookies = await cookieStore.getAll();
            return cookies.length == 0;
          })();
      )JS";
    return EvalJs(frame, script).ExtractBool();
  }

  EvalJsResult NavigateToURLWithPOST(RenderFrameHost* frame,
                                     std::string_view host) {
    TestNavigationObserver observer(web_contents());

    constexpr char script[] = R"JS(
        let form = document.createElement('form');
        form.setAttribute('method', 'POST');
        form.setAttribute('action', $1);
        document.body.appendChild(form);
        form.submit();
     )JS";

    EvalJsResult result =
        EvalJs(frame, JsReplace(script, EchoCookiesUrl(host)));
    observer.WaitForNavigationFinished();
    EXPECT_TRUE(WaitForLoadStop(web_contents()));
    return result;
  }

  EvalJsResult ReadCookiesViaFetchWithRedirect(
      RenderFrameHost* frame,
      std::string_view intermediate_host,
      std::string_view destination_host) {
    constexpr char script[] = "fetch($1).then((result) => result.text());";

    GURL redirect_url = RedirectUrl(https_server(), intermediate_host,
                                    EchoCookiesUrl(destination_host));

    return EvalJs(frame, JsReplace(script, redirect_url));
  }

 private:
  net::test_server::EmbeddedTestServer https_server_;
};

class ThirdPartyCookiesBlockedHttpCookieBrowserTest
    : public ThirdPartyCookiesHttpCookieBrowserTest {
 public:
  ThirdPartyCookiesBlockedHttpCookieBrowserTest() {
    feature_list_.InitWithFeatures(
        {
            net::features::kForceThirdPartyCookieBlocking,
        },
        {});
  }

  ~ThirdPartyCookiesBlockedHttpCookieBrowserTest() override = default;

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       SameSiteNoneCookieNavigateCrossSiteEmbedToSameSiteUrl) {
  ASSERT_TRUE(base::FeatureList::IsEnabled(
      net::features::kForceThirdPartyCookieBlocking));

  // Set SameSite=None cookie on kHostA.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));

  // Confirm cross-site iframe (kHostB embedded in kHostA) does not
  // send SameSite=None cookie to iframe.
  EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                  web_contents(), https_server(),
                  FrameTreeForUrl(EchoCookiesUrl(kHostB)), {0}),
              net::CookieStringIs(UnorderedElementsAre()));

  // Navigate embedded iframe from kHostB to kHostA and confirm that
  // SameSite=None cookie is sent.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostA)));

  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       SameSiteNoneCookieCrossSitePostRequest) {
  // Set and confirm SameSite=None cookie on top-level-site kHostB.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));

  ASSERT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));

  // Perform 'Post' to cross-site (kHostB) and confirm no cookie present in
  // method response. Since there is no redirect action at the same time as
  // the post, the cookie will be blocked as the request is being made
  // cross-site.
  EXPECT_THAT(PostWithCredentials(
                  web_contents()->GetPrimaryMainFrame(),
                  https_server()->GetURL(kHostB, kEchoCookiesWithCorsPath)),
              "");
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       SameSiteNoneCookieCrossSiteSubresourceNavigationPost) {
  // Set and confirm SameSite=None cookie on top-level-site kHostB.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));

  ASSERT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  // Starting at kHostA create a form that has an action value that causes a
  // navigation to a new top-level-site (kHostB). Submit the form to trigger the
  // navigation and confirm that no error has occurred in the response.
  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));

  ASSERT_TRUE(
      NavigateToURLWithPOST(web_contents()->GetPrimaryMainFrame(), kHostB)
          .is_ok());

  // Confirm that navigation from subresource occurred and cookies are still
  // available.
  EXPECT_THAT(web_contents()->GetLastCommittedURL().GetHost(), kHostB);

  EXPECT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       RedirectCrossSiteSubresourceToSameSiteUrl) {
  // Set and confirm SameSite=None cookie on top-level-site kHostA.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));

  ASSERT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  // Perform redirect from cross-site subresource and ensure that no cookie was
  // sent even though it was redirected to the top-level-site.
  EXPECT_EQ(ReadCookiesViaFetchWithRedirect(
                web_contents()->GetPrimaryMainFrame(), kHostB, kHostA),
            "None");
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       SameSiteNoneCookieBlockedOnABEmbeddedIframe) {
  // Set and confirm SameSite=None cookie on top-level-site kHostA is
  // present in the cookie header, document.cookie and cookie store.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
  // Confirm in cookie header.
  ASSERT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
  // Confirm in document.cookie.
  ASSERT_THAT(
      ExtractCookieFromDocument(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
  // Confirm in cookie store.
  ASSERT_TRUE(
      GetCookies(web_contents()->GetBrowserContext(), EchoCookiesUrl(kHostA))
          .starts_with(kSameSiteNoneCookieName));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));

  // Embed an iframe containing A in B and check cookie header.
  EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                  web_contents(), https_server(),
                  FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
              "None");

  // Check document.cookie.
  EXPECT_TRUE(ExtractCookieFromDocument(
                  ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0))
                  .empty());

  // Check cookie store.
  EXPECT_TRUE(
      CookieStoreEmpty(ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)));
}

IN_PROC_BROWSER_TEST_F(
    ThirdPartyCookiesBlockedHttpCookieBrowserTest,
    SameSiteNoneCookieBlockedInCrossSiteFetchRequestFromTopLevelFrame) {
  // Set and confirm SameSite=None cookie on Site A.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));

  ASSERT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  // From site B make a fetch call (with credentials) from site B to site A; and
  // check if cookies are present on the request.
  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));

  EXPECT_THAT(Fetch(web_contents()->GetPrimaryMainFrame(),
                    https_server()->GetURL(kHostA, kEchoCookiesWithCorsPath),
                    "cors", "include")
                  .ExtractString(),
              net::CookieStringIs(IsEmpty()));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       TestCrossSitePartitionKeyNotAvailable) {
  // Set cookie for site A kSameSite ancestor.
  // Create initial frame tree A->B (B is an iframe) and check cookie.
  // Navigate iframe with site B to A and confirm that cookie is present.

  // Set kSameSite cookie
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Embed an iframe containing B in A to create initial frame tree A->B.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostB)), {0}),
            "None");

  // Navigate embedded iframe B to A
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostA)));

  // Extract cookie from A
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       TestSubresourceRedirects) {
  // Initial frame tree A->B (B is an iframe).
  // A cookie is set for site C.
  // iframe B is navigated to site C.
  // Frame tree becomes A->C (C is an iframe).
  // Check if cookie set for C is present in C.

  // Embed an iframe containing B in A to create initial frame tree A->B.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostB)), {0}),
            "None");

  // Set SameSite=None partitioned cookie for kHostC from embedded iframe B.

  net::CookiePartitionKey partition_key =
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kCrossSite);

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostC, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;partitioned"}),
      net::CookieOptions::SameSiteCookieContext(
          net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE),
      partition_key));
  // confirm that there is a cookie with kHostC url in the mojom cookie manager
  // and that the cookie is partitioned and third party.
  std::vector<net::CanonicalCookie> cookies = GetCanonicalCookies(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostC, "/"),
      net::CookiePartitionKeyCollection(partition_key));
  ASSERT_EQ(cookies.size(), 1u);
  ASSERT_TRUE(cookies[0].IsPartitioned());
  ASSERT_TRUE(cookies[0].PartitionKey()->IsThirdParty());

  // Navigate embedded iframe B to C
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostC)));

  // Extract cookie from C
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       TestTopLevelRedirects) {
  // Navigate to Site A and set cookie on site A.
  // Redirect from site A to site B and back to site A.
  // Confirm cookie is present on site A after redirection.

  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
  // Check to make sure that there are no cookies set on kHostA.
  ASSERT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
              "None");

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Perform redirect from site A to site B and back to site A.
  ASSERT_TRUE(
      NavigateToURL(web_contents(),
                    RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
                    EchoCookiesUrl(kHostA)));

  EXPECT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(
    ThirdPartyCookiesBlockedHttpCookieBrowserTest,
    TestSameSiteEmbeddedResourceToCrossSiteEmbeddedResource) {
  // Initial frame tree A1->A2 (A2 is an iframe)
  // A cookie is set from top-level A1 for site B with kCrossSite ancestor chain
  // bit. iframe A2 is navigated to site B. Frame tree becomes A1->B (B is an
  // iframe). Check if cookie set from A1 is present in B.

  // Embed an iframe containing A in A to create initial frame tree A->A.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
            "None");

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kCrossSite)));

  // Navigate embedded iframe A2 to B.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostB)));

  // Confirm that the cookie is in the header sent to the iframe.
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       CrossSiteToSameSiteIframeRedirects) {
  // Set partitioned kSameSite ancestor cookie on top level site A.
  // Embed an iframe of site A and confirm cookie is accessible from iframe.
  // Navigate the iframe to a cross-domain (site B) and redirect back to A.
  // Confirm that cookie is accessible from the iframe.

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Embed an iframe containing A in A to create initial frame tree A->A.
  // Confirm that partitioned cookie is accessible from the iframe.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
            "samesite_none_cookie=1");

  // Navigate the iframe from A to B to A.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
      /*expected_commit_url=*/EchoCookiesUrl(kHostA)));

  // Confirm that the cookie is in the header sent to the iframe.
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       RedirectCrossSiteThroughSameSiteIframe) {
  // Set partitioned kSameSite ancestor cookie on top level site A.
  // Embed an iframe of site A and confirm cookie is accessible from iframe.
  // Navigate the iframe to a cross-domain (site B) and redirect back to a
  // different page with domain of A and then redirect back to the original
  // site. Confirm that cookie is accessible from the header in the final
  // redirect.

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Embed an iframe containing A in A to create initial frame tree A->A.
  // Confirm that partitioned cookie is accessible from the iframe.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
            "samesite_none_cookie=1");

  // Navigate the iframe from A to B redirecting to A redirecting to A.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      RedirectUrl(https_server(), kHostB,
                  RedirectUrl(https_server(), kHostA, EchoCookiesUrl(kHostA))),
      /*expected_commit_url=*/EchoCookiesUrl(kHostA)));

  // Confirm that the cookie is in the header sent to the iframe.
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       RedirectTwoCrossSitesThroughSameSiteIframe) {
  // Set partitioned kSameSite ancestor cookie on top level site A.
  // Embed an iframe of site A and confirm cookie is accessible from iframe.
  // Navigate the iframe to a cross-domain (site B) and redirect to a second
  // cross-domain (site C) and then redirect back to A.
  // Confirm that cookie is accessible from the header in the final
  // redirect.

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Embed an iframe containing A in A to create initial frame tree A->A.
  // Confirm that partitioned cookie is accessible from the iframe.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
            "samesite_none_cookie=1");

  // Navigate the iframe from A to B redirecting to C and then back to A.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      RedirectUrl(https_server(), kHostB,
                  RedirectUrl(https_server(), kHostC,
                              RedirectUrl(https_server(), kHostA,
                                          EchoCookiesUrl(kHostA)))),
      EchoCookiesUrl(kHostA)));

  // Confirm that the cookie is in the header sent to the iframe.
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(
    ThirdPartyCookiesBlockedHttpCookieBrowserTest,
    RedirectCrossSiteIframeToSameSiteThenNavigateToSameSite) {
  // Set partitioned kSameSite ancestor cookie on top level site A.
  // Embed an iframe of site A and confirm cookie is accessible from iframe.
  // Navigate the iframe to a cross-domain (site B) and redirect back to site A.
  // Then navigate to site A again.
  // Confirm that cookie is accessible from the header in the final navigation.

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Embed an iframe containing A in A to create initial frame tree A->A.
  // Confirm that partitioned cookie is accessible from the iframe.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
            "samesite_none_cookie=1");
  // Navigate the iframe from A to B redirecting to A.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
      /*expected_commit_url=*/EchoCookiesUrl(kHostA)));

  // Navigate to A from the iframe A that was redirected to site A from B.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostA)));

  // Confirm that the cookie is in the header sent to the iframe.
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
                       CrossSiteToSameSiteIframeNavigation) {
  // Set partitioned kSameSite ancestor cookie on top level site A.
  // Embed an iframe of site A and confirm cookie is accessible from iframe.
  // Navigate the iframe to a cross-domain (site B).
  // Then navigate the iframe back to site A.
  // Confirm that cookie is accessible from the iframe.

  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat(
          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
      net::CookiePartitionKey::FromURLForTesting(
          https_server()->GetURL(kHostA, "/"),
          net::CookiePartitionKey::AncestorChainBit::kSameSite)));

  // Embed an iframe containing A in A to create initial frame tree A->A.
  // Confirm that partitioned cookie is accessible from the iframe.
  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
                web_contents(), https_server(),
                FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
            "samesite_none_cookie=1");

  // Navigate the iframe from A to B. Then B to A.
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostB)));

  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
      EchoCookiesUrl(kHostA)));

  // Confirm that the cookie is in the header sent to the iframe.
  EXPECT_THAT(
      ExtractFrameContent(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  EXPECT_THAT(
      ExtractCookieFromDocument(
          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

class DevToolsOverridesThirdPartyCookiesBrowserTest
    : public ThirdPartyCookiesHttpCookieBrowserTest {
 public:
  DevToolsOverridesThirdPartyCookiesBrowserTest() = default;

  ~DevToolsOverridesThirdPartyCookiesBrowserTest() override = default;

  void SetUpOnMainThread() override {
    ThirdPartyCookiesHttpCookieBrowserTest::SetUpOnMainThread();
    web_contents_devtools_client.AttachToWebContents(shell()->web_contents());
    web_contents_devtools_client.SendCommandAsync("Network.enable");
  }

  void TearDownOnMainThread() override {
    web_contents_devtools_client.DetachProtocolClient();
    frame_devtools_client.DetachProtocolClient();
  }

 protected:
  void NavigateToPageWith3pIFrame(std::string_view host) {
    frame_devtools_client.DetachProtocolClient();
    GURL main_url(https_server()->GetURL(host, "/page_with_blank_iframe.html"));

    ASSERT_TRUE(content::NavigateToURL(web_contents(), main_url));
    EXPECT_TRUE(
        NavigateIframeToURL(web_contents(), "test_iframe",
                            https_server()->GetURL(kHostB, "/empty.html")));

    frame_devtools_client.AttachToFrameTreeHost(GetFrame());
    frame_devtools_client.SendCommandSync("Network.enable");
  }

  content::RenderFrameHost* GetFrame() {
    return ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
  }

  std::string SetCookieFromJS(content::RenderFrameHost* render_frame_host,
                              std::string cookie) {
    content::EvalJsResult result = content::EvalJs(
        render_frame_host,
        "document.cookie = '" + cookie + "; SameSite=None; Secure'",
        content::EXECUTE_SCRIPT_NO_USER_GESTURE);

    return result.ExtractString();
  }

  std::string ReadCookiesFromJS(content::RenderFrameHost* render_frame_host) {
    std::string res = content::EvalJs(render_frame_host, "document.cookie",
                                      content::EXECUTE_SCRIPT_NO_USER_GESTURE)
                          .ExtractString();

    return res;
  }

  void SendSetCookieControls(bool enable_third_party_cookie_restriction,
                             bool disable_third_party_cookie_metadata,
                             bool disable_third_party_cookie_heuristics) {
    base::Value::Dict command_params;
    web_contents_devtools_client.SendCommandSync("Network.enable");
    command_params.Set("enableThirdPartyCookieRestriction",
                       enable_third_party_cookie_restriction);
    command_params.Set("disableThirdPartyCookieMetadata",
                       disable_third_party_cookie_metadata);
    command_params.Set("disableThirdPartyCookieHeuristics",
                       disable_third_party_cookie_heuristics);
    web_contents_devtools_client.SendCommandSync("Network.setCookieControls",
                                                 std::move(command_params));
  }

  content::TestDevToolsProtocolClient web_contents_devtools_client;
  content::TestDevToolsProtocolClient frame_devtools_client;
};

IN_PROC_BROWSER_TEST_F(DevToolsOverridesThirdPartyCookiesBrowserTest,
                       DevToolsForceDisableTPCFromJS) {
  // Third-party access should work initially
  NavigateToPageWith3pIFrame(kHostA);
  SetCookieFromJS(GetFrame(), "cookieAllowed=true");
  EXPECT_EQ(ReadCookiesFromJS(GetFrame()), "cookieAllowed=true");

  // Turning on third-party cookie restriction
  SendSetCookieControls(/*enable_third_party_cookie_restriction=*/true,
                        /*disable_third_party_cookie_metadata=*/false,
                        /*disable_third_party_cookie_heuristics=*/false);

  // Refreshing so that RCM is re-created with new controls
  NavigateToPageWith3pIFrame("a.test");

  // Both of these should get blocked now
  SetCookieFromJS(GetFrame(), "cookieAllowed=false");
  EXPECT_EQ(ReadCookiesFromJS(GetFrame()), "");

  // Disabling the network domain should return to normal (unblocked) cookie
  // state
  frame_devtools_client.SendCommandSync("Network.disable");
  web_contents_devtools_client.SendCommandSync("Network.disable");

  // The cookie should be the same as before proving that the last
  // SetCookieFromJS didn't update the cookie
  EXPECT_EQ(ReadCookiesFromJS(GetFrame()), "cookieAllowed=true");
}

IN_PROC_BROWSER_TEST_F(DevToolsOverridesThirdPartyCookiesBrowserTest,
                       DevToolsForceDisableTPC) {
  // Set SameSite=None cookie on top-level-site kHostA.
  ASSERT_TRUE(SetCookie(
      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
      base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
  ASSERT_THAT(
      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
  // Embed an iframe containing A in B and check 3pc is allowed.
  ASSERT_THAT(
      content::ArrangeFramesAndGetContentFromLeaf(
          web_contents(), https_server(),
          FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));

  // Apply devtools overrides to enable 3pc restriction.
  SendSetCookieControls(/*enable_third_party_cookie_restriction=*/true,
                        /*disable_third_party_cookie_metadata=*/false,
                        /*disable_third_party_cookie_heuristics=*/false);

  // 3pc should be blocked due to devtools overrides.
  EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
                  web_contents(), https_server(),
                  FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
              "None");
  EXPECT_THAT(Fetch(web_contents()->GetPrimaryMainFrame(),
                    https_server()->GetURL(kHostA, kEchoCookiesWithCorsPath),
                    "cors", "include")
                  .ExtractString(),
              net::CookieStringIs(IsEmpty()));

  web_contents_devtools_client.SendCommandAsync("Network.disable");
  // The override should stop working and 3pc is re-allowed after devtools is
  // disabled.
  EXPECT_THAT(
      content::ArrangeFramesAndGetContentFromLeaf(
          web_contents(), https_server(),
          FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
  EXPECT_THAT(
      Fetch(web_contents()->GetPrimaryMainFrame(),
            https_server()->GetURL(kHostA, kEchoCookiesWithCorsPath), "cors",
            "include")
          .ExtractString(),
      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}

INSTANTIATE_TEST_SUITE_P(/* no label */,
                         HttpCookieBrowserTest,
                         ::testing::Bool());

}  // namespace
}  // namespace content