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

#include <atomic>

#include "base/path_service.h"
#include "base/strings/pattern.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/renderer.mojom.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.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/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/resource_load_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/base/features.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/scoped_mutually_exclusive_feature_list.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "url/gurl.h"

namespace content {
namespace {

class SplitCacheContentBrowserTestBase : public ContentBrowserTest {
 public:
  enum class Context { kMainFrame, kSameOriginFrame, kCrossOriginFrame };

  SplitCacheContentBrowserTestBase() = default;

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

  void SetUp() override {
    RenderWidgetHostImpl::DisableResizeAckCheckForTesting();
    ContentBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // Setup the server to allow serving separate sites, so we can perform
    // cross-process navigation.
    host_resolver()->AddRule("*", "127.0.0.1");

    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
        &SplitCacheContentBrowserTestBase::CachedScriptHandler,
        base::Unretained(this)));
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  std::unique_ptr<net::test_server::HttpResponse> CachedScriptHandler(
      const net::test_server::HttpRequest& request) {
    GURL absolute_url = embedded_test_server()->GetURL(request.relative_url);

    // Return a page that redirects to d.com/title1.html.
    if (absolute_url.GetPath() == "/redirect_to_d") {
      auto http_response =
          std::make_unique<net::test_server::BasicHttpResponse>();
      http_response->set_code(net::HTTP_SEE_OTHER);
      http_response->AddCustomHeader(
          "Location",
          embedded_test_server()->GetURL("d.com", "/title1.html").spec());
      return http_response;
    }

    // Return valid cacheable script.
    if (absolute_url.GetPath() == "/script") {
      auto http_response =
          std::make_unique<net::test_server::BasicHttpResponse>();
      http_response->set_code(net::HTTP_OK);
      http_response->set_content("console.log(\"Hello World\");");
      http_response->set_content_type("application/javascript");
      http_response->AddCustomHeader("Cache-Control", "max-age=1000");
      return http_response;
    }

    // A basic cacheable worker that loads 3p.com/script
    if (absolute_url.GetPath() == "/worker.js") {
      auto http_response =
          std::make_unique<net::test_server::BasicHttpResponse>();
      http_response->set_code(net::HTTP_OK);

      GURL resource = GenURL("3p.com", "/script");
      // Self-terminate the worker just after loading the third party
      // script, so that the parent context doesn't need to wait for the
      // worker's termination when cleaning up the test. See
      // https://crbug.com/1104847 for more details.
      std::string content = base::StringPrintf("importScripts('%s');\nclose();",
                                               resource.spec().c_str());

      http_response->set_content(content);
      http_response->set_content_type("application/javascript");
      http_response->AddCustomHeader("Cache-Control", "max-age=100000");
      return http_response;
    }

    // Make the document resource cacheable.
    if (absolute_url.GetPath() == "/title1.html") {
      auto http_response =
          std::make_unique<net::test_server::BasicHttpResponse>();
      http_response->set_code(net::HTTP_OK);
      http_response->AddCustomHeader("Cache-Control", "max-age=100000");
      return http_response;
    }

    // A cacheable worker that loads a nested worker on an origin provided
    // as a query param.
    if (absolute_url.GetPath() == "/embedding_worker.js") {
      auto http_response =
          std::make_unique<net::test_server::BasicHttpResponse>();
      http_response->set_code(net::HTTP_OK);

      GURL resource =
          GenURL(base::StringPrintf("%s.com", absolute_url.GetQuery().c_str()),
                 "/worker.js");

      const char kLoadWorkerScript[] = "let w = new Worker('%s');";
      std::string content =
          base::StringPrintf(kLoadWorkerScript, resource.spec().c_str());

      http_response->set_content(content);
      http_response->set_content_type("application/javascript");
      http_response->AddCustomHeader("Cache-Control", "max-age=100000");
      return http_response;
    }

    return nullptr;
  }

 protected:
  // Creates and loads subframe, waits for load to stop, and then returns
  // subframe from the web contents frame tree.
  RenderFrameHost* CreateSubframe(const GURL& sub_frame) {
    EXPECT_TRUE(ExecJs(shell(), GetSubframeScript(sub_frame)));
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

    return static_cast<WebContentsImpl*>(shell()->web_contents())
        ->GetPrimaryFrameTree()
        .root()
        ->child_at(0)
        ->current_frame_host();
  }

  // Loads 3p.com/script on page |url|, optionally from |sub_frame| if it's
  // valid, and returns whether the script was cached or not.
  bool TestResourceLoad(const GURL& url, const GURL& sub_frame) {
    return TestResourceLoadHelper(url, sub_frame, GURL());
  }

  // Loads 3p.com/script on page |url| from |worker| and returns whether
  // the script was cached or not.
  bool TestResourceLoadFromDedicatedWorker(const GURL& url,
                                           const GURL& worker) {
    DCHECK(worker.is_valid());
    return TestResourceLoadHelper(url, GURL(), worker);
  }

  // Loads 3p.com/script on page |url| from |worker| inside |sub_frame|
  // and returns whether the script was cached or not.
  bool TestResourceLoadFromDedicatedWorkerInIframe(const GURL& url,
                                                   const GURL& sub_frame,
                                                   const GURL& worker) {
    DCHECK(sub_frame.is_valid());
    DCHECK(worker.is_valid());
    return TestResourceLoadHelper(url, sub_frame, worker);
  }

  // Loads 3p.com/script on |popup| opened from page |url| and returns whether
  // the script was cached or not.
  bool TestResourceLoadFromPopup(const GURL& url, const GURL& popup) {
    DCHECK(popup.is_valid());
    return TestResourceLoadHelper(url, popup, GURL(), true);
  }

  // Loads 3p.com/script on page |url|. If |new_frame| is valid, it is loaded
  // from a new frame with that url; otherwise, it is loaded from the main
  // frame. This new frame is a popup if |use_popup|; otherwise, it is a
  // subframe. The load is optionally performed by |worker| if it's valid.
  bool TestResourceLoadHelper(const GURL& url,
                              const GURL& new_frame,
                              const GURL& worker,
                              bool use_popup = false) {
    DCHECK(url.is_valid());

    // Allocate a new process to prevent using the in-memory cache.
    // 1) Prevent the old page from entering the back-forward cache. Otherwise
    //    the old process will be kept alive, because it is still being used.
    // 2) Navigate to a WebUI URL, which uses a new process.
    DisableBFCacheForRFHForTesting(
        shell()->web_contents()->GetPrimaryMainFrame());
    EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("blob-internals")));

    // In the case of a redirect, the observed URL will be different from
    // what NavigateToURL(...) expects.
    if (base::StartsWith(url.GetPath(), "/redirect",
                         base::CompareCase::SENSITIVE)) {
      EXPECT_FALSE(NavigateToURL(shell(), url));
    } else {
      EXPECT_TRUE(NavigateToURL(shell(), url));
    }

    RenderFrameHost* host_to_load_resource =
        shell()->web_contents()->GetPrimaryMainFrame();
    RenderFrameHostImpl* main_frame =
        static_cast<RenderFrameHostImpl*>(host_to_load_resource);

    Shell* shell_to_observe = shell();

    if (new_frame.is_valid()) {
      // If there is supposed to be a subframe or popup, create it.
      if (use_popup) {
        shell_to_observe = OpenPopup(main_frame, new_frame, "");
        host_to_load_resource =
            static_cast<WebContentsImpl*>(shell_to_observe->web_contents())
                ->GetPrimaryMainFrame();
      } else {
        host_to_load_resource = CreateSubframe(new_frame);
      }
    }

    // `shell_to_observe` may still contain responses depending on process reuse
    // policies. Clear the in-memory cache in `shell_to_observe` to make sure
    // the following ResourceLoadObserver can observe network requests.
    base::RunLoop loop;
    shell_to_observe->web_contents()
        ->GetPrimaryMainFrame()
        ->GetProcess()
        ->GetRendererInterface()
        ->PurgeResourceCache(loop.QuitClosure());
    loop.Run();
    // Observe network requests.
    ResourceLoadObserver observer(shell_to_observe);

    GURL resource = GenURL("3p.com", "/script");

    // If there is supposed to be a worker to load this resource, create it.
    // Otherwise, load the resource directly.
    if (worker.is_valid()) {
      EXPECT_TRUE(ExecJs(host_to_load_resource, GetWorkerScript(worker)));
    } else {
      EXPECT_TRUE(
          ExecJs(host_to_load_resource, GetLoadResourceScript(resource)));
    }

    observer.WaitForResourceCompletion(resource);

    // Test the network isolation key.
    url::Origin top_frame_origin =
        main_frame->frame_tree_node()->current_origin();

    RenderFrameHostImpl* frame_host =
        static_cast<RenderFrameHostImpl*>(host_to_load_resource);
    url::Origin frame_origin;
    if (new_frame.is_empty()) {
      frame_origin = top_frame_origin;
    } else {
      frame_origin = url::Origin::Create(new_frame);
      if (use_popup && !frame_origin.opaque()) {
        // The popup is in a new WebContents, so its top_frame_origin is also
        // new unless it is blank.
        top_frame_origin = frame_origin;
      } else {
        // Take redirects and initially empty subframes/popups into account.
        frame_origin = frame_host->GetLastCommittedOrigin();
      }
    }

    if (!top_frame_origin.opaque() && !frame_origin.opaque()) {
      EXPECT_EQ(net::NetworkIsolationKey(net::SchemefulSite(top_frame_origin),
                                         net::SchemefulSite(frame_origin)),
                frame_host->GetNetworkIsolationKey());
    } else {
      EXPECT_TRUE(frame_host->GetNetworkIsolationKey().IsTransient());
    }

    return (*observer.GetResource(resource))->was_cached;
  }

  // Navigates to |url| and returns if the navigation resource was fetched from
  // the cache or not.
  bool NavigationResourceCached(const GURL& url,
                                const GURL& sub_frame,
                                bool subframe_navigation_resource_cached) {
    return NavigationResourceCached(url, url, sub_frame,
                                    subframe_navigation_resource_cached);
  }

  // Same as above, but allows explicitly specifying the expected commit URL
  // for the navigation to |url|, in case it differs.
  bool NavigationResourceCached(const GURL& url,
                                const GURL& expected_commit_url,
                                const GURL& sub_frame,
                                bool subframe_navigation_resource_cached) {
    // Do a cross-process navigation to clear the in-memory cache.
    // We assume that we don't start this call from "chrome://blob-internals",
    // as otherwise it won't be a cross-process navigation. We are relying on
    // this navigation to discard the old process.
    EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("blob-internals")));

    // Observe network requests.
    ResourceLoadObserver observer(shell());

    EXPECT_TRUE(NavigateToURL(shell(), url, expected_commit_url));

    RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
        shell()->web_contents()->GetPrimaryMainFrame());

    observer.WaitForResourceCompletion(url);

    if (sub_frame.is_valid()) {
      EXPECT_EQ(1U, main_frame->frame_tree_node()->child_count());
      NavigateFrameToURL(main_frame->frame_tree_node()->child_at(0), sub_frame);
      EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
      observer.WaitForResourceCompletion(sub_frame);
      EXPECT_EQ(subframe_navigation_resource_cached,
                (*observer.GetResource(sub_frame))->was_cached);
    }

    return (*observer.GetResource(url))->was_cached;
  }

  // Loads a dedicated worker script and checks to see whether or not the
  // script was cached.
  bool DedicatedWorkerScriptCached(const GURL& url,
                                   const GURL& sub_frame,
                                   const GURL& worker) {
    DCHECK(url.is_valid());
    DCHECK(worker.is_valid());

    // Do a cross-process navigation to clear the in-memory cache.
    // We assume that we don't start this call from "chrome://blob-internals",
    // as otherwise it won't be a cross-process navigation. We are relying on
    // this navigation to discard the old process.
    EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("blob-internals")));

    // Observe network requests.
    ResourceLoadObserver observer(shell());

    EXPECT_TRUE(NavigateToURL(shell(), url));

    RenderFrameHost* host_to_load_resource =
        shell()->web_contents()->GetPrimaryMainFrame();

    // If there is supposed to be a subframe, create it.
    if (sub_frame.is_valid()) {
      host_to_load_resource = CreateSubframe(sub_frame);
    }

    EXPECT_TRUE(ExecJs(host_to_load_resource, GetWorkerScript(worker)));

    observer.WaitForResourceCompletion(GenURL("3p.com", "/script"));
    observer.WaitForResourceCompletion(worker);

    return (*observer.GetResource(worker))->was_cached;
  }

  bool NavigationRedirectCached(const GURL& url, const GURL& redirect_url) {
    // Do a cross-process navigation to clear the in-memory cache.
    // We assume that we don't start this call from "chrome://blob-internals",
    // as otherwise it won't be a cross-process navigation. We are relying on
    // this navigation to discard the old process.
    EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("blob-internals")));

    EXPECT_TRUE(NavigateToURL(shell(), url));

    // Observe the redirect.
    ResourceLoadObserver observer(shell());
    EXPECT_TRUE(ExecJs(shell(), GetRedirectScript(redirect_url)));
    WaitForLoadStop(shell()->web_contents());
    observer.WaitForResourceCompletion(redirect_url);

    return (*observer.GetResource(redirect_url))->was_cached;
  }

  // Gets script to create subframe.
  std::string GetSubframeScript(const GURL& sub_frame) {
    const char kLoadIframeScript[] = R"(
        let iframe = document.createElement('iframe');
        iframe.src = $1;
        document.body.appendChild(iframe);
      )";
    return JsReplace(kLoadIframeScript, sub_frame);
  }

  // Gets script to create worker.
  std::string GetWorkerScript(const GURL& worker) {
    const char kLoadWorkerScript[] = "let w = new Worker($1);";
    return JsReplace(kLoadWorkerScript, worker);
  }

  // Gets script to load resource.
  std::string GetLoadResourceScript(const GURL& resource) {
    const char kLoadResourceScript[] = R"(
        let script = document.createElement('script');
        script.src = $1;
        document.body.appendChild(script);
      )";
    return JsReplace(kLoadResourceScript, resource);
  }

  // Gets script to redirect via JavaScript.
  std::string GetRedirectScript(const GURL& location) {
    const char kRedirectScript[] = R"(
        window.location.href = $1;
      )";
    return JsReplace(kRedirectScript, location);
  }

  GURL GenURL(const std::string& host, const std::string& path) {
    return embedded_test_server()->GetURL(host, path);
  }
};

class SplitCacheEnabledContentBrowserTest
    : public SplitCacheContentBrowserTestBase {
 public:
  SplitCacheEnabledContentBrowserTest() {
    split_cache_enabled_feature_list_.InitAndEnableFeature(
        net::features::kSplitCacheByNetworkIsolationKey);
  }

 private:
  base::test::ScopedFeatureList split_cache_enabled_feature_list_;
};

class SplitCacheDisabledContentBrowserTest
    : public SplitCacheContentBrowserTestBase {
 public:
  SplitCacheDisabledContentBrowserTest() {
    split_cache_disabled_feature_list_.InitAndDisableFeature(
        net::features::kSplitCacheByNetworkIsolationKey);
  }

 private:
  base::test::ScopedFeatureList split_cache_disabled_feature_list_;
};

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, MainFrame) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // The second time, it's cached.
  EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Now load it from a different site, and the resource isn't cached because
  // the top frame origin is different.
  EXPECT_FALSE(TestResourceLoad(GenURL("b.com", "/title1.html"), GURL()));

  // Now load it from 3p.com, which is same-site to the cacheable
  // resource. Still not supposed to be cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("3p.com", "/title1.html"), GURL()));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, MainFrameRedirect) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load it from a a.com/redirect_to_d which redirects to d.com/title1.html and
  // the resource shouldn't be cached because now we're on d.com.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/redirect_to_d"), GURL()));

  // Navigate to d.com directly. The main resource should be cached due to the
  // earlier navigation.
  EXPECT_TRUE(TestResourceLoad(GenURL("d.com", "/title1.html"), GURL()));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, Subframe) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load the resource from a same-origin iframe on a page where it's already
  // cached. It should still be cached.
  EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
                               GenURL("a.com", "/title1.html")));

  // Load the resource from a cross-origin iframe on a page where the
  // iframe hasn't been cached previously.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"),
                                GenURL("e.com", "/title1.html")));
  EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
                               GenURL("e.com", "/title1.html")));

  // Load the resource from a same-origin iframe on a page where it's not
  // cached. It should not be cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("e.com", "/title1.html"),
                                GenURL("e.com", "/title1.html")));
  EXPECT_TRUE(TestResourceLoad(GenURL("e.com", "/title1.html"),
                               GenURL("e.com", "/title1.html")));

  // Load the resource from a cross-origin iframe where the iframe's origin has
  // seen the object before but the top frame hasn't. It should not be cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("f.com", "/title1.html"),
                                GenURL("a.com", "/title1.html")));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, MainFrameDataUrl) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load the resource from a data url which has an opaque origin. It shouldn't
  // be cached.
  GURL data_url("data:text/html,<body>Hello World</body>");
  EXPECT_FALSE(TestResourceLoad(data_url, GURL()));

  // Load the same resource from the same data url, it shouldn't be cached
  // because the origin should be unique.
  EXPECT_FALSE(TestResourceLoad(data_url, GURL()));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       MainFrameAboutBlank) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load the resource from a document that points to about:blank.
  GURL blank_url(url::kAboutBlankURL);
  EXPECT_FALSE(TestResourceLoad(blank_url, GURL()));

  // Load the same resource from about:blank url again, it shouldn't be cached
  // because the origin is unique. TODO(crbug.com/40092527) will change this
  // behavior and about:blank main frame pages will inherit the origin of the
  // page that opened it.
  EXPECT_FALSE(TestResourceLoad(blank_url, GURL()));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, SubFrameRedirect) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load it from a a.com/redirect_to_d which redirects to d.com/title1.html and
  // the resource shouldn't be cached because now we're on d.com.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"),
                                GenURL("a.com", "/redirect_to_d")));

  // Now load it from the d.com iframe directly. It should be cached.
  EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
                               GenURL("d.com", "/title1.html")));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, SubFrameDataUrl) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load the resource from a data url which has an opaque origin. It shouldn't
  // be cached.
  GURL data_url("data:text/html,<body>Hello World</body>");
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), data_url));

  // Load the same resource from the same data url. It shouldn't be cached
  // because the cache isn't used for transient origins.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), data_url));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       SubframeAboutBlank) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load the resource from a subframe document that points to about:blank. The
  // resource is cached because the resource load is using the main frame's
  // URLLoaderFactory and main frame's factory has the NIK set to
  // (a.com, a.com) which is already in the cache.
  GURL blank_url(url::kAboutBlankURL);
  EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), blank_url));

  // Load the resource from a popup window that points to about:blank. The
  // resource is cached because the resource load is using the original main
  // frame's URLLoaderFactory and the original main frame's factory has the NIK
  // set to (a.com, a.com) which is already in the cache.
  EXPECT_TRUE(
      TestResourceLoadFromPopup(GenURL("a.com", "/title1.html"), blank_url));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, Popup) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
  ASSERT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Load the resource from a popup window that points to a new origin. The
  // resource is not cached because the resource load is using a NIK set to
  // (g.com, g.com).
  EXPECT_FALSE(TestResourceLoadFromPopup(GenURL("a.com", "/title1.html"),
                                         GenURL("g.com", "/title1.html")));

  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.foo.com", "/title1.html"), GURL()));

  // The second time, it's cached when accessed with the same eTLD+1.
  EXPECT_TRUE(TestResourceLoad(GenURL("b.foo.com", "/title1.html"), GURL()));

  // Now load it from a different site, and the resource isn't cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("b.com", "/title1.html"), GURL()));

  // Now load it from 3p.com, which is same-site to the cacheable
  // resource. Still not supposed to be cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("3p.com", "/title1.html"), GURL()));

  // Test case with iframe. This should be a cache hit since the network
  // isolation key is (foo.com, foo.com) as in the first case.
  EXPECT_TRUE(TestResourceLoad(GenURL("a.foo.com", "/title1.html"),
                               GenURL("iframe.foo.com", "/title1.html")));
}

IN_PROC_BROWSER_TEST_F(SplitCacheDisabledContentBrowserTest, NonSplitCache) {
  // Load a cacheable resource for the first time, and it's not cached.
  EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // The second time, it's cached.
  EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));

  // Now load it from a different site, and the resource is cached.
  EXPECT_TRUE(TestResourceLoad(GenURL("b.com", "/title1.html"), GURL()));

  // Load it from a cross-origin iframe, and it's still cached.
  EXPECT_TRUE(TestResourceLoad(GenURL("b.com", "/title1.html"),
                               GenURL("c.com", "/title1.html")));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       NavigationResources) {
  // Navigate for the first time, and it's not cached.
  EXPECT_FALSE(
      NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));

  // The second time, it's cached.
  EXPECT_TRUE(
      NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));

  // Navigate to a.com/redirect_to_d which redirects to d.com/title1.html.
  EXPECT_FALSE(NavigationResourceCached(GenURL("a.com", "/redirect_to_d"),
                                        GenURL("d.com", "/title1.html"), GURL(),
                                        false));

  // Navigate to d.com directly. The main resource should be cached due to the
  // earlier redirected navigation. Note that the HTTP cache doesn't deem the
  // earlier navigation cross-site (for the purposes of calculating the cache
  // key) since the navigation is browser-initiated.
  EXPECT_TRUE(
      NavigationResourceCached(GenURL("d.com", "/title1.html"), GURL(), false));

  // Navigate to a subframe with the same top frame origin as in an earlier
  // navigation and same url as already navigated to earlier in a main frame
  // navigation.
  // The 'is-subframe-document-resource' boolean prevents this resource from
  // sharing a cache partition with the earlier top-level navigation, even
  // in cases where the initiator is same-origin with the page being loaded.
  EXPECT_FALSE(NavigationResourceCached(
      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/title1.html"),
      false));

  EXPECT_TRUE(NavigationResourceCached(
      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/title1.html"), true));

  // Navigate to the same subframe document from a different top frame origin.
  // It should be a cache miss.
  EXPECT_FALSE(NavigationResourceCached(
      GenURL("b.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/title1.html"), false));
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       SubframeNavigationResources) {
  // Navigate for the first time, and it's not cached.
  NavigationResourceCached(
      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/title1.html"), false);

  // The second time it should be a cache hit.
  NavigationResourceCached(
      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/title1.html"), true);

  // Navigate to the same subframe document from a different top frame origin.
  // It should be a cache miss.
  NavigationResourceCached(
      GenURL("b.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/title1.html"), false);

  // Navigate the subframe to a.com/redirect_to_d which redirects to
  // d.com/title1.html.
  NavigationResourceCached(
      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("a.com", "/redirect_to_d"), false);

  // Navigate to d.com directly. The resource should be cached due to the
  // earlier redirected navigation.
  NavigationResourceCached(
      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("d.com", "/title1.html"), true);
}

// Tests that when a subresource URL which is same-site to the fetching frame
// is later used to create a subframe from the same top-level site, it should
// not be a cache hit (crbug.com/1135149).
IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       SubframeNavigationResource) {
  // main.com iframes 3p.com which fetches a subresource 3p.com/script with
  // cache key (main.com, 3p.com, 3p.com/script). Then main.com iframes evil.com
  // which iframes 3p.com/script. It should be a cache miss since the first time
  // it was fetched as a subresource and second time as a subframe document
  // resource.

  // Fetch 3p.com/script from a 3p.com iframe, top-level site main.com.
  TestResourceLoad(GenURL("main.com", "/title1.html") /* top-level frame */,
                   GenURL("3p.com", "/title1.html") /* subframe */);

  // Create evil.com iframe inside top-level site main.com.
  NavigationResourceCached(
      GenURL("main.com", "/navigation_controller/page_with_iframe.html"),
      GenURL("evil.com", "/title1.html"), false);
  RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
      shell()->web_contents()->GetPrimaryMainFrame());
  EXPECT_EQ(1U, main_frame->frame_tree_node()->child_count());

  // Observe network requests.
  ResourceLoadObserver observer(shell());

  // Now iframe 3p.com/script within evil.com.
  GURL subframe_url = GenURL("3p.com", "/script");
  EXPECT_TRUE(ExecJs(main_frame->frame_tree_node()->child_at(0),
                     GetSubframeScript(subframe_url)));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  observer.WaitForResourceCompletion(subframe_url);
  EXPECT_EQ(false, (*observer.GetResource(subframe_url))->was_cached);
}

// Tests that a cross-site navigation to a document that was previously loaded
// via top-level navigation doesn't use the cache, since doing so could enable
// cross-site leaks.
IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       CrossSiteNavigation) {
  // Do a top-level navigation to a document to add it to the cache.
  EXPECT_FALSE(
      NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));

  // Verify that the document did get added to the cache.
  EXPECT_TRUE(
      NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));

  // Navigate to a cross-site document that performs a client-side redirect to
  // the document visited previously. Ensure that repeating this request results
  // in a cache hit, and then try again with a different initiating site.
  bool evil_com_initiator_first_navigation_result = NavigationRedirectCached(
      GenURL("evil.com", "/title1.html"), GenURL("a.com", "/title1.html"));

  EXPECT_TRUE(NavigationRedirectCached(GenURL("evil.com", "/title1.html"),
                                       GenURL("a.com", "/title1.html")));

  bool evil2_com_initiator_navigation_result = NavigationRedirectCached(
      GenURL("evil2.com", "/title1.html"), GenURL("a.com", "/title1.html"));

  // With the cross-site navigation boolean, the first of these should not be in
  // the cache because it's cross-site, but a subsequent cross-site navigation
  // from a different initiator should result in a cache hit.
  EXPECT_FALSE(evil_com_initiator_first_navigation_result);
  EXPECT_TRUE(evil2_com_initiator_navigation_result);
}

// Tests that a cross-origin but same-site navigation to a document that was
// previously loaded via top-level navigation uses the cache.
IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       CrossOriginSameSiteNavigation) {
  // Do a top-level navigation to a document to add it to the cache.
  EXPECT_FALSE(
      NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));

  // Verify that the document did get added to the cache.
  EXPECT_TRUE(
      NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));

  // Navigate to a cross-site document that performs a client-side redirect to
  // the document visited previously. Ensure that repeating this request results
  // in a cache hit, and then try again with a different initiating site that is
  // cross-origin but same-site.
  bool a_example_com_initiator_first_navigation_result =
      NavigationRedirectCached(GenURL("a.example.com", "/title1.html"),
                               GenURL("a.com", "/title1.html"));

  EXPECT_TRUE(NavigationRedirectCached(GenURL("a.example.com", "/title1.html"),
                                       GenURL("a.com", "/title1.html")));

  bool b_example_com_initiator_navigation_result = NavigationRedirectCached(
      GenURL("b.example.com", "/title1.html"), GenURL("a.com", "/title1.html"));

  // With the cross-site navigation boolean, the first of these should not be in
  // the cache because it's cross-site, but a subsequent cross-site navigation
  // from a different initiator should result in a cache hit.
  EXPECT_FALSE(a_example_com_initiator_first_navigation_result);
  EXPECT_TRUE(b_example_com_initiator_navigation_result);
}

// This class invokes ComputeHttpCacheSize on the Network Context and
// waits for the callback to be invoked.
class SplitCacheComputeHttpCacheSize {
 public:
  SplitCacheComputeHttpCacheSize() = default;
  ~SplitCacheComputeHttpCacheSize() = default;

  int64_t ComputeHttpCacheSize(BrowserContext* context,
                               base::Time start_time,
                               base::Time end_time) {
    last_computed_cache_size_ = -1;
    auto* network_context =
        context->GetDefaultStoragePartition()->GetNetworkContext();
    network::mojom::NetworkContext::ComputeHttpCacheSizeCallback size_callback =
        base::BindOnce(
            &SplitCacheComputeHttpCacheSize::ComputeCacheSizeCallback,
            base::Unretained(this));

    network_context->ComputeHttpCacheSize(start_time, end_time,
                                          std::move(size_callback));
    runloop_ = std::make_unique<base::RunLoop>();
    runloop_->Run();
    return last_computed_cache_size_;
  }

  void ComputeCacheSizeCallback(bool is_upper_bound, int64_t size) {
    last_computed_cache_size_ = size;
    runloop_->Quit();
  }

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

 private:
  std::unique_ptr<base::RunLoop> runloop_;
  int64_t last_computed_cache_size_ = -1;
};

// Tests that NotifyExternalCacheHit() has the correct value of
// is_subframe_document_resource by checking that the size of the http cache
// resources accessed after the resource is loaded from the blink cache is the
// same as before that.
// TODO(crbug.com/40164302): Test is flaky on Win.
#if BUILDFLAG(IS_WIN)
#define MAYBE_NotifyExternalCacheHitCheckSubframeBit \
  DISABLED_NotifyExternalCacheHitCheckSubframeBit
#else
#define MAYBE_NotifyExternalCacheHitCheckSubframeBit \
  NotifyExternalCacheHitCheckSubframeBit
#endif
IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       MAYBE_NotifyExternalCacheHitCheckSubframeBit) {
  ResourceLoadObserver observer(shell());
  BrowserContext* context = shell()->web_contents()->GetBrowserContext();
  std::unique_ptr<SplitCacheComputeHttpCacheSize> http_cache_size =
      std::make_unique<SplitCacheComputeHttpCacheSize>();

  // Since no resources are loaded yet, Http cache's size will be 0.
  EXPECT_EQ(0, http_cache_size->ComputeHttpCacheSize(context, base::Time(),
                                                     base::Time::Max()));

  // First fetch will populate the cache.
  GURL page_url(
      embedded_test_server()->GetURL("/page_with_cached_subresource.html"));
  EXPECT_TRUE(NavigateToURL(shell(), page_url));

  // Checking the size of resources should be >0 as resources have been loaded.
  int64_t size1 = http_cache_size->ComputeHttpCacheSize(context, base::Time(),
                                                        base::Time::Max());
  EXPECT_GT(size1, 0);
  ASSERT_EQ(2U, observer.resource_load_entries().size());
  EXPECT_TRUE(observer.memory_cached_loaded_urls().empty());
  observer.Reset();

  // Make sure time has moved forward from when the last entry was cached.
  base::Time start = base::Time::Now();
  while (start == base::Time::Now()) {
  }
  base::Time after_first = base::Time::Now();

  // Loading again should serve the request out of the in-memory cache.
  EXPECT_TRUE(NavigateToURL(shell(), page_url));

  ASSERT_EQ(1U, observer.resource_load_entries().size());
  ASSERT_EQ(1U, observer.memory_cached_loaded_urls().size());

  // Loading from the in-memory cache also changes the last accessed time of
  // those resources in the http cache. So if we check the size of resources
  // accessed after the first load till now, it will be the same as before the
  // 2nd navigation.
  int64_t size2 = http_cache_size->ComputeHttpCacheSize(context, after_first,
                                                        base::Time::Max());
  EXPECT_EQ(size1, size2);
}

IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest, DedicatedWorkers) {
  // Load 3p.com/script from a.com's worker. The first time it's loaded from the
  // network and the second it's cached.
  EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
      GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
      GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));

  // Load 3p.com/script from a worker with a new top-frame origin. Due to split
  // caching it's a cache miss.
  EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
      GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
      GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));

  // Load 3p.com/script from a nested worker with a new top-frame origin. Due to
  // split caching it's a cache miss.
  EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
      GenURL("c.com", "/title1.html"),
      GenURL("c.com", "/embedding_worker.js?c")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
      GenURL("c.com", "/title1.html"),
      GenURL("c.com", "/embedding_worker.js?c")));

  // Load 3p.com/script from a worker with a new top-frame origin and nested in
  // a cross-origin iframe. Due to split caching it's a cache miss.
  EXPECT_FALSE(TestResourceLoadFromDedicatedWorkerInIframe(
      GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
      GenURL("e.com", "/worker.js")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
      GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
      GenURL("e.com", "/worker.js")));

  // Load 3p.com/script from a worker with a new top-frame origin and nested in
  // a cross-origin iframe whose URL has previously been loaded.
  EXPECT_FALSE(TestResourceLoadFromDedicatedWorkerInIframe(
      GenURL("f.com", "/title1.html"), GenURL("e.com", "/title1.html"),
      GenURL("e.com", "/worker.js")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
      GenURL("f.com", "/title1.html"), GenURL("e.com", "/title1.html"),
      GenURL("e.com", "/worker.js")));
}

// https://crbug.com/1218723 started flaking after Field Trial Testing Config
// was enabled for content_browsertests.
IN_PROC_BROWSER_TEST_F(SplitCacheEnabledContentBrowserTest,
                       DISABLED_DedicatedWorkersScripts) {
  // Load a.com's worker. The first time the worker script is loaded from the
  // network and the second it's cached.
  EXPECT_FALSE(DedicatedWorkerScriptCached(
      GenURL("a.com", "/title1.html"), GURL(), GenURL("a.com", "/worker.js")));
  EXPECT_TRUE(DedicatedWorkerScriptCached(
      GenURL("a.com", "/title1.html"), GURL(), GenURL("a.com", "/worker.js")));

  // Load a nested worker with a new top-frame origin. It's a cache miss for
  // the embedding worker the first time, as it hasn't been loaded yet, and
  // then the second time it's cached.
  EXPECT_FALSE(
      DedicatedWorkerScriptCached(GenURL("c.com", "/title1.html"), GURL(),
                                  GenURL("c.com", "/embedding_worker.js?c")));
  EXPECT_TRUE(
      DedicatedWorkerScriptCached(GenURL("c.com", "/title1.html"), GURL(),
                                  GenURL("c.com", "/embedding_worker.js?c")));

  // Load a worker with a new top-frame origin and nested in a cross-origin
  // iframe. It's a cache miss for the worker script the first time, then
  // the second time it's cached.
  EXPECT_FALSE(DedicatedWorkerScriptCached(GenURL("d.com", "/title1.html"),
                                           GenURL("e.com", "/title1.html"),
                                           GenURL("e.com", "/worker.js")));
  EXPECT_TRUE(DedicatedWorkerScriptCached(GenURL("d.com", "/title1.html"),
                                          GenURL("e.com", "/title1.html"),
                                          GenURL("e.com", "/worker.js")));

  // Load a worker with a new top-frame origin and nested in a cross-origin
  // iframe whose URL has previously been loaded. Due to split caching it's a
  // cache miss for the worker script the first time.
  EXPECT_FALSE(DedicatedWorkerScriptCached(GenURL("f.com", "/title1.html"),
                                           GenURL("e.com", "/title1.html"),
                                           GenURL("e.com", "/worker.js")));
  EXPECT_TRUE(DedicatedWorkerScriptCached(GenURL("f.com", "/title1.html"),
                                          GenURL("e.com", "/title1.html"),
                                          GenURL("e.com", "/worker.js")));
}

IN_PROC_BROWSER_TEST_F(SplitCacheDisabledContentBrowserTest, DedicatedWorkers) {
  // Load 3p.com/script from a.com's worker. The first time it's loaded from the
  // network and the second it's cached.
  EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
      GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
      GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));

  // Load 3p.com/script from b.com's worker. The cache isn't split by top-frame
  // origin so the resource is already cached.
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
      GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));

  // Load 3p.com/script from a nested worker with a new top-frame origin. The
  // cache isn't split by top-frame origin so the resource is already cached.
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
      GenURL("c.com", "/title1.html"),
      GenURL("c.com", "/embedding_worker.js?c")));

  // Load 3p.com/script from a worker with a new top-frame origin and nested in
  // a cross-origin iframe. The cache isn't split by top-frame origin so the
  // resource is already cached.
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
      GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
      GenURL("e.com", "/worker.js")));
  EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
      GenURL("f.com", "/title1.html"), GenURL("e.com", "/title1.html"),
      GenURL("e.com", "/worker.js")));
}

class SplitCacheByIncludeCredentialsTest : public ContentBrowserTest {
 public:
  SplitCacheByIncludeCredentialsTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    feature_list_.InitAndEnableFeature(
        net::features::kSplitCacheByIncludeCredentials);
  }

  int cacheable_request_count() const { return cacheable_request_count_; }
  net::EmbeddedTestServer* https_server() { return &https_server_; }
  GURL CacheableUrl() { return https_server()->GetURL("b.test", "/cacheable"); }

  void RequestAnonymous(Shell* shell) {
    EXPECT_TRUE(ExecJs(shell, JsReplace(R"(
      new Promise(resolve => {
        const image = new Image();
        image.src = $1;
        image.crossOrigin = "anonymous";
        image.onload = resolve;
        document.body.appendChild(image);
      });
    )",
                                        CacheableUrl())));
  }

  void RequestUseCredentials(Shell* shell) {
    EXPECT_TRUE(ExecJs(shell, JsReplace(R"(
      new Promise(resolve => {
        const image = new Image();
        image.src = $1;
        image.crossOrigin = "use-credentials";
        image.onload = resolve;
        document.body.appendChild(image);
      });
    )",
                                        CacheableUrl())));
  }

 private:
  void SetUpOnMainThread() final {
    ContentBrowserTest::SetUpOnMainThread();

    cacheable_request_count_ = 0;
    host_resolver()->AddRule("*", "127.0.0.1");
    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server()->RegisterRequestHandler(
        base::BindRepeating(&SplitCacheByIncludeCredentialsTest::RequestHandler,
                            base::Unretained(this)));
    https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
    net::test_server::RegisterDefaultHandlers(https_server());
    SetupCrossSiteRedirector(https_server());
    CHECK(https_server()->Start());
  }

  std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
      const net::test_server::HttpRequest& request) {
    if (request.relative_url == "/cacheable") {
      cacheable_request_count_++;
      auto response = std::make_unique<net::test_server::BasicHttpResponse>();
      response->set_content_type("image/svg+xml");
      response->set_content("<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>");
      response->AddCustomHeader("Cache-Control", "max-age=3600");
      response->AddCustomHeader("Cross-Origin-Resource-Policy", "cross-origin");
      response->AddCustomHeader("Access-Control-Allow-Credentials", "true");
      response->AddCustomHeader(
          "Access-Control-Allow-Origin",
          https_server()->GetOrigin("a.test").Serialize());
      return response;
    }
    return nullptr;
  }

  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer https_server_;
  // Initialized and read from the UI thread. Written from the |https_server_|.
  std::atomic<int> cacheable_request_count_;
};

// Note: Compared to .DifferentProcess and .DifferentProcessVariant, this test
// emits requests from the same renderer process. This is useful for checking
// the behavior of blink's memory cache instead of the HTTP cache.
IN_PROC_BROWSER_TEST_F(SplitCacheByIncludeCredentialsTest, SameProcess) {
  GURL page_url(https_server()->GetURL("a.test", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), page_url));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  EXPECT_EQ(0, cacheable_request_count());

  RequestAnonymous(shell());
  EXPECT_EQ(1, cacheable_request_count());
  RequestAnonymous(shell());
  EXPECT_EQ(1, cacheable_request_count());

  RequestUseCredentials(shell());
  EXPECT_EQ(2, cacheable_request_count());
  RequestUseCredentials(shell());
  EXPECT_EQ(2, cacheable_request_count());

  RequestAnonymous(shell());
  EXPECT_EQ(2, cacheable_request_count());
}

IN_PROC_BROWSER_TEST_F(SplitCacheByIncludeCredentialsTest, SameProcessVariant) {
  GURL page_url(https_server()->GetURL("a.test", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), page_url));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  EXPECT_EQ(0, cacheable_request_count());

  RequestUseCredentials(shell());
  EXPECT_EQ(1, cacheable_request_count());
  RequestUseCredentials(shell());
  EXPECT_EQ(1, cacheable_request_count());

  RequestAnonymous(shell());
  EXPECT_EQ(2, cacheable_request_count());
  RequestAnonymous(shell());
  EXPECT_EQ(2, cacheable_request_count());

  RequestUseCredentials(shell());
  EXPECT_EQ(2, cacheable_request_count());
}

// Note: Compared to .SameProcess and .SameProcessVariant, this test emits
// requests from two different renderer process. This is useful for checking the
// behavior of the HTTP cache instead of blink's memory cache.
//
// COOP+COEP are used to get two same-origin documents loaded from different
// renderer process. This avoids interferences from
// SplitCacheByNetworkIsolationKey.
IN_PROC_BROWSER_TEST_F(SplitCacheByIncludeCredentialsTest, DifferentProcess) {
  GURL page_1_url(https_server()->GetURL("a.test", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), page_1_url));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  GURL page_2_url =
      https_server()->GetURL("a.test",
                             "/set-header?"
                             "Cross-Origin-Opener-Policy: same-origin&"
                             "Cross-Origin-Embedder-Policy: require-corp");
  ShellAddedObserver shell_observer;
  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.open($1)", page_2_url)));
  Shell* new_shell = shell_observer.GetShell();
  EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));

  EXPECT_NE(static_cast<WebContentsImpl*>(shell()->web_contents())
                ->GetPrimaryMainFrame()
                ->GetProcess(),
            static_cast<WebContentsImpl*>(new_shell->web_contents())
                ->GetPrimaryMainFrame()
                ->GetProcess());

  EXPECT_EQ(0, cacheable_request_count());

  RequestAnonymous(shell());
  EXPECT_EQ(1, cacheable_request_count());
  RequestAnonymous(new_shell);
  EXPECT_EQ(1, cacheable_request_count());

  RequestUseCredentials(shell());
  EXPECT_EQ(2, cacheable_request_count());
  RequestUseCredentials(new_shell);
  EXPECT_EQ(2, cacheable_request_count());

  RequestAnonymous(shell());
  EXPECT_EQ(2, cacheable_request_count());
}

IN_PROC_BROWSER_TEST_F(SplitCacheByIncludeCredentialsTest,
                       DifferentProcessVariant) {
  GURL page_1_url(https_server()->GetURL("a.test", "/empty.html"));
  EXPECT_TRUE(NavigateToURL(shell(), page_1_url));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  GURL page_2_url =
      https_server()->GetURL("a.test",
                             "/set-header?"
                             "Cross-Origin-Opener-Policy: same-origin&"
                             "Cross-Origin-Embedder-Policy: require-corp");
  ShellAddedObserver shell_observer;
  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.open($1)", page_2_url)));
  Shell* new_shell = shell_observer.GetShell();
  EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));

  EXPECT_NE(static_cast<WebContentsImpl*>(shell()->web_contents())
                ->GetPrimaryMainFrame()
                ->GetProcess(),
            static_cast<WebContentsImpl*>(new_shell->web_contents())
                ->GetPrimaryMainFrame()
                ->GetProcess());

  EXPECT_EQ(0, cacheable_request_count());

  RequestUseCredentials(shell());
  EXPECT_EQ(1, cacheable_request_count());
  RequestUseCredentials(new_shell);
  EXPECT_EQ(1, cacheable_request_count());

  RequestAnonymous(shell());
  EXPECT_EQ(2, cacheable_request_count());
  RequestAnonymous(new_shell);
  EXPECT_EQ(2, cacheable_request_count());

  RequestUseCredentials(shell());
  EXPECT_EQ(2, cacheable_request_count());
}

}  // namespace
}  // namespace content