blob: d2570c27660be062a9609c45717af41dbf826ca3 [file] [log] [blame]
// 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 SplitCacheContentBrowserTest : public ContentBrowserTest {
public:
enum class Context { kMainFrame, kSameOriginFrame, kCrossOriginFrame };
SplitCacheContentBrowserTest() = default;
SplitCacheContentBrowserTest(const SplitCacheContentBrowserTest&) = delete;
SplitCacheContentBrowserTest& operator=(const SplitCacheContentBrowserTest&) =
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(&SplitCacheContentBrowserTest::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.path() == "/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.path() == "/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.path() == "/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.path() == "/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.path() == "/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.query().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.path(), "/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);
}
};
enum class SplitCacheTestCase {
kEnabledTripleKeyed,
kEnabledTriplePlusCrossSiteMainFrameNavBool,
};
const struct {
const SplitCacheTestCase test_case;
base::test::FeatureRef feature;
} kTestCaseToFeatureMapping[] = {
{SplitCacheTestCase::kEnabledTriplePlusCrossSiteMainFrameNavBool,
net::features::kSplitCacheByCrossSiteMainFrameNavigationBoolean}};
class SplitCacheContentBrowserTestEnabled
: public SplitCacheContentBrowserTest,
public testing::WithParamInterface<SplitCacheTestCase> {
public:
SplitCacheContentBrowserTestEnabled()
: split_cache_experiment_feature_list_(GetParam(),
kTestCaseToFeatureMapping) {
split_cache_always_enabled_feature_list_.InitAndEnableFeature(
net::features::kSplitCacheByNetworkIsolationKey);
}
private:
net::test::ScopedMutuallyExclusiveFeatureList
split_cache_experiment_feature_list_;
base::test::ScopedFeatureList split_cache_always_enabled_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SplitCacheContentBrowserTestEnabled,
testing::ValuesIn(
{SplitCacheTestCase::kEnabledTripleKeyed,
SplitCacheTestCase::kEnabledTriplePlusCrossSiteMainFrameNavBool}),
[](const testing::TestParamInfo<SplitCacheTestCase>& info) {
switch (info.param) {
case SplitCacheTestCase::kEnabledTripleKeyed:
return "SplitCacheEnabledTripleKeyed";
case SplitCacheTestCase::kEnabledTriplePlusCrossSiteMainFrameNavBool:
return "SplitCacheEnabledTriplePlusCrossSiteMainFrameNavigationBool";
}
});
class SplitCacheContentBrowserTestDisabled
: public SplitCacheContentBrowserTest {
public:
SplitCacheContentBrowserTestDisabled() {
feature_list_.InitAndDisableFeature(
net::features::kSplitCacheByNetworkIsolationKey);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_P(SplitCacheContentBrowserTestEnabled, 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_P(SplitCacheContentBrowserTestEnabled, 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_P(SplitCacheContentBrowserTestEnabled, 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_P(SplitCacheContentBrowserTestEnabled, 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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled, 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_P(SplitCacheContentBrowserTestEnabled, 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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled, 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(SplitCacheContentBrowserTestDisabled, 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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled,
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"));
switch (GetParam()) {
case SplitCacheTestCase::kEnabledTripleKeyed:
// If we aren't partitioning cross-site navigations then these should come
// from the cache.
EXPECT_TRUE(evil_com_initiator_first_navigation_result);
EXPECT_TRUE(evil2_com_initiator_navigation_result);
break;
case SplitCacheTestCase::kEnabledTriplePlusCrossSiteMainFrameNavBool:
// Using 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);
break;
}
}
// 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_P(SplitCacheContentBrowserTestEnabled,
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"));
switch (GetParam()) {
case SplitCacheTestCase::kEnabledTripleKeyed:
// If we aren't partitioning cross-site navigations then these should come
// from the cache.
EXPECT_TRUE(a_example_com_initiator_first_navigation_result);
EXPECT_TRUE(b_example_com_initiator_navigation_result);
break;
case SplitCacheTestCase::kEnabledTriplePlusCrossSiteMainFrameNavBool:
// Using 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);
break;
}
}
// 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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled,
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_P(SplitCacheContentBrowserTestEnabled,
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(SplitCacheContentBrowserTestDisabled, 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