// 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 <initializer_list>
#include <memory>
#include <ranges>
#include <string_view>

#include "base/cfi_buildflags.h"
#include "base/containers/map_util.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/path_service.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/optional_util.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/net/storage_test_utils.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/storage_access_api/storage_access_grant_permission_context.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "chrome/browser/ui/omnibox/omnibox_next_features.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/webid/federated_identity_permission_context.h"
#include "chrome/browser/webid/federated_identity_permission_context_factory.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/features.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/request_type.h"
#include "components/permissions/test/mock_permission_prompt_factory.h"
#include "components/permissions/test/permission_request_observer.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_paths.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_devtools_protocol_client.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/base/features.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/cookies/cookie_partition_key_collection.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/install_default_websocket_handlers.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
#include "ui/base/window_open_disposition.h"
#include "url/origin.h"

using content::BrowserThread;
using testing::_;
using testing::AllOf;
using testing::Conditional;
using testing::Contains;
using testing::Each;
using testing::Gt;
using testing::IsEmpty;
using testing::IsSupersetOf;
using testing::Key;
using testing::Not;
using testing::Pair;
using testing::StartsWith;
using testing::UnorderedElementsAre;

using PathAndHeaderMapMatchers = std::initializer_list<testing::Matcher<
    std::pair<std::string, net::test_server::HttpRequest::HeaderMap>>>;
using HeaderMapMatchers = std::initializer_list<
    testing::Matcher<std::pair<std::string, std::string>>>;

namespace {

constexpr std::string_view kHostA = "a.test";
constexpr std::string_view kOriginA = "https://a.test";
constexpr std::string_view kOriginB = "https://b.test";
constexpr std::string_view kHostASubdomain = "subdomain.a.test";
constexpr std::string_view kHostB = "b.test";
constexpr std::string_view kHostBSubdomain = "subdomain.b.test";
constexpr std::string_view kHostBSubdomain2 = "subdomain2.b.test";
constexpr std::string_view kHostC = "c.test";
constexpr std::string_view kHostD = "d.test";

constexpr std::string_view kUseCounterHistogram = "Blink.UseCounter.Features";
constexpr std::string_view kGrantIsImplicitHistogram =
    "API.StorageAccess.GrantIsImplicit";

// Path for URL of custom response
const char* kEchoCookiesWithCorsPath = "/echocookieswithcors";
constexpr char kRetryPath[] = "/retry-with-storage-access";

constexpr char kQueryStorageAccessPermission[] =
    "navigator.permissions.query({name: 'storage-access'}).then("
    "  (permission) => permission.state);";

constexpr char kHeaderNotProvidedSentinel[] = "HEADER_NOT_PROVIDED";

constexpr char kSecFetchStorageAccess[] = "Sec-Fetch-Storage-Access";

const ContentSettingPatternSource kExpectedSettingDefault(
    ContentSettingsPattern::Wildcard(),
    ContentSettingsPattern::Wildcard(),
    content_settings::ContentSettingToValue(CONTENT_SETTING_ASK),
    content_settings::ProviderType::kDefaultProvider,
    /*incognito=*/false);

enum class TestType { kFrame, kWorker };

// Helpers to express expected
std::pair<std::string, std::string> CookieBundle(std::string_view cookies) {
  DCHECK_NE(cookies, "None");
  DCHECK_NE(cookies, "");
  return {std::string(cookies), std::string(cookies)};
}

std::tuple<std::string, std::string, std::string> CookieBundleWithContent(
    std::string_view cookies) {
  DCHECK_NE(cookies, "None");
  DCHECK_NE(cookies, "");
  return {std::string(cookies), std::string(cookies), std::string(cookies)};
}

constexpr std::pair<const char*, const char*> kNoCookies =
    std::make_pair("",     // cookie string via `document.cookie`
                   "None"  // cookie string via `echoheader?cookie`
    );

constexpr std::tuple<const char*, const char*, const char*>
    kNoCookiesWithContent =
        std::make_tuple("",      // cookie string via `document.cookie`
                        "None",  // cookie string via `echoheader?cookie`
                        "None"   // cookie string via frame content (also via
                                 // `echoheader?cookie`)
        );

// Executes the inner_matcher on the string arg after it's transformed into a
// vector of pairs of strings. This assumes that the argument is a string, whose
// value is a '\n' delimited list of name/value pairs (delimited by ':').
MATCHER_P(HeadersAre, inner_matcher, "") {
  return testing::ExplainMatchResult(net::WhenKVSplit('\n', ':', inner_matcher),
                                     arg, result_listener);
}

void SetCORSHeaders(const net::test_server::HttpRequest& request,
                    net::test_server::BasicHttpResponse& response) {
  if (auto it = request.headers.find(net::HttpRequestHeaders::kOrigin);
      it != request.headers.end()) {
    response.AddCustomHeader("Access-Control-Allow-Origin", it->second);
    response.AddCustomHeader("Vary", "origin");
    response.AddCustomHeader("Access-Control-Allow-Credentials", "true");
  }
}

// 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 = "None";
  // 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");
  SetCORSHeaders(request, *http_response);
  http_response->set_content(content);

  return http_response;
}

std::unique_ptr<net::test_server::HttpResponse> HandleRetryRequest(
    int& fetch_count,
    std::string_view allowed_origin,
    const net::test_server::HttpRequest& request) {
  if (request.relative_url != kRetryPath) {
    return nullptr;
  }

  fetch_count++;
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_OK);
  http_response->set_content_type("text/plain");
  http_response->AddCustomHeader(
      "Activate-Storage-Access",
      base::StrCat({"retry; allowed-origin=", allowed_origin}));
  SetCORSHeaders(request, *http_response);

  std::optional<std::string> storage_access_header = base::OptionalFromPtr(
      base::FindOrNull(request.headers, kSecFetchStorageAccess));
  if (storage_access_header == "inactive") {
    std::optional<std::string> origin_header = base::OptionalFromPtr(
        base::FindOrNull(request.headers, net::HttpRequestHeaders::kOrigin));
    CHECK(origin_header);
    if (allowed_origin != "*" && origin_header) {
      std::string trimmed_allowed_origin;
      base::TrimString(allowed_origin, "\"", &trimmed_allowed_origin);
      EXPECT_EQ(trimmed_allowed_origin, origin_header);
    }
  }

  auto serialize_header_name_and_value =
      [&](std::string_view header_name) -> std::string {
    std::string value =
        base::OptionalFromPtr(base::FindOrNull(request.headers, header_name))
            .value_or(kHeaderNotProvidedSentinel);
    return base::JoinString({header_name, value}, ":");
  };

  http_response->set_content(base::JoinString(
      {
          serialize_header_name_and_value(net::HttpRequestHeaders::kCookie),
          serialize_header_name_and_value(kSecFetchStorageAccess),
      },
      "\n"));

  return http_response;
}

std::string QueryPermission(content::RenderFrameHost* render_frame_host) {
  return content::EvalJs(render_frame_host, kQueryStorageAccessPermission)
      .ExtractString();
}

bool ThirdPartyPartitionedStorageAllowedByDefault() {
  return base::FeatureList::IsEnabled(
             net::features::kThirdPartyPartitionedStorageAllowedByDefault) &&
         base::FeatureList::IsEnabled(
             net::features::kThirdPartyStoragePartitioning);
}

std::string CookieAttributes(std::string_view domain) {
  return base::StrCat({";SameSite=None;Secure;Domain=", domain, ";Path=/"});
}

std::vector<base::test::FeatureRefAndParams> GetEnabledFeaturesForStorage(
    bool is_storage_partitioned) {
  std::vector<base::test::FeatureRefAndParams> enabled;
  if (is_storage_partitioned) {
    enabled.push_back({net::features::kThirdPartyStoragePartitioning, {}});
  }
  return enabled;
}

std::vector<base::test::FeatureRef> GetDisabledFeaturesForStorage(
    bool is_storage_partitioned) {
  std::vector<base::test::FeatureRef> disabled;
  if (!is_storage_partitioned) {
    disabled.push_back(net::features::kThirdPartyStoragePartitioning);
  }
  return disabled;
}

class StorageAccessAPIBaseBrowserTest : public policy::PolicyTest {
 protected:
  StorageAccessAPIBaseBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  void SetUp() override {
    features_.InitWithFeaturesAndParameters(GetEnabledFeatures(),
                                            GetDisabledFeatures());
    InProcessBrowserTest::SetUp();
  }

  virtual std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() {
    return {};
  }

  virtual std::vector<base::test::FeatureRef> GetDisabledFeatures() {
    return {};
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    base::FilePath path;
    base::PathService::Get(content::DIR_TEST_DATA, &path);
    https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server_.ServeFilesFromDirectory(path);
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.RegisterRequestHandler(
        base::BindRepeating(&HandleEchoCookiesWithCorsRequest));
    https_server_.RegisterRequestHandler(base::BindLambdaForTesting(
        [&](const net::test_server::HttpRequest& request) {
          return HandleRetryRequest(retry_path_fetch_count_,
                                    retry_allowed_origin_, request);
        }));
    https_server_.RegisterRequestMonitor(base::BindLambdaForTesting(
        [&](const net::test_server::HttpRequest& request) {
          base::AutoLock lock(lock_);
          observed_request_headers_.emplace_back(request.relative_url,
                                                 request.headers);
        }));
    net::test_server::InstallDefaultWebSocketHandlers(&https_server_);
    ASSERT_TRUE(https_server_.Start());

    // All the sites used during these tests should have a cookie.
    SetCrossSiteCookieOnDomain(kHostA);
    SetCrossSiteCookieOnDomain(kHostB);
    SetCrossSiteCookieOnDomain(kHostC);
    SetCrossSiteCookieOnDomain(kHostD);

    prompt_factory_ = MakePromptFactory(browser());

    // Don't respond to the prompt at all, by default. This forces any test that
    // assumes a particular response to the prompt to explicitly declare that
    // assumption by setting up the auto-response themselves.
    prompt_factory_->set_response_type(
        permissions::PermissionRequestManager::NONE);

    // Most of these tests invoke document.requestStorageAccess from a kHostB
    // iframe. We pre-seed that site with user interaction, to avoid being
    // blocked by the top-level user interaction heuristic.
    EnsureUserInteractionOn(kHostB);
  }

  std::unique_ptr<permissions::MockPermissionPromptFactory> MakePromptFactory(
      BrowserWindowInterface* browser_ptr) {
    CHECK(browser_ptr);
    return std::make_unique<permissions::MockPermissionPromptFactory>(
        permissions::PermissionRequestManager::FromWebContents(
            browser_ptr->GetTabStripModel()->GetActiveWebContents()));
  }

  void TearDownOnMainThread() override { prompt_factory_.reset(); }

  void SetCrossSiteCookieOnDomain(std::string_view domain) {
    GURL domain_url = GetURL(domain);
    std::string cookie = base::StrCat({"cross-site=", domain});
    ASSERT_TRUE(
        content::SetCookie(browser()->GetProfile(), domain_url,
                           base::StrCat({cookie, CookieAttributes(domain)})));
    ASSERT_THAT(content::GetCookies(browser()->GetProfile(), domain_url),
                testing::HasSubstr(cookie));
  }

  void SetPartitionedCookieInContext(std::string_view top_level_host,
                                     std::string_view embedded_host) {
    GURL host_url = GetURL(embedded_host);
    std::string cookie =
        base::StrCat({"cross-site=", embedded_host, "(partitioned)"});
    net::CookiePartitionKey partition_key =
        net::CookiePartitionKey::FromURLForTesting(GetURL(top_level_host));
    ASSERT_TRUE(content::SetCookie(
        browser()->GetProfile(), host_url,
        base::StrCat({cookie, CookieAttributes(/*domain=*/embedded_host),
                      ";Partitioned"}),
        net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
        &partition_key));
    ASSERT_THAT(content::GetCookies(
                    browser()->GetProfile(), host_url,
                    net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
                    net::CookiePartitionKeyCollection(partition_key)),
                testing::HasSubstr(cookie));
  }

  void BlockAllCookiesOnHost(std::string_view host) {
    CookieSettingsFactory::GetForProfile(browser()->GetProfile())
        ->SetCookieSetting(GetURL(host), ContentSetting::CONTENT_SETTING_BLOCK);
  }

  GURL GetURL(std::string_view host, std::string_view path = "/") {
    return https_server_.GetURL(host, path);
  }

  // TODO(crbug.com/381856829): Update SetBlockThirdPartyCookies to use sync
  // interface once implemented.
  void SetBlockThirdPartyCookies(bool value) {
    browser()->GetProfile()->GetPrefs()->SetInteger(
        prefs::kCookieControlsMode,
        static_cast<int>(
            value ? content_settings::CookieControlsMode::kBlockThirdParty
                  : content_settings::CookieControlsMode::kOff));
  }

  void NavigateToPage(std::string_view host, std::string_view path) {
    GURL main_url(https_server_.GetURL(host, path));
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  }

  void NavigateToPageWithFrame(std::string_view host,
                               BrowserWindowInterface* browser_ptr = nullptr,
                               bool credentialless = false) {
    GURL main_url(https_server_.GetURL(
        host, credentialless ? "/iframe_credentialless.html" : "/iframe.html"));
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser_ptr ? browser_ptr : browser(), main_url));
  }

  void NavigateToPageWithFrameBlockingStorageAccessPermissionsPolicy(
      std::string_view host) {
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(),
        https_server_.GetURL(host, base::StrCat({
                                       "/cross_site_iframe_factory.html?",
                                       host,
                                       "(",
                                       host,
                                       "{disallow-storage-access})",
                                   }))));
  }

  void NavigateToNewTabWithFrame(std::string_view host) {
    GURL main_url(https_server_.GetURL(host, "/iframe.html"));
    ui_test_utils::NavigateToURLWithDisposition(
        browser(), main_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
        ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  }

  void NavigateFrameTo(std::string_view host, std::string_view path) {
    NavigateFrameTo(https_server_.GetURL(host, path));
  }

  void NavigateFrameTo(const GURL& url,
                       BrowserWindowInterface* browser_ptr = nullptr,
                       std::string_view iframe_id = "test") {
    content::WebContents* web_contents = (browser_ptr ? browser_ptr : browser())
                                             ->GetTabStripModel()
                                             ->GetActiveWebContents();
    EXPECT_TRUE(NavigateIframeToURL(web_contents, iframe_id, url));
  }

  void NavigateNestedFrameTo(std::string_view host, std::string_view path) {
    NavigateNestedFrameTo(https_server_.GetURL(host, path));
  }

  // Navigates the innermost frame to the given URL. (The web_contents is
  // assumed to be showing a page containing an iframe that contains another
  // iframe.) The navigation's initiator is the middle iframe (not the leaf).
  void NavigateNestedFrameTo(const GURL& url) {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::TestNavigationObserver load_observer(web_contents);
    ASSERT_TRUE(ExecJs(
        GetFrame(),
        base::StringPrintf("document.body.querySelector('iframe').src = '%s';",
                           url.spec().c_str())));
    load_observer.Wait();
  }

  void NavigateToPageWithTwoFrames(std::string_view host) {
    GURL main_url(https_server_.GetURL(host, "/two_iframes_blank.html"));
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  }

  void NavigateFirstFrameTo(const GURL& url) {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(NavigateIframeToURL(web_contents, "iframe1", url));
  }

  void NavigateSecondFrameTo(const GURL& url) {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(NavigateIframeToURL(web_contents, "iframe2", url));
  }

  GURL EchoCookiesURL(std::string_view host) {
    return https_server().GetURL(host, "/echoheader?cookie");
  }

  std::string ServerRedirectPath(const GURL& url) {
    return base::StrCat(
        {"/server-redirect?",
         base::EscapeQueryParamValue(url.spec(), /*use_plus=*/true)});
  }

  GURL RedirectViaHosts(const std::vector<std::string_view>& hosts,
                        const GURL& destination) {
    GURL url = destination;

    for (const auto& host : std::views::reverse(hosts)) {
      url = https_server().GetURL(host, ServerRedirectPath(url));
    }
    return url;
  }

  std::string CookiesFromFetch(content::RenderFrameHost* render_frame_host,
                               std::string_view subresource_host) {
    return ContentFromFetch(render_frame_host, subresource_host,
                            kEchoCookiesWithCorsPath);
  }

  std::string ContentFromFetch(content::RenderFrameHost* render_frame_host,
                               std::string_view subresource_host,
                               std::string_view path) {
    return storage::test::FetchWithCredentials(
        render_frame_host, https_server_.GetURL(subresource_host, path),
        /*cors_enabled=*/true);
  }

  // Reads cookies via `document.cookie` in the provided RFH, and via a
  // subresource request from the provided RFH to the given host. These are
  // bundled together to ensure that tests always check both, and that they're
  // consistent.
  std::pair<std::string, std::string> ReadCookies(
      content::RenderFrameHost* render_frame_host,
      std::string_view subresource_host) {
    return {
        content::EvalJs(render_frame_host, "document.cookie",
                        content::EXECUTE_SCRIPT_NO_USER_GESTURE)
            .ExtractString(),
        CookiesFromFetch(render_frame_host, subresource_host),
    };
  }

  // Reads cookies via `document.cookie` in the provided RFH, and via a
  // subresource request from the provided RFH to the given host, and also
  // includes the content of the provided RFH. This is most useful to check that
  // the cookies accessible during navigation (via `echoheader?cookie`), during
  // load (via `echoheader?cookie` for subresources), and via script execution
  // (via `document.cookie`) are consistent with each other.
  std::tuple<std::string, std::string, std::string> ReadCookiesAndContent(
      content::RenderFrameHost* render_frame_host,
      std::string_view subresource_host) {
    auto [js_cookies, subresource_cookies] =
        ReadCookies(render_frame_host, subresource_host);
    return {
        js_cookies,
        subresource_cookies,
        storage::test::GetFrameContent(render_frame_host),
    };
  }

  content::RenderFrameHost* GetPrimaryMainFrame(
      BrowserWindowInterface* browser_ptr = nullptr) {
    content::WebContents* web_contents = (browser_ptr ? browser_ptr : browser())
                                             ->GetTabStripModel()
                                             ->GetActiveWebContents();
    return web_contents->GetPrimaryMainFrame();
  }

  content::RenderFrameHost* GetFrame(
      BrowserWindowInterface* browser_ptr = nullptr) {
    return ChildFrameAt(GetPrimaryMainFrame(browser_ptr), 0);
  }

  content::RenderFrameHost* GetNestedFrame(
      BrowserWindowInterface* browser_ptr = nullptr) {
    return ChildFrameAt(GetFrame(browser_ptr), 0);
  }

  content::RenderFrameHost* GetFirstFrame() { return GetFrame(); }

  content::RenderFrameHost* GetSecondFrame() {
    return ChildFrameAt(GetPrimaryMainFrame(), 1);
  }

  void EnsureUserInteractionOn(std::string_view host,
                               BrowserWindowInterface* browser_ptr = nullptr) {
    if (browser_ptr == nullptr) {
      browser_ptr = browser();
    }
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser_ptr, https_server_.GetURL(host, "/empty.html")));
    // ExecJs runs with a synthetic user interaction (by default), which is all
    // we need, so our script is a no-op.
    ASSERT_TRUE(content::ExecJs(
        browser_ptr->GetTabStripModel()->GetActiveWebContents(), ""));
  }

  void OpenConnectToPage(content::RenderFrameHost* frame) {
    std::string query = base::StrCat(
        {"url=", net::test_server::GetWebSocketURL(https_server_, kHostB,
                                                   "/echo-request-headers")
                     .spec()});
    GURL::Replacements replacements;
    replacements.SetQueryStr(query);

    ASSERT_TRUE(content::NavigateToURLFromRenderer(
        frame, https_server()
                   .GetURL(kHostB, "/websocket/connect_to.html")
                   .ReplaceComponents(replacements)));
  }

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

  permissions::MockPermissionPromptFactory* prompt_factory() {
    return prompt_factory_.get();
  }

  content_settings::PageSpecificContentSettings* content_settings() {
    return content_settings::PageSpecificContentSettings::GetForFrame(
        GetPrimaryMainFrame());
  }

  std::vector<std::pair<std::string, net::test_server::HttpRequest::HeaderMap>>
  ObservedRequestHeaders() const {
    base::AutoLock lock(lock_);
    return observed_request_headers_;
  }

  void SetRetryAllowedOriginFromHost(std::string_view host) {
    set_retry_allowed_origin(base::StrCat({
        "\"",
        url::Origin::Create(GetURL(host)).Serialize(),
        "\"",
    }));
  }

  void set_retry_allowed_origin(std::string_view allowed_origin) {
    retry_allowed_origin_ = allowed_origin;
  }

  int retry_path_fetch_count_ = 0;

 private:
  net::test_server::EmbeddedTestServer https_server_;
  base::test::ScopedFeatureList features_;
  std::unique_ptr<permissions::MockPermissionPromptFactory> prompt_factory_;
  mutable base::Lock lock_;
  std::vector<std::pair<std::string, net::test_server::HttpRequest::HeaderMap>>
      observed_request_headers_ GUARDED_BY(lock_);
  std::string retry_allowed_origin_ = "";
};

// Test fixture for core Storage Access API functionality, guaranteed by spec.
// This fixture should use the minimal set of features/params.
class StorageAccessAPIBrowserTest : public StorageAccessAPIBaseBrowserTest {};

// Test fixture for tests whose behavior could be affected by Origin Isolation
// (kOriginKeyedProcessesByDefault). This is a parameterised version of
// StorageAccessAPIBrowserTest that runs each test with Origin Isolation
// enabled and disabled.
class StorageAccessAPIOriginIsolationBrowserTest
    : public StorageAccessAPIBaseBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  StorageAccessAPIOriginIsolationBrowserTest() = default;
  ~StorageAccessAPIOriginIsolationBrowserTest() = default;

  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
    if (GetParam()) {
      return {
          {features::kOriginKeyedProcessesByDefault, {}},
      };
    }
    return {};
  }

  std::vector<base::test::FeatureRef> GetDisabledFeatures() override {
    // TODO(crbug.com/452061489): Fix tests that fail when the WebUI Omnibox is
    // enabled and then remove the two omnibox Features below.
    if (!GetParam()) {
      return {features::kOriginKeyedProcessesByDefault,
              omnibox::internal::kWebUIOmniboxPopup,
              omnibox::internal::kWebUIOmniboxAimPopup};
    }
    return {omnibox::internal::kWebUIOmniboxPopup,
            omnibox::internal::kWebUIOmniboxAimPopup};
  }
};

// Check default values for permissions.query on storage-access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryDefault) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");

  EXPECT_EQ(QueryPermission(GetPrimaryMainFrame()), "granted");
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
}

// Check default values for permissions.query on storage-access when 3p cookie
// is allowed.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       PermissionQueryDefault_AllowCrossSiteCookie) {
  SetBlockThirdPartyCookies(false);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");

  EXPECT_EQ(QueryPermission(GetPrimaryMainFrame()), "granted");
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       PermissionGrantedViaDevtools) {
  SetBlockThirdPartyCookies(true);

  content::TestDevToolsProtocolClient devtools_client;
  devtools_client.AttachToWebContents(
      browser()->tab_strip_model()->GetActiveWebContents());
  devtools_client.SendCommandSync("Browser.enable");

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");

  // Ensure that we do not currently have a granted permission
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
  EXPECT_THAT(content_settings()->GetTwoSiteRequests(
                  ContentSettingsType::STORAGE_ACCESS),
              IsEmpty());

  auto test_storage_access = [&](bool expected_for_frame,
                                 bool expected_for_other_frame) {
    EXPECT_EQ(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()),
              expected_for_frame);
    EXPECT_EQ(QueryPermission(GetFrame()),
              expected_for_frame ? "granted" : "prompt");
    EXPECT_EQ(
        ReadCookies(GetFrame(), kHostB),
        expected_for_frame ? CookieBundle("cross-site=b.test") : kNoCookies);

    // Check access for other frame
    NavigateToPageWithFrame(kHostA);
    NavigateFrameTo(kHostC, "/empty.html");
    EXPECT_EQ(QueryPermission(GetFrame()),
              expected_for_other_frame ? "granted" : "prompt");

    // Ensure that after a navigation the permission state is preserved.
    NavigateToPageWithFrame(kHostA);
    NavigateFrameTo(kHostB, "/echoheader?cookie");
    EXPECT_EQ(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()),
              expected_for_frame);
    EXPECT_EQ(QueryPermission(GetFrame()),
              expected_for_frame ? "granted" : "prompt");
    EXPECT_EQ(
        ReadCookies(GetFrame(), kHostB),
        expected_for_frame ? CookieBundle("cross-site=b.test") : kNoCookies);

    devtools_client.SendCommandSync("Browser.resetPermissions");

    EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
  };

  devtools_client.SendCommandSync(
      "Browser.setPermission",
      base::DictValue()
          .Set("setting", "granted")
          .Set("permission", base::DictValue().Set("name", "storage-access")));
  test_storage_access(/*expected_for_frame=*/true,
                      /*expected_for_other_frame=*/true);

  devtools_client.SendCommandSync(
      "Browser.setPermission",
      base::DictValue()
          .Set("setting", "granted")
          .Set("origin", kOriginA)
          .Set("embeddedOrigin", kOriginB)
          .Set("permission", base::DictValue().Set("name", "storage-access")));
  test_storage_access(/*expected_for_frame=*/true,
                      /*expected_for_other_frame=*/false);

  devtools_client.DetachProtocolClient();
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       PermissionDeniedViaDevtools) {
  SetBlockThirdPartyCookies(true);

  content::TestDevToolsProtocolClient devtools_client;
  devtools_client.AttachToWebContents(
      browser()->tab_strip_model()->GetActiveWebContents());
  devtools_client.SendCommandSync("Browser.enable");

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/empty.html");

  devtools_client.SendCommandSync(
      "Browser.setPermission",
      base::DictValue()
          .Set("setting", "granted")
          .Set("origin", kOriginA)
          .Set("embeddedOrigin", kOriginB)
          .Set("permission", base::DictValue().Set("name", "storage-access")));
  ASSERT_EQ(QueryPermission(GetFrame()), "granted");

  devtools_client.SendCommandSync(
      "Browser.setPermission",
      base::DictValue()
          .Set("setting", "denied")
          .Set("origin", kOriginA)
          .Set("embeddedOrigin", kOriginB)
          .Set("permission", base::DictValue().Set("name", "storage-access")));

  // Ensure that the 'denied' status is masked as 'prompt'.
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");

  devtools_client.DetachProtocolClient();
}

// Test that permissions.query changes to "granted" when a storage access
// request was successful.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryGranted) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_THAT(content_settings()->GetTwoSiteRequests(
                  ContentSettingsType::STORAGE_ACCESS),
              IsEmpty());

  // Grant initial permission.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "granted");

  EXPECT_THAT(
      content_settings()->GetTwoSiteRequests(
          ContentSettingsType::STORAGE_ACCESS),
      UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), true)));

  // Ensure that after a navigation the permission state is preserved.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "granted");

  EXPECT_THAT(content_settings()->GetTwoSiteRequests(
                  ContentSettingsType::STORAGE_ACCESS),
              IsEmpty());

  // And the permission is regranted without prompt.
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::NONE);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_THAT(
      content_settings()->GetTwoSiteRequests(
          ContentSettingsType::STORAGE_ACCESS),
      UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), true)));
}

// Test that permissions.query changes to "denied" when a storage access
// request was denied.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryDenied) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  EXPECT_THAT(content_settings()->GetTwoSiteRequests(
                  ContentSettingsType::STORAGE_ACCESS),
              IsEmpty());

  // Deny initial permission.
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");

  EXPECT_THAT(
      content_settings()->GetTwoSiteRequests(
          ContentSettingsType::STORAGE_ACCESS),
      UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), false)));

  // Ensure that after a navigation the permission state is preserved.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");

  EXPECT_THAT(content_settings()->GetTwoSiteRequests(
                  ContentSettingsType::STORAGE_ACCESS),
              IsEmpty());

  // And the permission is denied without prompt.
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::NONE);
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_THAT(
      content_settings()->GetTwoSiteRequests(
          ContentSettingsType::STORAGE_ACCESS),
      UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), false)));
}

// Test that permissions.query inside a credentialless iframe does not expose
// embargoed status when a storage access request has been repeatedly dismissed.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       PermissionQueryEmbargoed_CredentiallessFrame) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/echoheader?cookie");

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DISMISS);

  // Dismissing the prompt 3 times places the origin under embargo.
  for (int i = 0; i < 3; ++i) {
    EXPECT_FALSE(
        content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  }
  ASSERT_EQ(prompt_factory()->TotalRequestCount(), 3);
  ASSERT_EQ(QueryPermission(GetFrame()), "prompt");

  NavigateToPageWithFrame(kHostA, /*browser_ptr=*/nullptr,
                          /*credentialless=*/true);
  NavigateFrameTo(kHostB, "/echoheader?cookie");
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // Even when under embargo, permissions.query inside a credentialless iframe
  // should return "prompt" rather than "denied".
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryCrossSite) {
  SetBlockThirdPartyCookies(true);

  EnsureUserInteractionOn(kHostA);

  NavigateToPageWithFrame(kHostB);
  NavigateFrameTo(kHostA, "/echoheader?cookie");

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  // Grant initial permission.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(QueryPermission(GetFrame()), "granted");

  // Ensure that the scope of the permission grant is for the entire site.
  NavigateFrameTo(kHostASubdomain, "/echoheader?cookie");
  EXPECT_EQ(QueryPermission(GetFrame()), "granted");

  // The permission should not be available cross-site.
  NavigateFrameTo(kHostC, "/echoheader?cookie");
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       Permission_Denied_WithoutInteraction) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::NONE);

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()",
                               content::EXECUTE_SCRIPT_NO_USER_GESTURE));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
}

// Validate that a cross-site iframe can bypass third-party cookie blocking via
// the Storage Access API.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));
}

// Validate that a cross-partition iframe can bypass blob URL partitioning via
// the Storage Access API. For an equivalent content browser test see
// BlobUrlBrowserTest.BlobUrlPartitioningNotAlwaysBypassedWithThirdPartyCookieEnabled.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyCookiesIFrameRequestsAccess_BlobURL) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostA, "/empty.html");

  content::RenderFrameHost* main_frame = GetPrimaryMainFrame();
  content::RenderFrameHost* nested_frame = GetNestedFrame();

  std::string blob_url_string =
      content::EvalJs(main_frame,
                      "const blob_url = URL.createObjectURL(new "
                      "Blob(['<!doctype html><body>potato</body>'], {type: "
                      "'text/html'}));"
                      "blob_url;")
          .ExtractString();
  GURL blob_url(blob_url_string);

  std::string fetch_blob_url_js = content::JsReplace("fetch($1)", blob_url);

  EXPECT_TRUE(content::ExecJs(main_frame, fetch_blob_url_js));
  EXPECT_FALSE(content::ExecJs(nested_frame, fetch_blob_url_js));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(nested_frame));

  EXPECT_TRUE(content::ExecJs(nested_frame, fetch_blob_url_js));

  EXPECT_TRUE(content::ExecJs(
      main_frame, content::JsReplace("URL.revokeObjectURL($1)", blob_url)));

  EXPECT_FALSE(content::ExecJs(main_frame, fetch_blob_url_js));
  EXPECT_FALSE(content::ExecJs(nested_frame, fetch_blob_url_js));
}

// TODO(https://crbug.com/540611509): Fails on Linux CFI and Linux debug builds.
#if BUILDFLAG(IS_LINUX) &&                                      \
    (!defined(NDEBUG) ||                                        \
     BUILDFLAG(CFI_CAST_CHECK) || BUILDFLAG(CFI_ICALL_CHECK) || \
     BUILDFLAG(CFI_ENFORCEMENT_TRAP) || BUILDFLAG(CFI_ENFORCEMENT_DIAGNOSTIC))
#define MAYBE_AccessGranted_DoesNotConsumeUserInteraction \
  DISABLED_AccessGranted_DoesNotConsumeUserInteraction
#else
#define MAYBE_AccessGranted_DoesNotConsumeUserInteraction \
  AccessGranted_DoesNotConsumeUserInteraction
#endif
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       MAYBE_AccessGranted_DoesNotConsumeUserInteraction) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(content::EvalJs(GetFrame(), "navigator.userActivation.isActive",
                            content::EXECUTE_SCRIPT_NO_USER_GESTURE),
            true);
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       AccessGranted_NoSubsequentUserInteraction) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->ResetCounts();

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(
      GetFrame(), /*omit_user_gesture=*/true));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 0);
}

// Validate that if an iframe obtains access, then cookies become unblocked for
// just that top-level/third-party combination and are still blocked for other
// combinations.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe_UnrelatedSites) {
  SetBlockThirdPartyCookies(true);
  base::HistogramTester histogram_tester;

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateFrameTo(EchoCookiesURL(kHostC));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();

  EXPECT_THAT(
      histogram_tester.GetBucketCount(
          kUseCounterHistogram,
          blink::mojom::WebFeature::kStorageAccessAPI_HasStorageAccess_Method),
      Gt(0));
  EXPECT_THAT(histogram_tester.GetBucketCount(
                  kUseCounterHistogram,
                  blink::mojom::WebFeature::
                      kStorageAccessAPI_requestStorageAccess_Method),
              Gt(0));
}

// Validate that a nested A(B(B)) iframe can obtain cookie access, and that that
// access is not shared with the "middle" B iframe.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_InnerRequestsAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB),
            CookieBundle("cross-site=b.test"));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
}

// Validate that in a A(B) frame tree, the iframe's cross-origin same-site
// fetches are not credentialed.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyCookiesIFrameRequestsAccess_CrossOriginFetch) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostBSubdomain));

  ASSERT_EQ(ReadCookies(GetFrame(), kHostBSubdomain), kNoCookies);
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostBSubdomain2), "None");
}

// Validate that in a A(B) frame tree, the iframe can make uncredentialed
// same-site requests, even if the requests are cross-origin, and the correct
// metrics buckets are sampled to.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameRequestsAccess_CrossOriginFetch_Uncredentialed) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostBSubdomain));

  ASSERT_EQ(ReadCookies(GetFrame(), kHostBSubdomain), kNoCookies);
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(content::EvalJs(GetFrame(),
                            content::JsReplace(R"(
      fetch($1, {method: 'GET', mode: 'cors', credentials: 'omit'})
        .then((result) => result.text());
  )",
                                               https_server().GetURL(
                                                   kHostBSubdomain2,
                                                   kEchoCookiesWithCorsPath))),
            "None");
}

// Validate that in a A(B) frame tree, the iframe cannot make credentialed
// cross-site requests.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyCookiesIFrameRequestsAccess_CrossSiteFetch) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostC), "None");

  EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostBSubdomain2), "None");
}

// Validate that in a A(B(B)) frame tree, the middle B iframe can obtain access,
// and that access is not shared with the leaf B iframe.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_MiddleRequestsAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), kNoCookies);

  // Subresource request from the cross-site iframe to an end point that's
  // same-origin with the top-level does not enable cookie access.
  EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostA), "None");
  EXPECT_EQ(CookiesFromFetch(GetNestedFrame(), kHostA), "None");
}

// Validate that in a A(B(C)) frame tree, the C leaf iframe can obtain cookie
// access.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_DistinctSites) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostC);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostC));

  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC),
            kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC),
            CookieBundle("cross-site=c.test"));
}

// Validate that cross-site sibling iframes cannot take advantage of each
// other's granted permission.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyCookiesCrossSiteSiblingIFrameRequestsAccess) {
  EnsureUserInteractionOn(kHostC);

  NavigateToPageWithTwoFrames(kHostA);
  NavigateFirstFrameTo(EchoCookiesURL(kHostB));
  NavigateSecondFrameTo(EchoCookiesURL(kHostC));
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW);
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSetting(GetURL(kHostC), CONTENT_SETTING_ALLOW);

  // Verify that both same-origin subresource request and cross-origin
  // subresource request can access cookies for the kHostB iframe.
  ASSERT_EQ(CookiesFromFetch(GetFirstFrame(), kHostB), "cross-site=b.test");
  ASSERT_EQ(CookiesFromFetch(GetFirstFrame(), kHostC), "cross-site=c.test");

  // Verify that both same-origin subresource request and cross-origin
  // subresource request can access cookies for the kHostC iframe.
  ASSERT_EQ(CookiesFromFetch(GetSecondFrame(), kHostC), "cross-site=c.test");
  ASSERT_EQ(CookiesFromFetch(GetSecondFrame(), kHostB), "cross-site=b.test");

  SetBlockThirdPartyCookies(true);
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->ResetCookieSetting(GetURL(kHostB));
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->ResetCookieSetting(GetURL(kHostC));
  // Navigate the first iframe to kHostB and grant Storage Access.
  NavigateFirstFrameTo(EchoCookiesURL(kHostB));
  EXPECT_EQ(ReadCookiesAndContent(GetFirstFrame(), kHostB),
            kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFirstFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetFirstFrame()));
  EXPECT_EQ(ReadCookies(GetFirstFrame(), kHostB),
            CookieBundle("cross-site=b.test"));

  // Navigate the second iframe to kHostC and grant Storage Access.
  NavigateSecondFrameTo(EchoCookiesURL(kHostC));
  EXPECT_EQ(ReadCookiesAndContent(GetSecondFrame(), kHostC),
            kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetSecondFrame()));
  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetSecondFrame()));
  EXPECT_EQ(ReadCookies(GetSecondFrame(), kHostC),
            CookieBundle("cross-site=c.test"));

  // Verify same-origin subresource request has cookie access whereas the
  // cross-origin subresource request does not for the kHostB iframe.
  EXPECT_EQ(CookiesFromFetch(GetFirstFrame(), kHostB), "cross-site=b.test");
  EXPECT_EQ(CookiesFromFetch(GetFirstFrame(), kHostC), "None");

  // Verify same-origin subresource request has cookie access whereas the
  // cross-origin subresource request does not for the kHostC iframe.
  EXPECT_EQ(CookiesFromFetch(GetSecondFrame(), kHostC), "cross-site=c.test");
  EXPECT_EQ(CookiesFromFetch(GetSecondFrame(), kHostB), "None");
}

// Validate that the Storage Access API does not override any explicit user
// settings to block storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyCookiesIFrameThirdPartyExceptions) {
  SetBlockThirdPartyCookies(true);
  BlockAllCookiesOnHost(kHostB);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(content::EvalJs(GetFrame(), "navigator.userActivation.isActive",
                            content::EXECUTE_SCRIPT_NO_USER_GESTURE),
            false);

  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
}

// Validate that user settings take precedence for the leaf in a A(B(B)) frame
// tree.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameThirdPartyExceptions_NestedSameSite) {
  SetBlockThirdPartyCookies(true);
  BlockAllCookiesOnHost(kHostB);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(
      content::ExecJs(GetNestedFrame(), "document.requestStorageAccess()"));

  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), kNoCookies);
}

// Validate that user settings take precedence for the leaf in a A(B(C)) frame
// tree.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameThirdPartyExceptions_NestedCrossSite) {
  EnsureUserInteractionOn(kHostC);

  SetBlockThirdPartyCookies(true);
  BlockAllCookiesOnHost(kHostC);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostC));

  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC),
            kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(
      content::ExecJs(GetNestedFrame(), "document.requestStorageAccess()"));

  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC), kNoCookies);
}

// Validate that user settings take precedence for the leaf in a A(B(A)) frame
// tree.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameThirdPartyExceptions_CrossSiteAncestorChain) {
  EnsureUserInteractionOn(kHostA);
  SetBlockThirdPartyCookies(true);
  BlockAllCookiesOnHost(kHostA);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostA));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetPrimaryMainFrame()));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(
      content::ExecJs(GetNestedFrame(), "document.requestStorageAccess()"));

  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostA),
            kNoCookiesWithContent);
}

// Validate that user settings take precedence for the leaf in a A(A) frame
// tree.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    ThirdPartyCookiesIFrameThirdPartyExceptions_SameSiteAncestorChain) {
  EnsureUserInteractionOn(kHostA);
  SetBlockThirdPartyCookies(true);
  BlockAllCookiesOnHost(kHostA);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostA));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetPrimaryMainFrame()));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent);
}

// Validates that once a grant is removed access is also removed.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ThirdPartyGrantsDeletedAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));

  // Manually delete all our grants.
  HostContentSettingsMap* settings_map =
      HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile());
  settings_map->ClearSettingsForOneType(ContentSettingsType::STORAGE_ACCESS);

  // Try to ensure that the pref observer is triggered and the updated settings
  // are propagated to the network service.
  base::RunLoop().RunUntilIdle();
  browser()
      ->GetProfile()
      ->GetDefaultStoragePartition()
      ->FlushNetworkInterfaceForTesting();

  // Verify cookie cannot be accessed.
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);

  // Access change should be reflected immediately.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

// Validates that if the user explicitly blocks cookies, cookie access is
// blocked even with the existing grant.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       ExplicitUserSettingsBlockThirdPartyGrantsAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));

  BlockAllCookiesOnHost(kHostB);
  // Try to ensure that the pref observer is triggered and the updated settings
  // are propagated to the network service.
  base::RunLoop().RunUntilIdle();
  browser()
      ->GetProfile()
      ->GetDefaultStoragePartition()
      ->FlushNetworkInterfaceForTesting();

  // Access change should be reflected immediately.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // TODO(crbug.com/40266432): should EXPECT_FALSE here since user
  // explicitly blocks cookies for hostB
  EXPECT_TRUE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
}

// Validate that if the iframe's origin is opaque, it cannot obtain storage
// access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, OpaqueOriginRejects) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  ASSERT_TRUE(
      ExecJs(GetPrimaryMainFrame(),
             "document.querySelector('iframe').sandbox='allow-scripts';"));
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

// Validate that if the iframe is sandboxed and allows scripts but is missing
// the Storage Access sandbox tag, the iframe cannot obtain storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       MissingSandboxTokenRejects) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  ASSERT_TRUE(ExecJs(GetPrimaryMainFrame(),
                     "document.querySelector('iframe').sandbox='allow-"
                     "scripts allow-same-origin';"));
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
}

// Validate that if the iframe is sandboxed and has the Storage Access sandbox
// tag, the iframe can obtain storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, SandboxTokenResolves) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  ASSERT_TRUE(
      ExecJs(GetPrimaryMainFrame(),
             "document.querySelector('iframe').sandbox='allow-scripts "
             "allow-same-origin allow-storage-access-by-user-activation';"));
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));
}

// Validates that expired grants don't get reused.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, ThirdPartyGrantsExpiry) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostC));

  // Manually create a pre-expired grant and ensure it doesn't grant access for
  // HostB.
  const base::TimeDelta lifetime = base::Days(30);
  const base::Time creation_time =
      base::Time::Now() - base::Minutes(5) - lifetime;
  HostContentSettingsMap* settings_map =
      HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile());
  content_settings::ContentSettingConstraints constraints(creation_time);
  constraints.set_lifetime(lifetime);
  constraints.set_session_model(
      content_settings::mojom::SessionModel::USER_SESSION);
  settings_map->SetContentSettingDefaultScope(
      GetURL(kHostB), GetURL(kHostA), ContentSettingsType::STORAGE_ACCESS,
      CONTENT_SETTING_ALLOW, constraints);
  settings_map->SetContentSettingDefaultScope(
      GetURL(kHostC), GetURL(kHostA), ContentSettingsType::STORAGE_ACCESS,
      CONTENT_SETTING_ALLOW);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  // The iframe should request for new grant since the existing one is expired.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // The nested iframe reuses the existing grant without prompting.
  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC),
            CookieBundle("cross-site=c.test"));

  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostC));
  // Only when the initiator is the frame that's been navigated can inherit
  // per-frame storage access.
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC),
            kNoCookiesWithContent);
}

// Validate that if an iframe navigates itself to a same-origin endpoint, and
// that navigation does not include any cross-origin redirects, the new document
// can inherit storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       Navigation_SelfInitiated_SameOrigin_Preserves) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(
      content::NavigateToURLFromRenderer(GetFrame(), EchoCookiesURL(kHostB)));

  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB),
            CookieBundleWithContent("cross-site=b.test"));
}

// Validate that if an iframe is navigated (by a cross-site initiator) to a
// same-origin endpoint, and that navigation does not include any cross-origin
// redirects, the new document cannot inherit storage access.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    Navigation_NonSelfInitiated_SameOriginDestination_CrossSiteInitiator) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // The navigation for this frame does not include cookies, since the initiator
  // is cross-site from the destination, and the initiator did not have storage
  // access.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
}

// Validate that if an iframe is navigated (by a same-site initiator) to a
// same-origin endpoint (even if the navigation does not include any
// cross-origin redirects), the new document cannot inherit storage access.
IN_PROC_BROWSER_TEST_P(
    StorageAccessAPIOriginIsolationBrowserTest,
    Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain));

  ASSERT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  ASSERT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  // The navigation itself didn't carry cookies since the fetch was
  // cross-origin, and the new document did not inherit storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);
}

// Validate that if an iframe is navigated (by a same-site initiator) to a
// same-origin endpoint (even if the navigation does not include any
// cross-origin redirects, and the navigated frame has obtained storage access
// already), the new document cannot inherit storage access.
IN_PROC_BROWSER_TEST_P(
    StorageAccessAPIOriginIsolationBrowserTest,
    Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator_TargetHasStorageAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain));

  ASSERT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  ASSERT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));

  NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  // The navigation itself didn't carry cookies since the fetch was
  // cross-origin, and the new document did not inherit storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);
}

// Validate that if an iframe navigates itself to a same-site cross-origin
// endpoint, and that navigation does not include any cross-origin redirects,
// the new document cannot inherit storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       Navigation_SelfInitiated_SameSiteCrossOrigin) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(content::NavigateToURLFromRenderer(
      GetFrame(), EchoCookiesURL(kHostBSubdomain)));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // The navigation itself didn't carry cookies since the fetch was
  // cross-origin, and the new document did not inherit storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
}

// Validate that if an iframe navigates itself to a cross-site endpoint, and
// that navigation does not include any cross-origin redirects, the new document
// cannot inherit storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       Navigation_SelfInitiated_CrossSite) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(
      content::NavigateToURLFromRenderer(GetFrame(), EchoCookiesURL(kHostC)));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), kNoCookiesWithContent);
}

// Validate that if an iframe navigates itself to a same-origin endpoint, but
// that navigation include a cross-origin redirect, the new document
// cannot inherit storage access.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    Navigation_SelfInitiated_SameOrigin_CrossOriginRedirect) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  GURL dest = EchoCookiesURL(kHostB);
  EXPECT_TRUE(content::NavigateToURLFromRenderer(
      GetFrame(),
      /*url=*/
      RedirectViaHosts({kHostBSubdomain}, dest),
      /*expected_commit_url=*/dest));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // The navigation itself didn't carry cookies since the fetch was
  // cross-origin, and the new document did not inherit storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostBSubdomain),
            kNoCookiesWithContent);
}

// Validate that if an iframe navigates itself to a same-origin endpoint, and
// that navigation includes a cross-origin redirect (even if there's a
// subsequent same-origin redirect), the new document cannot inherit storage
// access.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    Navigation_SelfInitiated_SameOrigin_CrossSiteAndSameSiteRedirects) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  GURL dest = EchoCookiesURL(kHostB);
  EXPECT_TRUE(content::NavigateToURLFromRenderer(
      GetFrame(),
      /*url=*/
      RedirectViaHosts({kHostBSubdomain, kHostB}, dest),
      /*expected_commit_url=*/dest));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // The navigation itself didn't carry cookies since the fetch was
  // cross-origin, and the new document did not inherit storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostBSubdomain),
            kNoCookiesWithContent);
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       Navigation_ViaFormSubmission_SameOriginRedirect) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(https_server().GetURL(kHostB, "/form.html"));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(content::ExecJs(
      GetFrame(),
      content::JsReplace("document.getElementById('form').action = $1",
                         RedirectViaHosts({kHostB}, EchoCookiesURL(kHostB)))));

  {
    content::TestNavigationObserver load_observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    EXPECT_TRUE(content::ExecJs(GetFrame(),
                                "document.getElementById('form').submit()"));
    load_observer.Wait();
  }

  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // The navigation itself carried cookies from the previous document's storage
  // access, and the new document inherited storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB),
            CookieBundleWithContent("cross-site=b.test"));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       Navigation_ViaFormSubmission_CrossOriginRedirect) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(https_server().GetURL(kHostB, "/form.html"));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(content::ExecJs(
      GetFrame(),
      content::JsReplace(
          "document.getElementById('form').action = $1",
          RedirectViaHosts({kHostBSubdomain}, EchoCookiesURL(kHostB)))));

  {
    content::TestNavigationObserver load_observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    EXPECT_TRUE(content::ExecJs(GetFrame(),
                                "document.getElementById('form').submit()"));
    load_observer.Wait();
  }

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  // The navigation itself didn't carry cookies since the fetch was
  // cross-origin, and the new document did not inherit storage access.
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
}

// Validate that in a A(A) frame tree, the inner A iframe can obtain cookie
// access by default.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       EmbeddedSameOriginCookieAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostA));

  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA),
            CookieBundleWithContent("cross-site=a.test"));
}

// Validate that in a A(sub.A) frame tree, the inner A iframe can obtain cookie
// access by default.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       EmbeddedSameSiteCookieAccess) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostASubdomain));

  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA),
            CookieBundleWithContent("cross-site=a.test"));
}

// Validate that in a A(B(A)) frame tree, the inner A iframe can obtain cookie
// access after requesting access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       NestedSameOriginCookieAccess_CrossSiteAncestorChain) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostA, "/empty.html");

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA), kNoCookies);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA),
            CookieBundle("cross-site=a.test"));
}

// Validate that in a A(B(sub.A)) frame tree, the inner iframe can obtain cookie
// access after requesting access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       NestedSameSiteCookieAccess_CrossSiteAncestorChain) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostASubdomain, "/empty.html");

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA), kNoCookies);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostASubdomain),
            CookieBundle("cross-site=a.test"));

  // Subsequent permission requests are no-ops.
  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostASubdomain),
            CookieBundle("cross-site=a.test"));
}

IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    DedicatedWorker_InheritsStorageAccessFromDocument_NewGrant) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(
      GetURL(kHostB, "/workers/fetch_from_worker.html?start_worker_manually"));
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // When the worker's parent document has storage access at the time the worker
  // is created, the worker should inherit that access and be able to use it.
  EXPECT_TRUE(content::ExecJs(GetFrame(), "start_worker()"));
  EXPECT_EQ(
      content::EvalJs(GetFrame(), "fetch_from_worker('/echoheader?cookie');"),
      "cross-site=b.test");
}

IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    DedicatedWorker_InheritsStorageAccessFromDocument_ExistingGrant) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);
  // This cross-origin navigation avoids propagating storage access by default
  // so that the kHostB frame has to request it again.
  NavigateFrameTo(RedirectViaHosts(
      {kHostA},
      GetURL(kHostB, "/workers/fetch_from_worker.html?start_worker_manually")));
  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // When the worker's parent document has storage access at the time the worker
  // is created, the worker should inherit that access and be able to use it.
  EXPECT_TRUE(content::ExecJs(GetFrame(), "start_worker()"));
  EXPECT_EQ(
      content::EvalJs(GetFrame(), "fetch_from_worker('/echoheader?cookie');"),
      "cross-site=b.test");
}

IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIBrowserTest,
    DedicatedWorker_InheritsStorageAccessFromDocument_NavigationInheritance) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  // Get storage access and then do a self-initiated same-site navigation to the
  // fetch_from_worker.html, so that fetch_from_worker.html has storage access
  // upon load (and therefore has storage access when it creates the Worker).
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  ASSERT_TRUE(content::NavigateToURLFromRenderer(
      GetFrame(),
      https_server().GetURL(kHostB, "/workers/fetch_from_worker.html")));
  ASSERT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // When the worker's parent document has storage access at the time the worker
  // is created, the worker should inherit that access and be able to use it.
  EXPECT_EQ(
      content::EvalJs(GetFrame(), "fetch_from_worker('/echoheader?cookie');"),
      "cross-site=b.test");
}

// Regression test for https://crbug.com/409838513.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       DedicatedWorker_ABA_InheritsStorageAccessFromDocument) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(EchoCookiesURL(kHostA));
  ASSERT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  ASSERT_TRUE(content::NavigateToURLFromRenderer(
      GetNestedFrame(),
      https_server().GetURL(kHostA, "/workers/fetch_from_worker.html")));
  ASSERT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));

  // When the worker's parent document has storage access at the time the worker
  // is created, the worker should inherit that access and be able to use it.
  //
  // This should work despite the fact that this is an ABA context, and
  // therefore there is no explicit permission grant.
  EXPECT_EQ(content::EvalJs(GetNestedFrame(),
                            "fetch_from_worker('/echoheader?cookie');"),
            "cross-site=a.test");
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, DedicatedWorker_ABA) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(https_server().GetURL(
      kHostA, "/workers/fetch_from_worker.html?start_worker_manually"));

  ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  ASSERT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));

  // When the worker's parent document has storage access at the time the
  // worker is created, the worker should inherit that access and be able to
  // use it.
  //
  // This should work despite the fact that this is an ABA context, and
  // therefore there is no explicit permission grant.
  EXPECT_TRUE(content::ExecJs(GetNestedFrame(), "start_worker()"));
  EXPECT_EQ(content::EvalJs(GetNestedFrame(),
                            "fetch_from_worker('/echoheader?cookie');"),
            "cross-site=a.test");
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       WebsocketRequestsUseStorageAccessGrants) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  content::DOMMessageQueue message_queue(
      browser()->tab_strip_model()->GetActiveWebContents());
  // Before the document opts into Storage Access, the WebSocket should not send
  // unpartitioned cookies during the connection.
  {
    OpenConnectToPage(GetFrame());

    std::string message;
    EXPECT_TRUE(message_queue.WaitForMessage(&message));
    EXPECT_THAT(message, testing::Not(testing::HasSubstr("cross-site=b.test")));
  }

  // Get storage access and then do a self-initiated same-site navigation to the
  // connect_to.html, so that the websocket's frame has storage access
  // upon load (and therefore has storage access when it creates the websocket).
  NavigateFrameTo(EchoCookiesURL(kHostB));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // After the document opts into Storage Access, the WebSocket *should* send
  // unpartitioned cookies during the connection.
  {
    OpenConnectToPage(GetFrame());
    ASSERT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

    std::string message;
    EXPECT_TRUE(message_queue.WaitForMessage(&message));
    EXPECT_THAT(message, testing::HasSubstr("cross-site=b.test"));
  }
}

// Validate that in a A(B) frame tree, the embedded B iframe can obtain cookie
// access if requested and got accepted.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       EmbeddedCrossSiteCookieAccess_Accept) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(1, prompt_factory()->TotalRequestCount());
  EXPECT_EQ(1, prompt_factory()->RequestTypeSeen(
                   permissions::RequestType::kStorageAccess));
}

// Validate that in a A(B) frame tree, the embedded B iframe can not obtain
// cookie access if requested and got denied.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       EmbeddedCrossSiteCookieAccess_Deny) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(1, prompt_factory()->TotalRequestCount());
  EXPECT_EQ(1, prompt_factory()->RequestTypeSeen(
                   permissions::RequestType::kStorageAccess));
}

// Validate that if third-party cookies are blocked and the permission is
// denied, requestStorageAccess beyond cookies fails.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       BeyondCookies_WithoutCookiesWithoutPermission) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame(
      GetFrame()));
}

// Validate that if third-party cookies are blocked but the permission is
// allowed, requestStorageAccess beyond cookies succeeds.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       BeyondCookies_WithoutCookiesWithPermission) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame(
      GetFrame()));
}

class StorageAccessAPIStorageBrowserTest
    : public StorageAccessAPIBaseBrowserTest,
      public testing::WithParamInterface<std::tuple<TestType, bool>> {
 public:
  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
    return GetEnabledFeaturesForStorage(IsStoragePartitioned());
  }

  std::vector<base::test::FeatureRef> GetDisabledFeatures() override {
    return GetDisabledFeaturesForStorage(IsStoragePartitioned());
  }

  void ExpectStorage(content::RenderFrameHost* frame, bool expected) {
    switch (GetTestType()) {
      case TestType::kFrame:
        storage::test::ExpectStorageForFrame(frame, expected);
        return;
      case TestType::kWorker:
        storage::test::ExpectStorageForWorker(frame, expected);
        return;
    }
  }

  void SetStorage(content::RenderFrameHost* frame) {
    switch (GetTestType()) {
      case TestType::kFrame:
        storage::test::SetStorageForFrame(frame, /*include_cookies=*/false);
        return;
      case TestType::kWorker:
        storage::test::SetStorageForWorker(frame);
        return;
    }
  }

  bool DoesPermissionGrantStorage() const { return IsStoragePartitioned(); }

 private:
  TestType GetTestType() const { return std::get<0>(GetParam()); }
  bool IsStoragePartitioned() const { return std::get<1>(GetParam()); }
};

#if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)
// TODO(crbug.com/404576878): Test is flaky on Windows ASAN builds.
#define MAYBE_ThirdPartyIFrameStorageRequestsAccess \
  DISABLED_ThirdPartyIFrameStorageRequestsAccess
#else
#define MAYBE_ThirdPartyIFrameStorageRequestsAccess \
  ThirdPartyIFrameStorageRequestsAccess
#endif
// Validate that the Storage Access API will unblock other types of storage
// access when a grant is given and that it only applies to the top-level/third
// party pair requested on.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest,
                       MAYBE_ThirdPartyIFrameStorageRequestsAccess) {
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW);

  ExpectStorage(GetFrame(), false);
  SetStorage(GetFrame());
  ExpectStorage(GetFrame(), true);

  SetBlockThirdPartyCookies(true);
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->ResetCookieSetting(GetURL(kHostB));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");

  ExpectStorage(GetFrame(), ThirdPartyPartitionedStorageAllowedByDefault());
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");

  ExpectStorage(GetFrame(), DoesPermissionGrantStorage());
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
// TODO(crbug.com/430495897): Test is flaky on Windows ASAN builds.
#if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)
#define MAYBE_NestedThirdPartyIFrameStorage DISABLED_NestedThirdPartyIFrameStorage
#else
#define MAYBE_NestedThirdPartyIFrameStorage NestedThirdPartyIFrameStorage
#endif
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest,
                       MAYBE_NestedThirdPartyIFrameStorage) {
  EnsureUserInteractionOn(kHostC);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW);
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSetting(GetURL(kHostC), CONTENT_SETTING_ALLOW);

  ExpectStorage(GetNestedFrame(), false);
  SetStorage(GetNestedFrame());
  ExpectStorage(GetNestedFrame(), true);

  SetBlockThirdPartyCookies(true);
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->ResetCookieSetting(GetURL(kHostB));
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->ResetCookieSetting(GetURL(kHostC));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");

  ExpectStorage(GetNestedFrame(),
                ThirdPartyPartitionedStorageAllowedByDefault());
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");

  ExpectStorage(GetNestedFrame(), DoesPermissionGrantStorage());
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
}

// Test third-party cookie blocking of features that allow to communicate
// between tabs such as SharedWorkers.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest,
                       MultiTabTest_Storage) {
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW);

  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
  storage::test::SetCrossTabInfoForFrame(GetFrame());
  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // Create a second tab to test communication between tabs.
  NavigateToNewTabWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");

  permissions::PermissionRequestManager::FromWebContents(
      browser()->tab_strip_model()->GetActiveWebContents())
      ->set_auto_response_for_test(
          permissions::PermissionRequestManager::ACCEPT_ALL);

  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

  SetBlockThirdPartyCookies(true);
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->ResetCookieSetting(GetURL(kHostB));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");

  storage::test::ExpectCrossTabInfoForFrame(
      GetFrame(), ThirdPartyPartitionedStorageAllowedByDefault());
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/browsing_data/site_data.html");

  storage::test::ExpectCrossTabInfoForFrame(GetFrame(),
                                            DoesPermissionGrantStorage());
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

// Verifies that one tab can reuse the permission granted to another tab.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest, MultiTabTest) {
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/empty.html");

  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateToNewTabWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/empty.html");

  permissions::PermissionRequestManager::FromWebContents(
      browser()->tab_strip_model()->GetActiveWebContents())
      ->set_auto_response_for_test(
          permissions::PermissionRequestManager::DENY_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
}

INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
                         StorageAccessAPIOriginIsolationBrowserTest,
                         testing::Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param ? "origin_keyed" : "site_keyed";
                         });

INSTANTIATE_TEST_SUITE_P(
    /*no prefix*/,
    StorageAccessAPIStorageBrowserTest,
    testing::Combine(testing::Values(TestType::kFrame, TestType::kWorker),
                     testing::Bool()  // IsStoragePartitioned
                     ),
    /*name_generator=*/
    [](const testing::TestParamInfo<std::tuple<TestType, bool>>& info) {
      std::string name = base::NumberToString(info.index);
      switch (std::get<0>(info.param)) {
        case TestType::kFrame:
          name += "_frame";
          break;
        case TestType::kWorker:
          name += "_worker";
          break;
      }
      if (std::get<1>(info.param)) {
        name += "_partitioned";
      } else {
        name += "_unpartitioned";
      }
      return name;
    });

class StorageAccessAPIWithFirstPartySetsBrowserTest
    : public StorageAccessAPIBaseBrowserTest {
 public:
  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
    std::vector<base::test::FeatureRefAndParams> enabled =
        StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
    enabled.push_back(
        {content_settings::features::kStorageAccessAPIRelatedWebsiteSets, {}});
    return enabled;
  }

  void SetUpOnMainThread() override {
    StorageAccessAPIBaseBrowserTest::SetUpOnMainThread();
    // Explicitly enable Related Website Sets (formerly First Party Sets).
    browser()->GetProfile()->GetPrefs()->SetBoolean(
        prefs::kPrivacySandboxRelatedWebsiteSetsEnabled, true);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    StorageAccessAPIBaseBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(
        network::switches::kUseRelatedWebsiteSet,
        base::StrCat({R"({"primary": "https://)", kHostA,
                      R"(", "associatedSites": ["https://)", kHostB, R"("])",
                      R"(, "serviceSites": ["https://)", kHostD, R"("]})"}));
  }
};

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       StorageAccessWithFirstPartySetsDevToolsIssue) {
  SetBlockThirdPartyCookies(true);

  // Note: kHostA and kHostB are considered same-party due to the use of
  // `network::switches::kUseRelatedWebsiteSet`.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  content::TestDevToolsProtocolClient devtools_client;
  devtools_client.AttachToFrameTreeHost(ChildFrameAt(GetPrimaryMainFrame(), 0));
  devtools_client.SendCommandSync("Audits.enable");
  devtools_client.ClearNotifications();

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  auto matcher = [](const base::DictValue& params) {
    const std::string* maybe_issue_code =
        params.FindStringByDottedPath("issue.code");
    if (!maybe_issue_code || *maybe_issue_code != "DeprecationIssue") {
      return false;
    }
    const std::string* maybe_type = params.FindStringByDottedPath(
        "issue.details.deprecationIssueDetails.type");
    return maybe_type && *maybe_type == "RelatedWebsiteSets";
  };

  base::DictValue notification = devtools_client.WaitForMatchingNotification(
      "Audits.issueAdded", base::BindRepeating(matcher));

  // Verify that the issue is reported again when the permission is reused.
  NavigateFrameTo(EchoCookiesURL(kHostB));

  devtools_client.DetachProtocolClient();
  devtools_client.AttachToFrameTreeHost(ChildFrameAt(GetPrimaryMainFrame(), 0));
  devtools_client.SendCommandSync("Audits.enable");
  devtools_client.ClearNotifications();

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  notification = devtools_client.WaitForMatchingNotification(
      "Audits.issueAdded", base::BindRepeating(matcher));

  devtools_client.DetachProtocolClient();
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       Permission_AutograntedWithinFirstPartySet) {
  // Note: kHostA and kHostB are considered same-party due to the use of
  // `network::switches::kUseFirstPartySet`.
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       Permission_PromptOrDenyUnderServiceDomain) {
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  EnsureUserInteractionOn(kHostA);
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostD);
  NavigateFrameTo(EchoCookiesURL(kHostA));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // The promise should be rejected; `kHostD` is a service domain.
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);

  NavigateFrameTo(EchoCookiesURL(kHostA));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // Ensure that the denied state is not exposed to developers, per the spec.
  EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
}

IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIWithFirstPartySetsBrowserTest,
    Permission_AutograntedForServiceDomainWithExistingGrant) {
  SetBlockThirdPartyCookies(true);

  // Manually create a grant for the service site. Other test cases show that
  // the service site cannot create this grant on its own, but such a grant can
  // be created via other APIs (namely `document.requestStorageAccessFor`).
  content_settings::ContentSettingConstraints constraints;
  constraints.set_lifetime(base::Days(30));
  constraints.set_session_model(content_settings::mojom::SessionModel::DURABLE);
  constraints.set_decided_by_related_website_sets(true);
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetContentSettingDefaultScope(GetURL(kHostD), GetURL(kHostA),
                                      ContentSettingsType::STORAGE_ACCESS,
                                      CONTENT_SETTING_ALLOW, constraints);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostD));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostD), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostD), CookieBundle("cross-site=d.test"));

  EXPECT_EQ(QueryPermission(GetFrame()), "granted");
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       Permission_AutodeniedOutsideFirstPartySet_Overridden) {
  // Note: kHostA and kHostC are considered cross-party, since kHostA's set does
  // not include kHostC.
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostC);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostC));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostC), CookieBundle("cross-site=c.test"));
}

IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIWithFirstPartySetsBrowserTest,
    Permission_AutodeniedInsideFirstPartySet_WithoutInteraction) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()",
                               content::EXECUTE_SCRIPT_NO_USER_GESTURE));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       PRE_PermissionGrantsRestoredAfterRestart) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  ASSERT_EQ("granted", QueryPermission(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       PermissionGrantsRestoredAfterRestart) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ("granted", QueryPermission(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       Permission_GrantedForServiceDomain) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostD));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostD), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // `kHostD` is a service domain, but is not the top-level site, so the request
  // should be auto-granted.
  EXPECT_TRUE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                       SameSite_NoRegression) {
  // Note: kHostA and kHostB are considered same-party due to the use of
  // `network::switches::kUseFirstPartySet`. But they should not be "same-site",
  // so SameSite=Lax and SameSite=Strict should still block cookie access.
  ASSERT_TRUE(
      SetCookie(browser()->GetProfile(), GetURL(kHostB),
                "samesitelax=1; SameSite=Lax; Secure",
                net::CookieOptions::SameSiteCookieContext::MakeInclusive()));
  ASSERT_TRUE(
      SetCookie(browser()->GetProfile(), GetURL(kHostB),
                "samesitestrict=1; SameSite=Strict; Secure",
                net::CookieOptions::SameSiteCookieContext::MakeInclusive()));
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostB), "cross-site=b.test");
}

class StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest
    : public StorageAccessAPIBaseBrowserTest {
 public:
  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
    std::vector<base::test::FeatureRefAndParams> enabled =
        StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
    enabled.push_back(
        {content_settings::features::kStorageAccessAPIRelatedWebsiteSets, {}});
    return enabled;
  }

  StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest() {
    StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(5);
  }
};

// Validate that when auto-deny-outside-fps is disabled (but auto-grant is
// enabled), implicit grants still work.
IN_PROC_BROWSER_TEST_F(
    StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest,
    ImplicitGrants) {
  // Note: kHostA and kHostC are considered cross-party, since kHostA's set does
  // not include kHostC.
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostC));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // kHostC can request storage access, due to implicit grants.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  NavigateToPageWithFrame(kHostB);
  NavigateFrameTo(EchoCookiesURL(kHostC));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       RequestStorageAccess_CoexistsWithPartitionedCookies) {
  SetBlockThirdPartyCookies(true);

  SetPartitionedCookieInContext(/*top_level_host=*/kHostA,
                                /*embedded_host=*/kHostB);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB),
            CookieBundleWithContent("cross-site=b.test(partitioned)"));
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB),
            CookieBundle("cross-site=b.test; cross-site=b.test(partitioned)"));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       EnsureOnePromptDenialSuffices) {
  SetBlockThirdPartyCookies(true);
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  {
    // The first request should show a prompt, which is denied.
    permissions::PermissionRequestObserver pre_observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    ASSERT_FALSE(pre_observer.request_shown());
    ASSERT_FALSE(
        content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
    ASSERT_TRUE(pre_observer.request_shown());
    ASSERT_EQ(prompt_factory()->TotalRequestCount(), 1);
  }
  {
    // However, subsequent requests should not re-prompt.
    permissions::PermissionRequestObserver post_observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    // Validate that there's no stale data.
    ASSERT_FALSE(post_observer.request_shown());

    EXPECT_FALSE(
        content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
    // Verify no prompt was shown after the first one was already denied.
    EXPECT_FALSE(post_observer.request_shown());
    EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);
  }
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       DismissalAllowsFuturePrompts) {
  SetBlockThirdPartyCookies(true);
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DISMISS);

  {
    // The first request should show a prompt, which is dismissed.
    permissions::PermissionRequestObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    EXPECT_FALSE(
        content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
    ASSERT_TRUE(observer.request_shown());
    EXPECT_EQ(false,
              content::EvalJs(GetFrame(), "document.hasStorageAccess()"));
    ASSERT_EQ(prompt_factory()->TotalRequestCount(), 1);
  }

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  {
    // However, subsequent requests should be able to re-prompt.
    permissions::PermissionRequestObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents());

    EXPECT_TRUE(
        storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

    // Verify a prompt was shown.
    EXPECT_TRUE(observer.request_shown());
    EXPECT_EQ(prompt_factory()->TotalRequestCount(), 2);
  }
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       TopLevelUserInteractionRequired) {
  SetBlockThirdPartyCookies(true);

  // The test fixture pre-seeds kHostB with top-level user interaction, but not
  // the other hosts. We intentionally use kHostA as the embed, since it has not
  // been seeded with a top-level user interaction.

  NavigateToPageWithFrame(kHostB);
  NavigateFrameTo(EchoCookiesURL(kHostA));

  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent);

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostA), kNoCookies);

  // If kHostA has a top-level interaction, it can request storage access. The
  // user interaction should be tracked by site, not origin.
  EnsureUserInteractionOn(kHostASubdomain);
  NavigateToPageWithFrame(kHostB);
  NavigateFrameTo(EchoCookiesURL(kHostA));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostA), CookieBundle("cross-site=a.test"));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                       IncognitoDoesntUseRegularInteractionsOrPermission) {
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Even though there was previous interaction in the regular profile, requests
  // made by incognito profiles should be denied, due to the top-level user
  // interaction requirement.
  BrowserWindowInterface* incognito_browser = CreateBrowserWindow(
      BrowserWindowCreateParams(browser()->GetProfile()->GetPrimaryOTRProfile(
                                    /*create_if_needed=*/true),
                                /*from_user_gesture=*/true));

  NavigateToURLWithDisposition(incognito_browser,
                               https_server().GetURL(kHostA, "/iframe.html"),
                               WindowOpenDisposition::NEW_FOREGROUND_TAB,
                               ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  NavigateFrameTo(EchoCookiesURL(kHostB), incognito_browser);

  EXPECT_FALSE(content::ExecJs(GetFrame(incognito_browser),
                               "document.requestStorageAccess()"));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, IncognitoCanUseAPI) {
  BrowserWindowInterface* incognito_browser = CreateBrowserWindow(
      BrowserWindowCreateParams(browser()->GetProfile()->GetPrimaryOTRProfile(
                                    /*create_if_needed=*/true),
                                /*from_user_gesture=*/true));

  NavigateToURLWithDisposition(incognito_browser,
                               https_server().GetURL(kHostA, "/empty.html"),
                               WindowOpenDisposition::NEW_FOREGROUND_TAB,
                               ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);

  // Even in an incognito profile, it's possible to use the Storage Access API
  // if all the requirements are satisfied.

  EnsureUserInteractionOn(kHostB, incognito_browser);

  NavigateToPageWithFrame(kHostA, incognito_browser);
  NavigateFrameTo(EchoCookiesURL(kHostB), incognito_browser);

  std::unique_ptr<permissions::MockPermissionPromptFactory>
      incognito_prompt_factory = MakePromptFactory(incognito_browser);
  incognito_prompt_factory->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(
      GetFrame(incognito_browser)));
}

class StorageAccessAPIWithImplicitGrantsBrowserTest
    : public StorageAccessAPIBaseBrowserTest {
 public:
  StorageAccessAPIWithImplicitGrantsBrowserTest() {
    StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(2);
  }

 protected:
  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
    return {};
  }
};

IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithImplicitGrantsBrowserTest,
                       ImplicitGrantsAllowAccess) {
  base::HistogramTester histogram_tester;
  SetBlockThirdPartyCookies(true);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  // Access should be allowed by the first implicit grant.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  histogram_tester.ExpectUniqueSample(kGrantIsImplicitHistogram,
                                      /*sample=*/true, 1);

  NavigateToPageWithFrame(kHostC);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  // Access should be allowed by the second implicit grant.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  histogram_tester.ExpectUniqueSample(kGrantIsImplicitHistogram,
                                      /*sample=*/true, 2);

  NavigateToPageWithFrame(kHostD);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  // Access is denied since kHostB has already exhausted both of its implicit
  // grants, and we've set the prompt_factory to always deny.
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));

  NavigateToPageWithFrame(kHostB);
  NavigateFrameTo(EchoCookiesURL(kHostA));
  // Other embeds can still obtain access via their own implicit grants.
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  histogram_tester.ExpectUniqueSample(kGrantIsImplicitHistogram,
                                      /*sample=*/true, 3);
}

// Tests to verify that when 3p cookie is allowed, the embedded iframe can
// access cookie without requesting, and no prompt is shown if the iframe makes
// the request.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBaseBrowserTest, AllowedWhenUnblocked) {
  SetBlockThirdPartyCookies(false);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB),
            CookieBundleWithContent("cross-site=b.test"));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DISMISS);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIBaseBrowserTest, AllowedByUserBypass) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);

  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  // Enable UserBypass on hostA as top-level.
  CookieSettingsFactory::GetForProfile(browser()->GetProfile())
      ->SetCookieSettingForUserBypass(GetURL(kHostA));

  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB),
            CookieBundleWithContent("cross-site=b.test"));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DISMISS);
  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
}

// Validate that if third-party cookies are allowed but the permission is
// denied, requestStorageAccess beyond cookies succeeds.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBaseBrowserTest,
                       BeyondCookies_WithCookiesWithoutPermission) {
  SetBlockThirdPartyCookies(false);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame(
      GetFrame()));
}

// Validate that if third-party cookies are allowed and the permission is
// allowed, requestStorageAccess beyond cookies succeeds.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBaseBrowserTest,
                       BeyondCookies_WithCookiesWithPermission) {
  SetBlockThirdPartyCookies(false);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame(
      GetFrame()));
}

// Validate that if third-party cookies are allowed but the permission is
// denied, requestStorageAccess does grant local storage access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBaseBrowserTest,
                       BeyondCookies_LocalStorageWith3PCAndNoPermission) {
  // Allow 3PC and deny storage access requests.
  SetBlockThirdPartyCookies(false);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  // Set first-party local storage on site b.
  NavigateToPageWithFrame(kHostB);
  EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(),
                              "window.localStorage.setItem('test', 'a');"
                              "window.sessionStorage.setItem('test', 'b');"));

  // Test if first-party data is available on site a.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/empty.html");
  EXPECT_EQ(true, content::EvalJs(
                      GetFrame(),
                      "document.requestStorageAccess("
                      "  {localStorage: true, sessionStorage: true}"
                      ").then("
                      "  (handle) => {"
                      "    return handle.localStorage.getItem('test') == 'a' &&"
                      "           handle.sessionStorage.getItem('test') == 'b';"
                      "  },"
                      "  () => false"
                      ");"));
}

// Validate that if third-party cookies are allowed but the permission is
// denied, requestStorageAccess does grant unpartitioned blob URL access.
IN_PROC_BROWSER_TEST_F(StorageAccessAPIBaseBrowserTest,
                       BeyondCookies_BlobUrlWith3PCAndNoPermission) {
  // Allow 3PC and deny storage access requests.
  SetBlockThirdPartyCookies(false);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::DENY_ALL);

  // Navigate to a.com as a third-party context and then verify that handle can
  // be used to create a blob URL accessible from both a first-party and
  // third-party context.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(kHostB, "/iframe.html");
  NavigateNestedFrameTo(kHostA, "/empty.html");

  std::string blob_url = content::EvalJs(GetNestedFrame(),
                                         R"(
                (async () => {
                  const handle = await document.requestStorageAccess(
                      {createObjectURL: true});
                  const blob_url = handle.createObjectURL(new Blob(['test']));
                  return blob_url;
                })();
                )")
                             .ExtractString();
  ASSERT_THAT(blob_url, testing::StartsWith("blob:"));

  std::string fetch_blob_url_js = content::JsReplace("fetch($1)", blob_url);

  EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(), fetch_blob_url_js));

  EXPECT_TRUE(content::ExecJs(GetNestedFrame(), fetch_blob_url_js));

  EXPECT_TRUE(content::ExecJs(GetNestedFrame(), content::JsReplace(
                                                    R"(
                (async () => {
                  const handle = await document.requestStorageAccess(
                      {revokeObjectURL: true});
                  handle.revokeObjectURL($1);
                })();
                )",
                                                    blob_url)));

  EXPECT_FALSE(content::ExecJs(GetPrimaryMainFrame(), fetch_blob_url_js));
  EXPECT_FALSE(content::ExecJs(GetNestedFrame(), fetch_blob_url_js));
}

class StorageAccessAPIAutograntsWithFedCMBrowserTest
    : public StorageAccessAPIBaseBrowserTest {
 public:
  void GrantFedCMPermission() {
    const url::Origin rp_embedder =
        url::Origin::Create(GetURL(kHostASubdomain));
    const url::Origin rp_requester = url::Origin::Create(GetURL(kHostC));
    const url::Origin idp = url::Origin::Create(GetURL(kHostB));
    constexpr char account_id[] = "my account";

    FederatedIdentityPermissionContextFactory::GetForProfile(
        browser()->GetProfile())
        ->GrantSharingPermission(rp_requester, rp_embedder, idp, account_id);
  }

  void NavigateToPageWithPermissionsPolicyIframes(
      std::initializer_list<const std::string_view> hosts_list) {
    base::span hosts(hosts_list);
    ASSERT_FALSE(hosts.empty());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(), https_server().GetURL(
                       hosts[0], base::StrCat({
                                     "/cross_site_iframe_factory.html?",
                                     hosts[0],
                                     "(",
                                     MakeNonRootFrameNodes(hosts.subspan<1>()),
                                     ")",
                                 }))));
  }

 private:
  std::string MakeNonRootFrameNodes(base::span<const std::string_view> hosts) {
    std::string tree;
    for (const auto& host : hosts) {
      base::StrAppend(&tree, {host, "{allow-identity-credentials-get}("});
    }
    for (const auto& host : hosts) {
      (void)host;
      base::StrAppend(&tree, {")"});
    }
    return tree;
  }
};

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       FedCMGrants_RequiresPermissionPolicy) {
  SetBlockThirdPartyCookies(true);
  GrantFedCMPermission();
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::AutoResponseType::DENY_ALL);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       FedCMGrants_PreventSilentAccess) {
  SetBlockThirdPartyCookies(true);
  GrantFedCMPermission();
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::AutoResponseType::DENY_ALL);

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB});
  NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0");
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(),
                              "navigator.credentials.preventSilentAccess()"));

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       FedCMGrants_PreventSilentAccess_AfterAutogrant) {
  SetBlockThirdPartyCookies(true);
  GrantFedCMPermission();
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::AutoResponseType::DENY_ALL);

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB});
  NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0");
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(
      GetFrame(), /*omit_user_gesture=*/true));
  EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(),
                              "navigator.credentials.preventSilentAccess()"));

  NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0");
  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       FedCMGrants_PermissionPolicyHeaderIgnored) {
  SetBlockThirdPartyCookies(true);
  GrantFedCMPermission();
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::AutoResponseType::DENY_ALL);

  constexpr char kPageWithPermissionPolicyHeader[] = "/page_with_header.html";
  content::URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
      [&](content::URLLoaderInterceptor::RequestParams* params) {
        if (params->url_request.url.GetPath() !=
            kPageWithPermissionPolicyHeader) {
          return false;
        }

        CHECK_EQ(params->url_request.url.host(), kHostB);
        content::URLLoaderInterceptor::WriteResponse(
            "HTTP/1.1 200 OK\n"
            "Content-type: text/html\n"
            "Permissions-Policy: identity-credentials-get=(self "
            "\"https://b.com\")\n"
            "\n",
            /*body=*/"", params->client.get());
        return true;
      }));

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(
      GURL(base::StrCat({"https://", kHostB, kPageWithPermissionPolicyHeader})),
      browser());
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       FedCMGrantsAllowCookieAccessViaSAA) {
  SetBlockThirdPartyCookies(true);
  GrantFedCMPermission();

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB});
  NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0");
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));

  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(
      GetFrame(), /*omit_user_gesture=*/true));
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 0);
  EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB});
  NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0");
  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       FedCMGrantsAllowCookieAccess_NestedFrame) {
  SetBlockThirdPartyCookies(true);
  GrantFedCMPermission();

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostC, kHostB});
  EXPECT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
      GetNestedFrame(), EchoCookiesURL(kHostB)));
  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));

  EXPECT_TRUE(
      storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame()));
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 0);
  EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB),
            CookieBundle("cross-site=b.test"));

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostC, kHostB});
  EXPECT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
      GetNestedFrame(), EchoCookiesURL(kHostB)));
  EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB),
            kNoCookiesWithContent);
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
}

class StorageAccessHeadersBrowserTest
    : public StorageAccessAPIBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
    if (enable_experimental_features()) {
      return {
          // TODO(crbug.com/382291442): Remove below two once permissions
          // policies are launched.
          {{network::features::kPopulatePermissionsPolicyOnRequest}, {}},
          {{network::features::kStorageAccessHeadersRespectPermissionsPolicy},
           {}},
      };
    }
    return {};
  }

  bool enable_experimental_features() const { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(, StorageAccessHeadersBrowserTest, testing::Bool());

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest, RetryHeader) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostA);

  // Pre-seed with a <A, B> permission grant.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Now attempt to use that permission grant for a B subresource fetched by an
  // A document, without invoking the Storage Access API.
  NavigateToPage(kHostA, "/empty.html");
  EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath),
              HeadersAre(UnorderedElementsAre(
                  Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"),
                  Pair(kSecFetchStorageAccess, "active"))));
  EXPECT_EQ(retry_path_fetch_count_, 2);
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RetryHeader_WithBlockingPermissionsPolicy) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostA);

  // Pre-seed with a <A, B> permission grant.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Now attempt to use that permission grant for a B subresource fetched by an
  // A document, without invoking the Storage Access API.
  NavigateToPageWithFrameBlockingStorageAccessPermissionsPolicy(kHostA);
  EXPECT_THAT(
      ContentFromFetch(ChildFrameAt(GetPrimaryMainFrame(), 0), kHostB,
                       kRetryPath),
      HeadersAre(Conditional(
          enable_experimental_features(),
          UnorderedElementsAre(Pair(net::HttpRequestHeaders::kCookie,
                                    kHeaderNotProvidedSentinel),
                               Pair(kSecFetchStorageAccess, "none")),
          UnorderedElementsAre(
              Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"),
              Pair(kSecFetchStorageAccess, "active")))));
  EXPECT_EQ(retry_path_fetch_count_, enable_experimental_features() ? 1 : 2);
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest, RetryHeader_Wildcard) {
  SetBlockThirdPartyCookies(true);
  set_retry_allowed_origin("*");

  // Pre-seed with a <A, B> permission grant.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Now attempt to use that permission grant for a B subresource fetched by an
  // A document, without invoking the Storage Access API.
  NavigateToPage(kHostA, "/empty.html");
  EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath),
              HeadersAre(UnorderedElementsAre(
                  Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"),
                  Pair(kSecFetchStorageAccess, "active"))));
  EXPECT_EQ(retry_path_fetch_count_, 2);
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RetryHeader_NoopWithoutGrant) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostA);

  // Note: we do *not* pre-seed with a <A, B> permission grant.

  // Now attempt to use the `retry` header for a B subresource fetched by an A
  // document, without invoking the Storage Access API.
  NavigateToPage(kHostA, "/empty.html");
  EXPECT_THAT(
      ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath),
      HeadersAre(UnorderedElementsAre(
          Pair(net::HttpRequestHeaders::kCookie, kHeaderNotProvidedSentinel),
          Pair(kSecFetchStorageAccess, "none"))));
  EXPECT_EQ(retry_path_fetch_count_, 1);
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RetryHeader_ABAContext) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostB);

  // Attempt to get Storage Access for an A subresource fetched by a B document
  // (embedded under an A top-level document), without invoking the Storage
  // Access API. There's no privacy boundary here (since the top-level site and
  // the subresource fetch are for the same site); there's just a security
  // boundary, which the header opt-in disables.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostB, "/empty.html"));
  EXPECT_THAT(ContentFromFetch(GetFrame(), kHostA, kRetryPath),
              HeadersAre(UnorderedElementsAre(
                  Pair(net::HttpRequestHeaders::kCookie, "cross-site=a.test"),
                  Pair(kSecFetchStorageAccess, "active"))));
  EXPECT_EQ(retry_path_fetch_count_, 2);

  EXPECT_THAT(
      ObservedRequestHeaders(),
      IsSupersetOf<PathAndHeaderMapMatchers>({
          // The top-level page and the `fetch` call both omit the header.
          Pair("/iframe.html",
               AllOf(Contains(Pair("Host", StartsWith(kHostA))),
                     Not(Contains(Key(kSecFetchStorageAccess))))),
          Pair("/empty.html",
               AllOf(Contains(Pair("Host", StartsWith(kHostB))),
                     Not(Contains(Key(kSecFetchStorageAccess))))),
          // The iframe's subresource fetch includes the header.
          Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({
                               Pair("Host", StartsWith(kHostA)),
                               Pair(kSecFetchStorageAccess, "inactive"),
                           })),
          Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({
                               Pair("Host", StartsWith(kHostA)),
                               Pair(kSecFetchStorageAccess, "active"),
                           })),
      }));
}

// Regression test for https://crbug.com/352722603. Same as
// `RetryHeader_ABAContext`, except that the iframe calls
// `document.requestStorageAccess()` before issuing the fetch.
IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RetryHeader_ABAContext_WithIrrelevantApiCall) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostB);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostB, "/empty.html"));

  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  EXPECT_THAT(ContentFromFetch(GetFrame(), kHostA, kRetryPath),
              HeadersAre(UnorderedElementsAre(
                  Pair(net::HttpRequestHeaders::kCookie, "cross-site=a.test"),
                  Pair(kSecFetchStorageAccess, "active"))));
  EXPECT_EQ(retry_path_fetch_count_, 2);

  EXPECT_THAT(
      ObservedRequestHeaders(),
      IsSupersetOf<PathAndHeaderMapMatchers>({
          // The top-level page and the `fetch` call both omit the header.
          Pair("/iframe.html",
               AllOf(Contains(Pair("Host", StartsWith(kHostA))),
                     Not(Contains(Key(kSecFetchStorageAccess))))),
          Pair("/empty.html",
               AllOf(Contains(Pair("Host", StartsWith(kHostB))),
                     Not(Contains(Key(kSecFetchStorageAccess))))),
          // The iframe's subresource fetch includes the header.
          Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({
                               Pair("Host", StartsWith(kHostA)),
                               Pair(kSecFetchStorageAccess, "inactive"),
                           })),
          Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({
                               Pair("Host", StartsWith(kHostA)),
                               Pair(kSecFetchStorageAccess, "active"),
                           })),
      }));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest, LoadHeader) {
  SetBlockThirdPartyCookies(true);

  // Pre-seed with a <A, B> permission grant.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Now attempt to use that permission grant for a cross-site iframe, without
  // invoking the Storage Access API.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostB, "/set-header?Activate-Storage-Access: load"));
  // No need to request storage access, because we already have it.
  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       LoadHeader_WithBlockingPermissionsPolicy) {
  SetBlockThirdPartyCookies(true);

  // Pre-seed with a <A, B> permission grant.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(EchoCookiesURL(kHostB));
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Now attempt to use that permission grant for a cross-site iframe, without
  // invoking the Storage Access API.
  NavigateToPageWithFrameBlockingStorageAccessPermissionsPolicy(kHostA);
  NavigateFrameTo(GetURL(kHostB, "/set-header?Activate-Storage-Access: load"),
                  nullptr, "child-0");
  // No storage access was activated because permissions policy blocked it.
  EXPECT_NE(storage::test::HasStorageAccessForFrame(GetFrame()),
            enable_experimental_features());
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       LoadHeader_NoopWithoutGrant) {
  SetBlockThirdPartyCookies(true);

  // Note: we do *not* pre-seed with a <A, B> permission grant.

  // Now attempt to get storage access in a cross-site iframe, without invoking
  // the Storage Access API.
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostB, "/set-header?Activate-Storage-Access: load"));
  // Permission was never granted, so we don't have storage access.
  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeadersFirstParty) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostA));
  EXPECT_THAT(ObservedRequestHeaders(),
              Each(Pair(_, Not(Contains(Key(kSecFetchStorageAccess))))));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeadersCredentialsBlocked) {
  SetBlockThirdPartyCookies(true);
  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostB));
  ASSERT_TRUE(content::ExecJs(
      GetFrame(), content::JsReplace("fetch($1, {'credentials': 'omit'})",
                                     GetURL(kHostB))));
  EXPECT_THAT(
      ObservedRequestHeaders(),
      IsSupersetOf<PathAndHeaderMapMatchers>({
          // The top-level page and the `fetch` call both omit the header.
          Pair("/iframe.html", Not(Contains(Key(kSecFetchStorageAccess)))),
          Pair("/", AllOf(Contains(Pair("Host", StartsWith(kHostB))),
                          Not(Contains(Key(kSecFetchStorageAccess))))),
          // The iframe subresource fetch includes the header.
          Pair("/", IsSupersetOf<HeaderMapMatchers>({
                        Pair("Host", StartsWith(kHostB)),
                        Pair(kSecFetchStorageAccess, "none"),
                    })),
      }));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest, RequestHeadersNone) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA);
  NavigateFrameTo(GetURL(kHostB));
  EXPECT_THAT(ObservedRequestHeaders(),
              Contains(Pair("/", IsSupersetOf<HeaderMapMatchers>({
                                     Pair("Host", StartsWith(kHostB)),
                                     Pair(kSecFetchStorageAccess, "none"),
                                 }))));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeadersCredentiallessFrame) {
  SetBlockThirdPartyCookies(true);

  NavigateToPageWithFrame(kHostA, /*browser_ptr=*/nullptr,
                          /*credentialless=*/true);
  NavigateFrameTo(GetURL(kHostB));
  EXPECT_THAT(ObservedRequestHeaders(),
              Contains(Pair("/", IsSupersetOf<HeaderMapMatchers>({
                                     Pair("Host", StartsWith(kHostB)),
                                     Pair(kSecFetchStorageAccess, "none"),
                                 }))));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeadersInactive) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  // Header will be 'none' first time we navigate to `kHostB` since the
  // permission grant does not exist yet.
  NavigateFrameTo(GetURL(kHostB));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Top-level subresource fetches also include the "inactive" header.
  EXPECT_EQ(CookiesFromFetch(GetPrimaryMainFrame(), kHostB), "None");
  EXPECT_THAT(ObservedRequestHeaders(),
              IsSupersetOf<PathAndHeaderMapMatchers>({
                  Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))),
                  Pair("/echocookieswithcors",
                       Contains(Pair(kSecFetchStorageAccess, "inactive"))),
              }));

  // Subsequent navigation should be `inactive`.
  NavigateFrameTo(GetURL(kHostB));

  EXPECT_THAT(
      ObservedRequestHeaders(),
      Contains(Pair("/", Contains(Pair(kSecFetchStorageAccess, "inactive")))));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       NonCookieStorage_Subresource) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  // Header will be 'none' first time we navigate to `kHostB` since the
  // permission grant does not exist yet.
  NavigateFrameTo(GetURL(kHostB));
  ASSERT_TRUE(content::ExecJs(
      GetFrame(), "document.requestStorageAccess({'localStorage': true})"));

  // Subresource fetches from the embed include the "inactive" header.
  EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostB), "None");
  EXPECT_THAT(ObservedRequestHeaders(),
              IsSupersetOf<PathAndHeaderMapMatchers>({
                  Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))),
                  Pair("/echocookieswithcors",
                       Contains(Pair(kSecFetchStorageAccess, "inactive"))),
              }));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeaderRetryToActive) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostA);
  EnsureUserInteractionOn(kHostB);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  // Header will be 'none' first time we navigate to `kHostB` since the
  // permission grant does not exist yet.
  NavigateFrameTo(GetURL(kHostB));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Top-level subresource fetches can benefit from the permission.
  EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath),
              HeadersAre(UnorderedElementsAre(
                  Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"),
                  Pair(kSecFetchStorageAccess, "active"))));

  // Next navigation would be inactive, but with the `kRetryPath` we end up
  // opting into `storage-access`, making it `active`.
  NavigateFrameTo(GetURL(kHostB, kRetryPath));
  // The `fetch` call and the navigation both sent the "active" header,
  // eventually.
  EXPECT_THAT(ObservedRequestHeaders(),
              Contains(Pair(kRetryPath,
                            Contains(Pair(kSecFetchStorageAccess, "active"))))
                  .Times(2));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeaderSameOriginRedirect) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  // Header will be 'none' first time we navigate to `kHostB` since the
  // permission grant does not exist yet.
  GURL dest_url = GetURL(kHostB);
  NavigateFrameTo(dest_url);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Cause the frame to navigate itself via a same-origin redirect.
  EXPECT_TRUE(
      content::NavigateToURLFromRenderer(GetFrame(),
                                         /*url=*/
                                         RedirectViaHosts({kHostB}, dest_url),
                                         /*expected_commit_url=*/dest_url));
  // Since the frame navigated via a same-origin redirect, expect
  // `Sec-Fetch-Storage-Access` to reflect a preserved `active` storage access
  // status.
  EXPECT_THAT(ObservedRequestHeaders(),
              IsSupersetOf<PathAndHeaderMapMatchers>({
                  Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))),
                  Pair(ServerRedirectPath(dest_url),
                       Contains(Pair(kSecFetchStorageAccess, "active"))),
                  Pair("/", Contains(Pair(kSecFetchStorageAccess, "active"))),
              }));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       RequestHeaderCrossOriginRedirect) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  // Header will be 'none' first time we navigate to `kHostB` since the
  // permission grant does not exist yet.
  GURL dest_url = GetURL(kHostB);
  NavigateFrameTo(dest_url);
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  // Cause the frame to navigate itself via a cross-origin redirect.
  EXPECT_TRUE(
      content::NavigateToURLFromRenderer(GetFrame(),
                                         /*url=*/
                                         RedirectViaHosts({kHostA}, dest_url),
                                         /*expected_commit_url=*/dest_url));
  // Since the frame navigated via a cross-origin redirect, expect
  // `Sec-Fetch-Storage-Access` to no longer be `active`.
  EXPECT_THAT(ObservedRequestHeaders(),
              IsSupersetOf<PathAndHeaderMapMatchers>({
                  Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))),
                  Pair(ServerRedirectPath(dest_url),
                       Not(Contains(Key(kSecFetchStorageAccess)))),
                  Pair("/", Contains(Pair(kSecFetchStorageAccess, "inactive"))),
              }));
}

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersBrowserTest,
                       TopFrameRedirectToFirstPartyPage) {
  SetBlockThirdPartyCookies(true);
  EnsureUserInteractionOn(kHostB);
  prompt_factory()->set_response_type(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  NavigateToPageWithFrame(kHostA);
  // Header will be 'none' first time we navigate to `kHostB` since the
  // permission grant does not exist yet.
  NavigateFrameTo(GetURL(kHostB));
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));

  GURL redirect_dest_url = GetURL(kHostB, "/empty.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), RedirectViaHosts({kHostB}, redirect_dest_url)));

  // Since kHostB is top-level for the redirect, expect the
  // `Sec-Fetch-Storage-Access` header is not included in the request.
  EXPECT_THAT(
      ObservedRequestHeaders(),
      IsSupersetOf<PathAndHeaderMapMatchers>({
          Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))),
          Pair(ServerRedirectPath(redirect_dest_url),
               Not(Contains(Key(kSecFetchStorageAccess)))),
          Pair("/empty.html", Not(Contains(Key(kSecFetchStorageAccess)))),
      }));
}

using StorageAccessHeadersWithThirdPartyCookiesBrowserTest =
    StorageAccessHeadersBrowserTest;

INSTANTIATE_TEST_SUITE_P(,
                         StorageAccessHeadersWithThirdPartyCookiesBrowserTest,
                         testing::Bool());

IN_PROC_BROWSER_TEST_P(StorageAccessHeadersWithThirdPartyCookiesBrowserTest,
                       RetryHeader_NoopWhenCookiesAllowed) {
  SetBlockThirdPartyCookies(false);
  SetRetryAllowedOriginFromHost(kHostA);

  // Note: we do *not* pre-seed with a <A, B> permission grant.

  // Now attempt to use the `retry` header for a B subresource fetched by an A
  // document, without invoking the Storage Access API.
  NavigateToPage(kHostA, "/empty.html");
  EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath),
              HeadersAre(UnorderedElementsAre(
                  Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"),
                  Pair(kSecFetchStorageAccess, "active"))));
  EXPECT_EQ(retry_path_fetch_count_, 1);
}

IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest,
                       RetryHeader) {
  SetBlockThirdPartyCookies(true);
  SetRetryAllowedOriginFromHost(kHostA);
  GrantFedCMPermission();

  NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB});
  NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0");
  ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(
      GetFrame(), /*omit_user_gesture=*/true));

  // Attempt to use the FedCM permission grant for a B subresource fetched by an
  // A document, without invoking the Storage Access API. Since there was no
  // `identity-credentials-get` permissions policy associated with this
  // particular opt-in, the FedCM grant should not be usable via the header.
  NavigateToPage(kHostA, "/empty.html");
  EXPECT_THAT(
      ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath),
      HeadersAre(UnorderedElementsAre(
          Pair(net::HttpRequestHeaders::kCookie, kHeaderNotProvidedSentinel),
          Pair(kSecFetchStorageAccess, "none"))));
  EXPECT_EQ(retry_path_fetch_count_, 1);
}

class StorageAccessAPIWindowOpenTestBase
    : public StorageAccessAPIBaseBrowserTest {
 public:
  void SetUpOnMainThread() override {
    StorageAccessAPIBaseBrowserTest::SetUpOnMainThread();
    SetBlockThirdPartyCookies(!Are3PCEnabled());
    SetFirstPartyData();
  }

  void TearDownOnMainThread() override {
    new_prompt_factory_.reset();
    StorageAccessAPIBaseBrowserTest::TearDownOnMainThread();
  }

 protected:
  virtual bool Are3PCEnabled() const = 0;

  virtual bool ArePermissionPromptsAccepted() const = 0;

  virtual std::string MainFrameHost() const = 0;

  void SetupPromptFactoryForNewWebContents(
      content::WebContents* new_web_contents) {
    new_prompt_factory_ =
        std::make_unique<permissions::MockPermissionPromptFactory>(
            permissions::PermissionRequestManager::FromWebContents(
                new_web_contents));
    new_prompt_factory_->set_response_type(
        ArePermissionPromptsAccepted()
            ? permissions::PermissionRequestManager::ACCEPT_ALL
            : permissions::PermissionRequestManager::DENY_ALL);
  }

  void ExpectNoStorageAccessGrants() {
    EXPECT_THAT(
        HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
            ->GetSettingsForOneType(ContentSettingsType::STORAGE_ACCESS),
        UnorderedElementsAre(kExpectedSettingDefault));
  }

  void ExpectOneStorageAccessGrantFor(std::string_view frame_host,
                                      std::string_view embedder_host) {
    std::vector<ContentSettingPatternSource> settings =
        HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
            ->GetSettingsForOneType(ContentSettingsType::STORAGE_ACCESS);
    for (ContentSettingPatternSource& setting : settings) {
      // We aren't trying to verify metadata so it's easier to clear it.
      setting.metadata = content_settings::RuleMetaData();
    }
    EXPECT_THAT(
        settings,
        UnorderedElementsAre(
            kExpectedSettingDefault,
            ContentSettingPatternSource(
                ContentSettingsPattern::FromURLToSchemefulSitePattern(
                    GetURL(frame_host)),
                ContentSettingsPattern::FromURLToSchemefulSitePattern(
                    GetURL(embedder_host)),
                content_settings::ContentSettingToValue(
                    ArePermissionPromptsAccepted() ? CONTENT_SETTING_ALLOW
                                                   : CONTENT_SETTING_BLOCK),
                content_settings::ProviderType::kPrefProvider,
                /*incognito=*/false)));
  }

  content::EvalJsResult DoesFrameHaveLocalStorageForHost(
      content::RenderFrameHost* frame,
      std::string_view host) {
    return content::EvalJs(
        frame, content::JsReplace("!!window.localStorage.getItem($1)", host));
  }

  content::EvalJsResult DoesFrameAllowRequestStorageAccessForHost(
      content::RenderFrameHost* frame,
      std::string_view host) {
    return content::EvalJs(
        frame,
        content::JsReplace("document.requestStorageAccess({"
                           "  localStorage: true,"
                           "  cookies: true,"
                           "}).then("
                           "  (handle) => !!handle &&"
                           "              !!handle.localStorage.getItem($1),"
                           "  () => false"
                           ")",
                           host));
  }

 private:
  void SetFirstPartyData() {
    NavigateToPage(kHostA, "/empty.html");
    EXPECT_TRUE(content::ExecJs(
        GetPrimaryMainFrame(),
        content::JsReplace("window.localStorage.setItem($1, $1);", kHostA)));
    NavigateToPage(kHostB, "/empty.html");
    EXPECT_TRUE(content::ExecJs(
        GetPrimaryMainFrame(),
        content::JsReplace("window.localStorage.setItem($1, $1);", kHostB)));
    NavigateToPage(kHostC, "/empty.html");
    EXPECT_TRUE(content::ExecJs(
        GetPrimaryMainFrame(),
        content::JsReplace("window.localStorage.setItem($1, $1);", kHostC)));
  }

  std::unique_ptr<permissions::MockPermissionPromptFactory> new_prompt_factory_;
};

class StorageAccessAPIWindowOpenMainFrameTest
    : public StorageAccessAPIWindowOpenTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool, std::string>> {
 protected:
  bool Are3PCEnabled() const override { return std::get<0>(GetParam()); }

  bool ArePermissionPromptsAccepted() const override {
    return std::get<1>(GetParam());
  }

  std::string MainFrameHost() const override { return std::get<2>(GetParam()); }

  bool IsCrossOriginToOpenerFrame() const { return kHostA != MainFrameHost(); }
};

// Opens a popup and checks that the RSA call within the main frame fails.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIWindowOpenMainFrameTest,
                       PopupRSAMainFrameTest) {
  // Navigate to site a and open popup of site b.
  NavigateToPage(kHostA, "/empty.html");
  // Wait for the page to load; ignoring any webUI web contents that are being
  // created with the browser.!
  content::CreateAndLoadWebContentsObserver new_tab_observer(
      1, base::BindRepeating([](content::WebContents* web_contents) {
        return !web_contents->GetWebUI();
      }));
  EXPECT_TRUE(content::ExecJs(
      browser()->tab_strip_model()->GetActiveWebContents(),
      content::JsReplace("window.open($1, '_blank', 'popup')",
                         EchoCookiesURL(MainFrameHost()).spec())));
  content::WebContents* popup_web_contents = new_tab_observer.Wait();
  SetupPromptFactoryForNewWebContents(popup_web_contents);

  // Expect first-party data for popup.
  ASSERT_EQ(!IsCrossOriginToOpenerFrame(),
            DoesFrameHaveLocalStorageForHost(
                popup_web_contents->GetPrimaryMainFrame(), kHostA));
  ASSERT_EQ(IsCrossOriginToOpenerFrame(),
            DoesFrameHaveLocalStorageForHost(
                popup_web_contents->GetPrimaryMainFrame(), kHostB));
  EXPECT_EQ(ReadCookiesAndContent(popup_web_contents->GetPrimaryMainFrame(),
                                  MainFrameHost()),
            CookieBundleWithContent("cross-site=" + MainFrameHost()));

  // Expect no custom content settings related to storage access.
  ExpectNoStorageAccessGrants();

  // Expect no handle returned for popup.
  EXPECT_EQ(false,
            DoesFrameAllowRequestStorageAccessForHost(
                popup_web_contents->GetPrimaryMainFrame(), MainFrameHost()));

  // Expect no custom content settings related to storage access.
  ExpectNoStorageAccessGrants();
}

INSTANTIATE_TEST_SUITE_P(
    /*no prefix*/,
    StorageAccessAPIWindowOpenMainFrameTest,
    testing::Combine(/*3pc_enabled=*/testing::Bool(),
                     /*permission_prompts_accepted=*/testing::Bool(),
                     /*main_frame_host=*/testing::Values(kHostA, kHostB)),
    [](const testing::TestParamInfo<std::tuple<bool, bool, std::string>>&
           info) {
      return base::StringPrintf(
          "%s_%s_main_%c",
          std::get<0>(info.param) ? "3PCEnabled" : "3PCDisabled",
          std::get<1>(info.param) ? "PermissionPromptsAccepted"
                                  : "PermissionPromptsDenied",
          std::get<2>(info.param)[0]);
    });

}  // namespace
