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

#include "content/public/browser/web_contents_observer.h"

#include "base/strings/to_string.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/embedded_worker_instance.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/allow_service_worker_result.h"
#include "content/public/browser/cookie_access_details.h"
#include "content/public/browser/focused_node_details.h"
#include "content/public/browser/navigation_handle.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_mock_cert_verifier.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/base/features.h"
#include "net/cert/cert_verify_result.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/site_for_cookies.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/quic_simple_test_server.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/dom/dom_node_id.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/base/ui_base_switches.h"

using testing::_;
using testing::NotNull;

namespace content {

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

 protected:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  // Some platforms are flaky due to relatively slow loading interacting
  // with deferred commits.
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
  }

  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  RenderFrameHostImpl* top_frame_host() {
    return static_cast<RenderFrameHostImpl*>(
        web_contents()->GetPrimaryMainFrame());
  }

  base::test::ScopedFeatureList feature_list_;
};

namespace {

class ServiceWorkerAccessObserver : public WebContentsObserver {
 public:
  explicit ServiceWorkerAccessObserver(WebContentsImpl* web_contents)
      : WebContentsObserver(web_contents) {}

  MOCK_METHOD3(OnServiceWorkerAccessed,
               void(NavigationHandle*, const GURL&, AllowServiceWorkerResult));
  MOCK_METHOD3(OnServiceWorkerAccessed,
               void(RenderFrameHost*, const GURL&, AllowServiceWorkerResult));
};

}  // namespace

IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       OnServiceWorkerAccessed) {
  GURL service_worker_scope =
      embedded_test_server()->GetURL("/service_worker/");
  {
    // 1) Navigate to a page and register a ServiceWorker. Expect a notification
    // to be called when the service worker is accessed from a frame.
    ServiceWorkerAccessObserver observer(web_contents());
    base::RunLoop run_loop;
    EXPECT_CALL(
        observer,
        OnServiceWorkerAccessed(
            testing::Matcher<RenderFrameHost*>(NotNull()), service_worker_scope,
            AllowServiceWorkerResult::FromPolicy(false, false)))
        .WillOnce([&]() { run_loop.Quit(); });
    EXPECT_TRUE(NavigateToURL(
        web_contents(), embedded_test_server()->GetURL(
                            "/service_worker/create_service_worker.html")));
    EXPECT_EQ("DONE",
              EvalJs(top_frame_host(),
                     "register('fetch_event.js', '/service_worker/');"));
    run_loop.Run();
  }

  {
    // 2) Navigate to a page in scope of the previously registered ServiceWorker
    // and expect to get a notification about ServiceWorker being accessed for
    // a navigation.
    ServiceWorkerAccessObserver observer(web_contents());
    base::RunLoop run_loop;
    EXPECT_CALL(observer,
                OnServiceWorkerAccessed(
                    testing::Matcher<NavigationHandle*>(NotNull()),
                    service_worker_scope,
                    AllowServiceWorkerResult::FromPolicy(false, false)))
        .WillOnce([&]() { run_loop.Quit(); });
    EXPECT_TRUE(NavigateToURL(
        web_contents(),
        embedded_test_server()->GetURL("/service_worker/empty.html")));
    run_loop.Run();
  }
}

namespace {

class ServiceWorkerAccessContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  ServiceWorkerAccessContentBrowserClient() = default;

  void SetJavascriptAllowed(bool allowed) { javascript_allowed_ = allowed; }

  void SetCookiesAllowed(bool allowed) { cookies_allowed_ = allowed; }

  AllowServiceWorkerResult AllowServiceWorker(
      const GURL& scope,
      const net::SiteForCookies& site_for_cookies,
      const std::optional<url::Origin>& top_frame_origin,
      const blink::StorageKey& storage_key,
      const GURL& script_url,
      BrowserContext* context) override {
    return AllowServiceWorkerResult::FromPolicy(!javascript_allowed_,
                                                !cookies_allowed_);
  }

 private:
  bool cookies_allowed_ = true;
  bool javascript_allowed_ = true;
};

}  // namespace

IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       OnServiceWorkerAccessed_ContentClientBlocked) {
  GURL service_worker_scope =
      embedded_test_server()->GetURL("/service_worker/");
  {
    // 1) Navigate to a page and register a ServiceWorker. Expect a notification
    // to be called when the service worker is accessed from a frame.
    ServiceWorkerAccessObserver observer(web_contents());
    base::RunLoop run_loop;
    EXPECT_CALL(
        observer,
        OnServiceWorkerAccessed(
            testing::Matcher<RenderFrameHost*>(NotNull()), service_worker_scope,
            AllowServiceWorkerResult::FromPolicy(false, false)))
        .WillOnce([&]() { run_loop.Quit(); });
    EXPECT_TRUE(NavigateToURL(
        web_contents(), embedded_test_server()->GetURL(
                            "/service_worker/create_service_worker.html")));
    EXPECT_EQ("DONE",
              EvalJs(top_frame_host(),
                     "register('fetch_event.js', '/service_worker/');"));
    run_loop.Run();
  }

  // 2) Set content client and disallow javascript.
  ServiceWorkerAccessContentBrowserClient content_browser_client;
  content_browser_client.SetJavascriptAllowed(false);

  {
    // 2) Navigate to a page in scope of the previously registered ServiceWorker
    // and expect to get a notification about ServiceWorker being accessed for
    // a navigation. Javascript should be blocked according to the policy.
    ServiceWorkerAccessObserver observer(web_contents());
    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnServiceWorkerAccessed(
                              testing::Matcher<NavigationHandle*>(NotNull()),
                              service_worker_scope,
                              AllowServiceWorkerResult::FromPolicy(
                                  /* javascript_blocked_by_policy=*/true,
                                  /* cookies_blocked_by_policy=*/false)))
        .WillOnce([&]() { run_loop.Quit(); });
    EXPECT_TRUE(NavigateToURL(
        web_contents(),
        embedded_test_server()->GetURL("/service_worker/empty.html")));
    run_loop.Run();
  }

  content_browser_client.SetJavascriptAllowed(true);
  content_browser_client.SetCookiesAllowed(false);

  {
    // 3) Navigate to a page in scope of the previously registered ServiceWorker
    // and expect to get a notification about ServiceWorker being accessed for
    // a navigation. Cookies should be blocked according to the policy.
    ServiceWorkerAccessObserver observer(web_contents());
    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnServiceWorkerAccessed(
                              testing::Matcher<NavigationHandle*>(NotNull()),
                              service_worker_scope,
                              AllowServiceWorkerResult::FromPolicy(
                                  /* javascript_blocked_by_policy=*/false,
                                  /* cookies_blocked_by_policy=*/true)))
        .WillOnce([&]() { run_loop.Quit(); });
    EXPECT_TRUE(NavigateToURL(
        web_contents(),
        embedded_test_server()->GetURL("/service_worker/empty.html")));
    run_loop.Run();
  }
}

namespace {

enum class ContextType {
  kNavigation,
  kFrame,
};

class CookieTracker : public WebContentsObserver {
 public:
  explicit CookieTracker(WebContentsImpl* web_contents)
      : WebContentsObserver(web_contents) {}

  struct CookieAccessDescription {
    CookieAccessDetails::Type type;

    ContextType context_type;
    GlobalRoutingID frame_id;
    int64_t navigation_id = -1;

    GURL url;
    GURL first_party_url;
    std::string cookie_name;
    std::string cookie_value;

    net::CookieAccessResult cookie_access_result;

    friend std::ostream& operator<<(std::ostream& o,
                                    const CookieAccessDescription& d) {
      o << (d.type == CookieAccessDetails::Type::kRead ? "read" : "change");
      o << " url=" << d.url;
      o << " first_party_url=" << d.first_party_url;
      o << " name=" << d.cookie_name;
      o << " value=" << d.cookie_value;
      switch (d.context_type) {
        case ContextType::kNavigation:
          o << " context=navigation(";
          o << "id=" << d.navigation_id;
          o << ")";
          break;
        case ContextType::kFrame:
          o << " context=frame(";
          o << "process_id=" << d.frame_id.child_id.value();
          o << " frame_id=" << d.frame_id.route_id;
          o << ")";
          break;
      }
      o << " access_result=";
      net::PrintTo(d.cookie_access_result, &o);
      return o;
    }

   public:
    bool operator==(const CookieAccessDescription&) const = default;
  };

  void OnCookiesAccessed(NavigationHandle* navigation,
                         const CookieAccessDetails& details) override {
    for (const auto& cookie_with_access_result :
         details.cookie_access_result_list) {
      cookie_accesses_.push_back({
          details.type,
          ContextType::kNavigation,
          {},
          navigation->GetNavigationId(),
          details.url,
          details.first_party_url,
          cookie_with_access_result.cookie.Name(),
          cookie_with_access_result.cookie.Value(),
          cookie_with_access_result.access_result,
      });
    }

    QuitIfReady();
  }

  void OnCookiesAccessed(RenderFrameHost* rfh,
                         const CookieAccessDetails& details) override {
    for (const auto& cookie_with_access_result :
         details.cookie_access_result_list) {
      cookie_accesses_.push_back({
          details.type,
          ContextType::kFrame,
          {rfh->GetProcess()->GetID(), rfh->GetRoutingID()},
          -1,
          details.url,
          details.first_party_url,
          cookie_with_access_result.cookie.Name(),
          cookie_with_access_result.cookie.Value(),
          cookie_with_access_result.access_result,
      });
    }

    QuitIfReady();
  }

  void WaitForCookies(size_t count) {
    waiting_for_cookies_count_ = count;

    base::RunLoop run_loop;
    quit_closure_ = run_loop.QuitClosure();
    QuitIfReady();
    run_loop.Run();
  }

  std::vector<CookieAccessDescription>& cookie_accesses() {
    return cookie_accesses_;
  }

  GlobalRoutingID frame_id(size_t index) {
    if (index < frame_ids_.size())
      return frame_ids_[index];
    // Return bogus values which will never be returned by the code we are
    // testing. This ensures that if we return this value, the subsequent
    // comparison will fail.
    return {content::ChildProcessId(-42), -42};
  }

  int64_t navigation_id(size_t index) {
    if (index < navigation_ids_.size())
      return navigation_ids_[index];
    // Return bogus values which will never be returned by the code we are
    // testing. This ensures that if we return this value, the subsequent
    // comparison will fail.
    return -42;
  }

  void DidFinishNavigation(NavigationHandle* navigation) override {
    navigation_ids_.push_back(navigation->GetNavigationId());
  }

  void RenderFrameCreated(RenderFrameHost* rfh) override {
    frame_ids_.emplace_back(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
  }

 private:
  void QuitIfReady() {
    if (quit_closure_.is_null())
      return;
    if (cookie_accesses_.size() < waiting_for_cookies_count_)
      return;
    std::move(quit_closure_).Run();
  }

  std::vector<CookieAccessDescription> cookie_accesses_;

  // List of observed navigation and frame ids to be used in testing.
  std::vector<GlobalRoutingID> frame_ids_;
  std::vector<int64_t> navigation_ids_;

  size_t waiting_for_cookies_count_ = 0;
  base::OnceClosure quit_closure_;
};

using CookieAccess = CookieTracker::CookieAccessDescription;

// Helper for checking CookieAccess.
MATCHER_P9(MatchesCookieAccess,
           type,
           context_type,
           frame_id,
           navigation_id,
           url,
           first_party_url,
           cookie_name,
           cookie_value,
           cookie_access_result,
           "") {
  const CookieAccess& cookie_access = arg;
  return testing::ExplainMatchResult(type, cookie_access.type,
                                     result_listener) &&
         testing::ExplainMatchResult(context_type, cookie_access.context_type,
                                     result_listener) &&
         testing::ExplainMatchResult(frame_id, cookie_access.frame_id,
                                     result_listener) &&
         testing::ExplainMatchResult(navigation_id, cookie_access.navigation_id,
                                     result_listener) &&
         testing::ExplainMatchResult(url, cookie_access.url, result_listener) &&
         testing::ExplainMatchResult(
             first_party_url, cookie_access.first_party_url, result_listener) &&
         testing::ExplainMatchResult(cookie_name, cookie_access.cookie_name,
                                     result_listener) &&
         testing::ExplainMatchResult(cookie_value, cookie_access.cookie_value,
                                     result_listener) &&
         testing::ExplainMatchResult(cookie_access_result,
                                     cookie_access.cookie_access_result,
                                     result_listener);
}

}  // namespace

// TODO(crbug.com/40211581): Flaky on Windows, Mac, and Android.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
#define MAYBE_CookieCallbacks_MainFrame DISABLED_CookieCallbacks_MainFrame
#else
#define MAYBE_CookieCallbacks_MainFrame CookieCallbacks_MainFrame
#endif
IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       MAYBE_CookieCallbacks_MainFrame) {
  CookieTracker cookie_tracker(web_contents());

  GURL first_party_url(embedded_test_server()->GetURL("a.com", "/"));
  GURL url1(
      embedded_test_server()->GetURL("a.com", "/cookies/set_cookie.html"));
  GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Navigate to |url1|. This navigation should set a cookie, which we should
  // be notified about.
  EXPECT_TRUE(NavigateToURL(web_contents(), url1));
  cookie_tracker.WaitForCookies(1);

  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(CookieAccess{
                  CookieAccessDetails::Type::kChange,
                  ContextType::kNavigation,
                  {},
                  cookie_tracker.navigation_id(0),
                  url1,
                  first_party_url,
                  "foo",
                  "bar",
                  net::CookieAccessResult(
                      net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                      net::CookieInclusionStatus(),
                      net::CookieAccessSemantics::NONLEGACY,
                      net::CookieScopeSemantics::UNKNOWN, false)}));
  cookie_tracker.cookie_accesses().clear();

  // 2) Navigate to |url2| on the same site. Given that we have set a cookie
  // before, this should sent a previously set cookie with the request and we
  // should be notified about this.
  EXPECT_TRUE(NavigateToURL(web_contents(), url2));
  cookie_tracker.WaitForCookies(1);

  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(CookieAccess{
                  CookieAccessDetails::Type::kRead,
                  ContextType::kNavigation,
                  {},
                  cookie_tracker.navigation_id(1),
                  url2,
                  first_party_url,
                  "foo",
                  "bar",
                  net::CookieAccessResult(
                      net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                      net::CookieInclusionStatus(),
                      net::CookieAccessSemantics::NONLEGACY,
                      net::CookieScopeSemantics::NONLEGACY, false)}));
  cookie_tracker.cookie_accesses().clear();
}

// TODO(crbug.com/40211581): Flaky on Mac and Android and Win.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#define MAYBE_CookieCallbacks_MainFrameRedirect \
  DISABLED_CookieCallbacks_MainFrameRedirect
#else
#define MAYBE_CookieCallbacks_MainFrameRedirect \
  CookieCallbacks_MainFrameRedirect
#endif
IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       MAYBE_CookieCallbacks_MainFrameRedirect) {
  CookieTracker cookie_tracker(web_contents());

  GURL first_party_url(embedded_test_server()->GetURL("a.com", "/"));
  GURL url1(embedded_test_server()->GetURL(
      "a.com", "/cookies/redirect_and_set_cookie.html"));
  GURL url1_after_redirect(
      embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url2(embedded_test_server()->GetURL("a.com", "/title2.html"));

  // 1) Navigate to |url1|. The initial URL redirects and sets a cookie (we
  // should be notified about this) and as the redirect points to the same site,
  // cookie should be sent for the second request as well (we should be notified
  // about this as well).
  EXPECT_TRUE(NavigateToURL(web_contents(), url1, url1_after_redirect));

  cookie_tracker.WaitForCookies(2);
  EXPECT_THAT(
      cookie_tracker.cookie_accesses(),
      testing::UnorderedElementsAre(
          CookieAccess{CookieAccessDetails::Type::kChange,
                       ContextType::kNavigation,
                       {},
                       cookie_tracker.navigation_id(0),
                       url1,
                       first_party_url,
                       "foo",
                       "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::UNKNOWN, false)},
          CookieAccess{CookieAccessDetails::Type::kRead,
                       ContextType::kNavigation,
                       {},
                       cookie_tracker.navigation_id(0),
                       url1_after_redirect,
                       first_party_url,
                       "foo",
                       "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::NONLEGACY, false)}));
  cookie_tracker.cookie_accesses().clear();

  // 2) Navigate to another url on the same site and expect a notification about
  // a read cookie.
  EXPECT_TRUE(NavigateToURL(web_contents(), url2));

  cookie_tracker.WaitForCookies(1);
  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(CookieAccess{
                  CookieAccessDetails::Type::kRead,
                  ContextType::kNavigation,
                  {},
                  cookie_tracker.navigation_id(1),
                  url2,
                  first_party_url,
                  "foo",
                  "bar",
                  net::CookieAccessResult(
                      net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                      net::CookieInclusionStatus(),
                      net::CookieAccessSemantics::NONLEGACY,
                      net::CookieScopeSemantics::NONLEGACY, false)}));
  cookie_tracker.cookie_accesses().clear();
}

// TODO(crbug.com/40211581): Flaky on Mac, Android and Windows.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
#define MAYBE_CookieCallbacks_Subframe DISABLED_CookieCallbacks_Subframe
#else
#define MAYBE_CookieCallbacks_Subframe CookieCallbacks_Subframe
#endif
IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       MAYBE_CookieCallbacks_Subframe) {
  CookieTracker cookie_tracker(web_contents());

  GURL first_party_url(embedded_test_server()->GetURL("a.com", "/"));
  GURL url1(embedded_test_server()->GetURL(
      "a.com", "/cookies/set_cookie_from_subframe.html"));
  GURL url1_subframe(
      embedded_test_server()->GetURL("a.com", "/cookies/set_cookie.html"));
  GURL url2(embedded_test_server()->GetURL("a.com",
                                           "/cookies/page_with_subframe.html"));
  GURL url2_subframe(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Load a page with a subframe. The main resource of the the subframe
  // triggers setting a cookie. We should get a cookie change for the
  // subresource and no cookie read for the main resource.
  EXPECT_TRUE(NavigateToURL(web_contents(), url1));

  cookie_tracker.WaitForCookies(1);
  // Navigations are: main frame (0), subframe (1).
  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(CookieAccess{
                  CookieAccessDetails::Type::kChange,
                  ContextType::kNavigation,
                  {},
                  cookie_tracker.navigation_id(1),
                  url1_subframe,
                  first_party_url,
                  "foo",
                  "bar",
                  net::CookieAccessResult(
                      net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                      net::CookieInclusionStatus(),
                      net::CookieAccessSemantics::NONLEGACY,
                      net::CookieScopeSemantics::UNKNOWN, false)}));
  cookie_tracker.cookie_accesses().clear();

  EXPECT_TRUE(NavigateToURL(web_contents(), url2));

  // 2) Load a page with a subframe. Both main frame and subframe should get a
  // cookie read.
  cookie_tracker.WaitForCookies(2);
  // Navigations are: main frame (2), subframe (3).
  EXPECT_THAT(
      cookie_tracker.cookie_accesses(),
      testing::ElementsAre(
          CookieAccess{CookieAccessDetails::Type::kRead,
                       ContextType::kNavigation,
                       {},
                       cookie_tracker.navigation_id(2),
                       url2,
                       first_party_url,
                       "foo",
                       "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::NONLEGACY, false)},
          CookieAccess{CookieAccessDetails::Type::kRead,
                       ContextType::kNavigation,
                       {},
                       cookie_tracker.navigation_id(3),
                       url2_subframe,
                       first_party_url,
                       "foo",
                       "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::NONLEGACY, false)}));
  cookie_tracker.cookie_accesses().clear();
}

// TODO(crbug.com/40211581): Flaky on Mac.
// TODO(crbug.com/40899619): Fix on android and enable it.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
#define MAYBE_CookieCallbacks_Subresource DISABLED_CookieCallbacks_Subresource
#else
#define MAYBE_CookieCallbacks_Subresource CookieCallbacks_Subresource
#endif
IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       MAYBE_CookieCallbacks_Subresource) {
  CookieTracker cookie_tracker(web_contents());

  GURL first_party_url(embedded_test_server()->GetURL("a.com", "/"));
  GURL url1(embedded_test_server()->GetURL(
      "a.com", "/cookies/set_cookie_from_subresource.html"));
  GURL url1_image(embedded_test_server()->GetURL(
      "a.com", "/cookies/image_with_set_cookie.jpg"));
  GURL url2(embedded_test_server()->GetURL(
      "a.com", "/cookies/page_with_subresource.html"));
  GURL url2_image(embedded_test_server()->GetURL(
      "a.com", "/cookies/image_without_set_cookie.jpg"));

  EXPECT_TRUE(NavigateToURL(web_contents(), url1));

  // 1) Load a page with a subresource (image), which sets a cookie when
  // fetched.
  cookie_tracker.WaitForCookies(1);
  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(CookieAccess{
                  CookieAccessDetails::Type::kChange, ContextType::kFrame,
                  cookie_tracker.frame_id(0), -1, url1_image, first_party_url,
                  "foo", "bar",
                  net::CookieAccessResult(
                      net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                      net::CookieInclusionStatus(),
                      net::CookieAccessSemantics::NONLEGACY,
                      net::CookieScopeSemantics::UNKNOWN, false)}));
  cookie_tracker.cookie_accesses().clear();

  // 2) Load a page with subresource. Both the page and the resource should get
  // a cookie.
  EXPECT_TRUE(NavigateToURL(web_contents(), url2));
  // If the RFH changes after navigation, the cookie will be attributed to a
  // different frame.
  int frame_id_index =
      CanSameSiteMainFrameNavigationsChangeRenderFrameHosts() ? 1 : 0;
  cookie_tracker.WaitForCookies(2);
  EXPECT_THAT(
      cookie_tracker.cookie_accesses(),
      testing::ElementsAre(
          CookieAccess{CookieAccessDetails::Type::kRead,
                       ContextType::kNavigation,
                       {},
                       cookie_tracker.navigation_id(1),
                       url2,
                       first_party_url,
                       "foo",
                       "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::NONLEGACY, false)},
          CookieAccess{CookieAccessDetails::Type::kRead, ContextType::kFrame,
                       cookie_tracker.frame_id(frame_id_index), -1, url2_image,
                       first_party_url, "foo", "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::NONLEGACY, false)}));
  cookie_tracker.cookie_accesses().clear();
}

IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       CookieCallbacks_DocumentCookie) {
  CookieTracker cookie_tracker(web_contents());

  GURL first_party_url(embedded_test_server()->GetURL("a.com", "/"));
  GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));

  EXPECT_TRUE(NavigateToURL(web_contents(), url1));
  EXPECT_TRUE(ExecJs(web_contents(), "document.cookie='foo=bar'"));

  EXPECT_EQ("foo=bar", EvalJs(web_contents(), "document.cookie"));

  cookie_tracker.WaitForCookies(2);
  // TODO(crbug.com/380864710): Move this check before reading cookies once
  // GetCookiesOnSet is fully shipped. When the feature is enabled, a Set also
  // does a Set, and the Get is cached, thus not producing an access
  // notification.
  EXPECT_THAT(
      cookie_tracker.cookie_accesses(),
      testing::ElementsAre(
          CookieAccess{CookieAccessDetails::Type::kChange, ContextType::kFrame,
                       cookie_tracker.frame_id(0), -1, url1, first_party_url,
                       "foo", "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::UNKNOWN, false)},
          CookieAccess{CookieAccessDetails::Type::kRead, ContextType::kFrame,
                       cookie_tracker.frame_id(0), -1, url1, first_party_url,
                       "foo", "bar",
                       net::CookieAccessResult(
                           net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                           net::CookieInclusionStatus(),
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::NONLEGACY, false)}));
}

class WebContentsObserverBrowserTestWithTPCD
    : public WebContentsObserverBrowserTest {
 public:
  WebContentsObserverBrowserTestWithTPCD() {
    feature_list_.InitAndEnableFeature(
        net::features::kForceThirdPartyCookieBlocking);
  }

 private:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_https_test_server().SetSSLConfig(
        net::EmbeddedTestServer::CERT_TEST_NAMES);
    net::test_server::RegisterDefaultHandlers(&embedded_https_test_server());
    ASSERT_TRUE(embedded_https_test_server().Start());
  }

  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTestWithTPCD,
                       CookieCallbacks_BlockedAccessStatusForwarded) {
  ASSERT_TRUE(base::FeatureList::IsEnabled(
      net::features::kForceThirdPartyCookieBlocking));
  CookieTracker cookie_tracker(web_contents());

  // 1) Set a cookie on |url_a|.
  GURL url_a(embedded_https_test_server().GetURL("a.test", "/"));
  GURL url_a_check_cookie(
      embedded_https_test_server().GetURL("a.test", "/title1.html"));
  ASSERT_TRUE(content::SetCookie(web_contents()->GetBrowserContext(), url_a,
                                 "foo=bar;SameSite=None;Secure"));
  EXPECT_TRUE(NavigateToURL(web_contents(), url_a_check_cookie));
  cookie_tracker.WaitForCookies(1);
  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(CookieAccess{
                  CookieAccessDetails::Type::kRead,
                  ContextType::kNavigation,
                  {},
                  cookie_tracker.navigation_id(0),
                  url_a_check_cookie,
                  url_a,
                  "foo",
                  "bar",
                  net::CookieAccessResult(
                      net::CookieEffectiveSameSite::NO_RESTRICTION,
                      net::CookieInclusionStatus(),
                      net::CookieAccessSemantics::NONLEGACY,
                      net::CookieScopeSemantics::NONLEGACY, true)}));
  cookie_tracker.cookie_accesses().clear();

  // 2) Navigate to |url_b_cross_site|. This page should load b.test(a.test)
  // frames, which should block the cookie from a.test and we should be notified
  // about the cookie exclusion.
  GURL url_b(embedded_https_test_server().GetURL("b.test", "/"));
  GURL url_b_cross_site(embedded_https_test_server().GetURL(
      "b.test", "/cross_site_iframe_factory.html?b.test(a.test)"));
  EXPECT_TRUE(NavigateToURL(web_contents(), url_b_cross_site));
  cookie_tracker.WaitForCookies(2);
  EXPECT_THAT(cookie_tracker.cookie_accesses(),
              testing::ElementsAre(
                  MatchesCookieAccess(
                      CookieAccessDetails::Type::kRead, testing::_, testing::_,
                      testing::_, testing::_, testing::_, "foo", "bar",
                      net::CookieAccessResult(
                          net::CookieEffectiveSameSite::NO_RESTRICTION,
                          net::CookieInclusionStatus::MakeFromReasonsForTesting(
                              /*exclusions=*/{
                                  net::CookieInclusionStatus::ExclusionReason::
                                      EXCLUDE_THIRD_PARTY_PHASEOUT}),
                          net::CookieAccessSemantics::NONLEGACY,
                          net::CookieScopeSemantics::NONLEGACY, true)),
                  MatchesCookieAccess(
                      CookieAccessDetails::Type::kRead, testing::_, testing::_,
                      testing::_, testing::_, testing::_, "foo", "bar",
                      net::CookieAccessResult(
                          net::CookieEffectiveSameSite::NO_RESTRICTION,
                          net::CookieInclusionStatus::MakeFromReasonsForTesting(
                              /*exclusions=*/{
                                  net::CookieInclusionStatus::ExclusionReason::
                                      EXCLUDE_THIRD_PARTY_PHASEOUT}),
                          net::CookieAccessSemantics::NONLEGACY,
                          net::CookieScopeSemantics::NONLEGACY, true))));
  cookie_tracker.cookie_accesses().clear();
}
namespace {

class FocusedNodeObserver : public WebContentsObserver {
 public:
  explicit FocusedNodeObserver(WebContentsImpl* web_contents)
      : WebContentsObserver(web_contents) {}

  blink::mojom::FocusType last_focus_type() const {
    return last_details_.focus_type;
  }
  blink::DOMNodeIdType last_dom_node_id() const {
    return last_details_.global_dom_node_id.target_element_dom_id;
  }

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

  // WebContentsObserver:
  void OnFocusChangedInPage(const FocusedNodeDetails& details) override {
    last_details_ = details;
    run_loop_.Quit();
  }

 private:
  base::RunLoop run_loop_;
  FocusedNodeDetails last_details_;
};

// Tests that the focus type is reported correctly in FocusedNodeDetails when
// WebContentsObserver::OnFocusChangedInPage() is called.
IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
                       OnFocusChangedInPageFocusType) {
  FocusedNodeObserver observer(web_contents());
  GURL url(embedded_test_server()->GetURL("/form_that_posts_cross_site.html"));

  EXPECT_TRUE(NavigateToURL(web_contents(), url));
  SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents());
  SimulateMouseClickOrTapElementWithId(web_contents(), "text");
  observer.WaitForFocusChangedInPage();
  EXPECT_EQ(blink::mojom::FocusType::kMouse, observer.last_focus_type());
}

class WebContentsObserverBrowserTestNodeIdEnabled
    : public WebContentsObserverBrowserTest {
 public:
  WebContentsObserverBrowserTestNodeIdEnabled() = default;

 private:
  base::test::ScopedFeatureList feature_list_{
      blink::features::kPopulateDOMNodeIdInFocusedNodeDetails};
};

IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTestNodeIdEnabled,
                       OnFocusChangedInPageNodeId) {
  FocusedNodeObserver observer(web_contents());
  GURL url(embedded_test_server()->GetURL("/form_that_posts_cross_site.html"));

  EXPECT_TRUE(NavigateToURL(web_contents(), url));
  SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents());
  std::optional<int> expected_node_id =
      GetDOMNodeId(*web_contents()->GetPrimaryMainFrame(), "#text");
  ASSERT_TRUE(expected_node_id.has_value());

  SimulateMouseClickOrTapElementWithId(web_contents(), "text");
  observer.WaitForFocusChangedInPage();

  EXPECT_EQ(expected_node_id.value(), observer.last_dom_node_id().value());
}

}  // namespace

namespace {

class ColorSchemeObserver : public WebContentsObserver {
 public:
  explicit ColorSchemeObserver(WebContentsImpl* web_contents)
      : WebContentsObserver(web_contents) {}

  MOCK_METHOD1(InferredColorSchemeUpdated,
               void(std::optional<blink::mojom::PreferredColorScheme>));
};

class WebContentsObserverColorSchemeBrowserTest
    : public WebContentsObserverBrowserTest,
      public testing::WithParamInterface<blink::mojom::PreferredColorScheme> {
 public:
  WebContentsObserverColorSchemeBrowserTest() = default;

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    WebContentsObserverBrowserTest::SetUpCommandLine(command_line);
    // ShellContentBrowserClient::OverrideWebPreferences() overrides the
    // prefers-color-scheme according to switches::kForceDarkMode command line.
    if (GetParam() == blink::mojom::PreferredColorScheme::kDark)
      command_line->AppendSwitch(switches::kForceDarkMode);
  }
};

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    WebContentsObserverColorSchemeBrowserTest,
    ::testing::Values(blink::mojom::PreferredColorScheme::kDark,
                      blink::mojom::PreferredColorScheme::kLight));

IN_PROC_BROWSER_TEST_P(WebContentsObserverColorSchemeBrowserTest,
                       ColorSchemeInferred) {
  ColorSchemeObserver observer(web_contents());

  {
    base::RunLoop run_loop;
    EXPECT_CALL(observer,
                InferredColorSchemeUpdated(
                    std::optional<blink::mojom::PreferredColorScheme>()));
    EXPECT_CALL(
        observer,
        InferredColorSchemeUpdated(
            std::optional<blink::mojom::PreferredColorScheme>(GetParam())))
        .WillOnce([&]() { run_loop.Quit(); });
    GURL url(embedded_test_server()->GetURL("/color-scheme.html"));
    EXPECT_TRUE(NavigateToURL(web_contents(), url));
    run_loop.Run();
  }

  // Navigate to another URL to verify the callback invoked for each navigation
  // even if the color scheme isn't changed.
  {
    base::RunLoop run_loop;
    EXPECT_CALL(observer,
                InferredColorSchemeUpdated(
                    std::optional<blink::mojom::PreferredColorScheme>()));
    EXPECT_CALL(
        observer,
        InferredColorSchemeUpdated(
            std::optional<blink::mojom::PreferredColorScheme>(GetParam())))
        .WillOnce([&]() { run_loop.Quit(); });
    GURL url2(embedded_test_server()->GetURL("/color-scheme-2.html"));
    EXPECT_TRUE(NavigateToURL(web_contents(), url2));
    run_loop.Run();
  }
}

}  // namespace

namespace {

// Waits for a specific type of cookie access on a particular URL, and saves its
// CookieAccessDetails for later introspection.
class CookieObserver : public WebContentsObserver {
 public:
  explicit CookieObserver(WebContents* web_contents,
                          GURL url,
                          CookieAccessDetails::Type type)
      : WebContentsObserver(web_contents),
        monitored_url_(std::move(url)),
        monitored_type_(type) {}

  [[nodiscard]] bool Wait() { return future_.Wait(); }

  const CookieAccessDetails& details() { return future_.Get(); }

  // WebContentsObserver overrides:
  void OnCookiesAccessed(RenderFrameHost* render_frame_host,
                         const CookieAccessDetails& details) override {
    if (details.url != monitored_url_ || details.type != monitored_type_) {
      return;
    }

    future_.SetValue(details);
  }

  void OnCookiesAccessed(NavigationHandle* navigation_handle,
                         const CookieAccessDetails& details) override {
    if (details.url != monitored_url_ || details.type != monitored_type_) {
      return;
    }

    future_.SetValue(details);
  }

 private:
  GURL monitored_url_;
  CookieAccessDetails::Type monitored_type_;
  base::test::TestFuture<CookieAccessDetails> future_;
};

}  // namespace

// Tests for the CookieAccessDetails::source reported by
// WebContentsObserver::OnCookiesAccessed(). Some tests use QuicSimpleTestServer
// because Early Hints are only plumbed over HTTP/2 or HTTP/3 (QUIC).
class CookieSourceBrowserTest : public ContentBrowserTest {
 public:
  CookieSourceBrowserTest() {
    feature_list_.InitWithFeatures(
        std::vector<base::test::FeatureRef>{
            net::features::kSplitCacheByNetworkIsolationKey},
        std::vector<base::test::FeatureRef>{
            net::features::kMigrateSessionsOnNetworkChangeV2});
  }

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

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_https_test_server().SetSSLConfig(
        net::EmbeddedTestServer::CERT_TEST_NAMES);
    ASSERT_TRUE(embedded_https_test_server().Start());

    // Configure the certificate for the QUIC server.
    auto test_cert =
        net::ImportCertFromFile(net::GetTestCertsDirectory(), "quic-chain.pem");
    net::CertVerifyResult verify_result;
    verify_result.verified_cert = test_cert;
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCert(
        test_cert, verify_result, net::OK);
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ASSERT_TRUE(net::QuicSimpleTestServer::Start());
    command_line->AppendSwitchASCII(
        switches::kOriginToForceQuicOn,
        net::QuicSimpleTestServer::GetHostPort().ToString());
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void TearDown() override {
    // Needed by net::QuicSimpleTestServer::Shutdown() below.
    base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
    net::QuicSimpleTestServer::Shutdown();
  }

  void SetUpInProcessBrowserTestFixture() override {
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

 private:
  base::test::ScopedFeatureList feature_list_;

  ContentMockCertVerifier mock_cert_verifier_;
};

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, NavigationCookie) {
  GURL url =
      embedded_https_test_server().GetURL("a.test", "/set-cookie?foo=bar");

  CookieObserver observer(web_contents(), url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(NavigateToURL(web_contents(), url));
  ASSERT_TRUE(observer.Wait());
  EXPECT_EQ(observer.details().url, url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, IframeNavigationCookie) {
  GURL main_url = embedded_https_test_server().GetURL(
      "a.test", "/page_with_blank_iframe.html");
  GURL iframe_url = embedded_https_test_server().GetURL(
      "b.test", "/set-cookie?foo=bar;Secure;SameSite=None");

  ASSERT_TRUE(NavigateToURL(web_contents(), main_url));
  CookieObserver observer(web_contents(), iframe_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(NavigateIframeToURL(web_contents(), "test_iframe", iframe_url));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, iframe_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, NestedIframeNavigationCookie) {
  GURL main_url = embedded_https_test_server().GetURL(
      "a.test", "/page_with_blank_iframe.html");
  GURL outer_iframe_url = embedded_https_test_server().GetURL(
      "b.test", "/page_with_blank_iframe.html");
  GURL inner_iframe_url = embedded_https_test_server().GetURL(
      "c.test", "/set-cookie?foo=bar;Secure;SameSite=None");

  ASSERT_TRUE(NavigateToURL(web_contents(), main_url));
  ASSERT_TRUE(NavigateToURLFromRenderer(ChildFrameAt(web_contents(), 0),
                                        outer_iframe_url));
  CookieObserver observer(web_contents(), inner_iframe_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(NavigateToURLFromRenderer(
      ChildFrameAt(ChildFrameAt(web_contents(), 0), 0), inner_iframe_url));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, inner_iframe_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, JavaScriptCookie) {
  GURL url = embedded_https_test_server().GetURL("a.test", "/empty.html");

  ASSERT_TRUE(NavigateToURL(web_contents(), url));
  CookieObserver observer(web_contents(), url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(web_contents(), "document.cookie = 'foo=bar';"));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, IframeJavaScriptCookie) {
  GURL main_url = embedded_https_test_server().GetURL(
      "a.test", "/page_with_blank_iframe.html");
  GURL iframe_url =
      embedded_https_test_server().GetURL("b.test", "/empty.html");

  ASSERT_TRUE(NavigateToURL(web_contents(), main_url));
  ASSERT_TRUE(NavigateIframeToURL(web_contents(), "test_iframe", iframe_url));
  CookieObserver observer(web_contents(), iframe_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(ChildFrameAt(web_contents(), 0),
                     "document.cookie = 'foo=bar;Secure;SameSite=None';"));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, iframe_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, SubresourceRequestCookie) {
  GURL doc_url = embedded_https_test_server().GetURL("a.test", "/empty.html");
  GURL img_url = embedded_https_test_server().GetURL(
      "b.test", "/set-cookie?foo=bar;Secure;SameSite=None");

  ASSERT_TRUE(NavigateToURL(web_contents(), doc_url));
  CookieObserver observer(web_contents(), img_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
      const img = document.createElement('img');
      img.src = $1;
      document.body.append(img);
    )",
                                               img_url)));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, img_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, PrefetchCookie) {
  const GURL main_url =
      embedded_https_test_server().GetURL("a.test", "/empty.html");
  const GURL prefetch_url = embedded_https_test_server().GetURL(
      "b.test", "/set-cookie?foo=bar;Secure;SameSite=None");

  ASSERT_TRUE(NavigateToURL(web_contents(), main_url));

  CookieObserver observer(web_contents(), prefetch_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
      const link = document.createElement('link');
      link.rel = 'prefetch';
      link.as = 'document';
      link.href = $1;
      document.body.append(link);
    )",
                                               prefetch_url)));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, prefetch_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, CookieStoreApi) {
  GURL url = embedded_https_test_server().GetURL("a.test", "/empty.html");

  ASSERT_TRUE(NavigateToURL(web_contents(), url));
  CookieObserver observer(web_contents(), url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(web_contents(), "cookieStore.set('foo', 'bar')"));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, Fetch) {
  GURL main_url = embedded_https_test_server().GetURL("a.test", "/empty.html");
  GURL fetch_url =
      embedded_https_test_server().GetURL("a.test", "/set-cookie?foo=bar");

  ASSERT_TRUE(NavigateToURL(web_contents(), main_url));
  CookieObserver observer(web_contents(), fetch_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(web_contents(), JsReplace("fetch($1);", fetch_url)));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, fetch_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, XMLHttpRequest) {
  GURL main_url = embedded_https_test_server().GetURL("a.test", "/empty.html");
  GURL xhr_url =
      embedded_https_test_server().GetURL("a.test", "/set-cookie?foo=bar");

  ASSERT_TRUE(NavigateToURL(web_contents(), main_url));
  CookieObserver observer(web_contents(), xhr_url,
                          CookieAccessDetails::Type::kChange);
  ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
        const xhr = new XMLHttpRequest();
        xhr.open("GET", $1, true);
        xhr.send(null);
      )",
                                               xhr_url)));
  ASSERT_TRUE(observer.Wait());

  EXPECT_EQ(observer.details().url, xhr_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

IN_PROC_BROWSER_TEST_F(CookieSourceBrowserTest, EarlyHints) {
  // Register a response for /early.css
  {
    quiche::HttpHeaderBlock headers;
    headers[":path"] = "/early.css";
    headers[":status"] = base::ToString(net::HTTP_OK);
    headers["content-type"] = "text/css";
    headers["cache-control"] = "max-age=3600";
    headers["set-cookie"] = "foo=bar;";
    net::QuicSimpleTestServer::AddResponse("/early.css", std::move(headers),
                                           "/* empty */");
  }

  // Register a response for /early_hints.html
  {
    quiche::HttpHeaderBlock headers;
    headers[":path"] = "/early_hints.html";
    headers[":status"] = base::ToString(net::HTTP_OK);
    headers["content-type"] = "text/html";

    // Early Hint header.
    quiche::HttpHeaderBlock early_hint_header;
    early_hint_header["link"] = "</early.css>; rel=preload; as=style";
    std::vector<quiche::HttpHeaderBlock> early_hints;
    early_hints.push_back(std::move(early_hint_header));

    net::QuicSimpleTestServer::AddResponseWithEarlyHints(
        /*path=*/"/early_hints.html",
        /*response_headers=*/std::move(headers),
        /*response_body=*/
        R"(<link rel="stylesheet" href="/early.css" />)",
        /*early_hints=*/std::move(early_hints));
  }
  const GURL page_url =
      net::QuicSimpleTestServer::GetFileURL("/early_hints.html");
  const GURL early_url = net::QuicSimpleTestServer::GetFileURL("/early.css");

  CookieObserver observer(web_contents(), early_url,
                          CookieAccessDetails::Type::kChange);
  EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
  ASSERT_TRUE(observer.Wait());
  EXPECT_EQ(observer.details().url, early_url);
  EXPECT_EQ(observer.details().source,
            CookieAccessDetails::Source::kNonNavigation);
}

}  // namespace content
