blob: 3311e02acc40135fbca5db2000e2c71f543baa29 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/child_process_id_util.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/resource_load_observer.h"
#include "content/public/test/test_utils.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/http/http_request_headers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/basic_data_buffer_factory.h"
#include "services/network/public/cpp/data_buffer_factory.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_http_cache_client.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/network_service_test.mojom.h"
#include "services/network/public/mojom/shared_http_cache_client.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_client.h"
namespace content {
namespace {
std::string GetStringFromBuffers(
const std::unique_ptr<network::DataBufferList>& buffers) {
std::string result_string;
for (auto buffer : *buffers) {
result_string.append(reinterpret_cast<const char*>(buffer.data()),
buffer.size());
}
return result_string;
}
std::unique_ptr<net::test_server::BasicHttpResponse> CreateScriptResponse(
net::HttpStatusCode code = net::HTTP_OK) {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(code);
response->set_content_type("application/javascript");
response->AddCustomHeader("Cache-Control", "max-age=3600");
response->set_content("window._resolve('Done');");
return response;
}
std::unique_ptr<net::test_server::HttpResponse> HandleCustomResponse(
const net::test_server::HttpRequest& request) {
GURL url = request.GetURL();
std::string_view path = url.path();
if (path == "/custom_resource/acao_wildcard.js") {
auto response = CreateScriptResponse();
response->AddCustomHeader("Access-Control-Allow-Origin", "*");
return response;
}
if (path == "/custom_resource/acao_origin.js") {
auto response = CreateScriptResponse();
response->AddCustomHeader("Access-Control-Allow-Origin", "https://a.test");
return response;
}
if (path == "/custom_resource/corp_same_origin.js") {
auto response = CreateScriptResponse();
response->AddCustomHeader("Cross-Origin-Resource-Policy", "same-origin");
return response;
}
if (path == "/custom_resource/corp_cross_origin.js") {
auto response = CreateScriptResponse();
response->AddCustomHeader("Cross-Origin-Resource-Policy", "cross-origin");
return response;
}
if (path == "/custom_resource/partial.js") {
auto response = CreateScriptResponse(net::HTTP_PARTIAL_CONTENT);
response->AddCustomHeader("Access-Control-Allow-Origin", "*");
response->AddCustomHeader("Content-Range", "bytes 0-21/22");
return response;
}
if (path == "/custom_resource/redirect.js") {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_FOUND);
response->AddCustomHeader("Location", "/custom_resource/acao_wildcard.js");
response->AddCustomHeader("Access-Control-Allow-Origin", "*");
return response;
}
return nullptr;
}
class TestContentBrowserClient : public ContentBrowserTestContentBrowserClient {
public:
TestContentBrowserClient() = default;
~TestContentBrowserClient() override = default;
void ConfigureNetworkContextParams(
BrowserContext* context,
bool in_memory,
const base::FilePath& relative_partition_path,
network::mojom::NetworkContextParams* network_context_params,
cert_verifier::mojom::CertVerifierCreationParams*
cert_verifier_creation_params) override {
CHECK(network_context_params->http_cache_enabled);
if (!in_memory) {
network_context_params->file_paths =
network::mojom::NetworkContextFilePaths::New();
base::FilePath path = context->GetPath();
if (!relative_partition_path.empty()) {
path = path.Append(relative_partition_path);
}
path = path.AppendASCII("Cache");
network_context_params->file_paths->http_cache_directory = path;
}
ContentBrowserTestContentBrowserClient::ConfigureNetworkContextParams(
context, in_memory, relative_partition_path, network_context_params,
cert_verifier_creation_params);
}
};
class RendererAccessibleHttpCacheBrowserTestBase : public ContentBrowserTest {
public:
explicit RendererAccessibleHttpCacheBrowserTestBase(bool wal_mode) {
base::FieldTrialParams shared_cache_params;
shared_cache_params[net::features::kRendererAccessibleHttpCacheWalMode
.name] = wal_mode ? "true" : "false";
std::vector<base::test::FeatureRefAndParams> enabled_features = {
// The renderer accessible HTTP Cache depends on the SQL disk cache
// backend.
{net::features::kDiskCacheBackendExperiment,
{{net::features::kDiskCacheBackendParam.name, "sql"}}},
{net::features::kRendererAccessibleHttpCache, shared_cache_params}};
std::vector<base::test::FeatureRef> disabled_features = {
// Need to disable kNetworkServiceInProcess feature to run the tests on
// Android, as we crash the network service in SetUpOnMainThread() to
// enable the on-disk HTTP Cache.
features::kNetworkServiceInProcess,
// Origin-keyed agent clusters are not eligible for the renderer
// accessible HTTP cache. Disable kOriginKeyedProcessesByDefault so
// HTTPS test origins remain site-keyed on builders like linux-oi-rel.
features::kOriginKeyedProcessesByDefault};
feature_list_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
}
~RendererAccessibleHttpCacheBrowserTestBase() override = default;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_https_test_server().SetSSLConfig(
net::EmbeddedTestServer::CERT_TEST_NAMES);
embedded_https_test_server().RegisterRequestHandler(
base::BindRepeating(&HandleCustomResponse));
embedded_https_test_server().ServeFilesFromSourceDirectory(
"content/test/data");
ASSERT_TRUE(embedded_https_test_server().Start());
ContentBrowserTest::SetUpOnMainThread();
// When creating a network context,
// ContentBrowserClient::ConfigureNetworkContextParams() is called, where
// setting file_paths and http_cache_directory enables the on-disk HTTP
// Cache. However, since ShellContentBrowserClient and the network context
// are created within ContentBrowserTest::SetUp, it is not possible to
// substitute it with TestContentBrowserClient during that timing.
// Therefore, after creating TestContentBrowserClient, we trigger a Network
// Service crash and recreate the network context to enable the on-disk HTTP
// Cache.
test_content_browser_client_ = std::make_unique<TestContentBrowserClient>();
SimulateNetworkServiceCrash();
}
void TearDownOnMainThread() override { test_content_browser_client_.reset(); }
protected:
GURL GetURL(std::string_view path) {
return embedded_https_test_server().GetURL("a.test", path);
}
network::mojom::NetworkContext* GetNetworkContext() {
return shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition()
->GetNetworkContext();
}
void FlushSharedCacheEligibleEntries() {
mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
GetNetworkService()->BindTestInterfaceForTesting(
network_service_test.BindNewPipeAndPassReceiver());
base::RunLoop run_loop;
network_service_test->ProcessSharedCacheEligibleEntriesForTesting(
run_loop.QuitClosure());
run_loop.Run();
}
void ReloadInNewProcess(const GURL& page_url) {
DisableBFCacheForRFHForTesting(
shell()->web_contents()->GetPrimaryMainFrame());
EXPECT_TRUE(
NavigateToURL(shell()->web_contents(), GetWebUIURL("blob-internals")));
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
}
std::string LoadTestFile(const std::string_view path) {
base::FilePath test_data_dir;
CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_data_dir));
base::ScopedAllowBlockingForTesting allow_blocking;
std::string test_file_string;
CHECK(base::ReadFileToString(
test_data_dir.AppendASCII("content/test/data").AppendASCII(path),
&test_file_string));
return test_file_string;
}
void FetchScript(const GURL& resource_url) {
std::string fetch_script = JsReplace(
R"(
new Promise(resolve => {
window._resolve = resolve;
const script = document.createElement('script');
script.src = $1;
document.body.appendChild(script);
});
)",
resource_url);
EXPECT_EQ("Done", EvalJs(shell()->web_contents(), fetch_script));
}
scoped_refptr<network::SharedHttpCacheClient> InitSharedHttpCacheClient(
const GURL& page_url) {
base::RunLoop run_loop_for_db_init;
mojo::PendingRemote<network::mojom::SharedHttpCacheClientFactory>
http_cache_client_remote;
auto shared_http_cache_client =
network::SharedHttpCacheClient::CreateAndInit(
http_cache_client_remote.InitWithNewPipeAndPassReceiver(),
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_BLOCKING}),
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_BLOCKING, base::MayBlock()}),
run_loop_for_db_init.QuitClosure());
net::NetworkIsolationKey key{net::SchemefulSite(page_url),
net::SchemefulSite(page_url)};
GetNetworkContext()->RegisterHttpCacheClient(
key, std::move(http_cache_client_remote));
run_loop_for_db_init.Run();
return shared_http_cache_client;
}
// Ensures the Shared Cache SQLite database is created for `page_url`'s
// NetworkIsolationKey before initializing and returning a
// SharedHttpCacheClient.
//
// `SqlSharedCache` delays database file creation and `CreateClient`
// notification until at least one eligible entry is written and flushed. In
// negative test cases where the tested resource is expected to be excluded
// from the cache, `InitSharedHttpCacheClient` alone would block indefinitely
// waiting for `CreateClient`. This helper fetches a known cacheable resource
// first, flushes eligible entries, initializes the client, and verifies that
// the control resource is present in the cache.
scoped_refptr<network::SharedHttpCacheClient>
EnsureSharedCacheCreatedAndInitClient(const GURL& page_url) {
GURL valid_url = GetURL("/loader/cacheable_jsonp.js");
FetchScript(valid_url);
FlushSharedCacheEligibleEntries();
auto client = InitSharedHttpCacheClient(page_url);
EXPECT_TRUE(CheckResourceInSharedCache(client, valid_url));
return client;
}
bool CheckResourceInSharedCache(
scoped_refptr<network::SharedHttpCacheClient> shared_http_cache_client,
const GURL& resource_url) {
network::ResourceRequest request = CreateSubresourceRequest(resource_url);
base::RunLoop run_loop_for_find;
std::optional<network::SharedHttpCacheClient::Response> found_response;
shared_http_cache_client->Find(
request, base::MakeRefCounted<network::BasicDataBufferFactory>(),
base::BindLambdaForTesting(
[&](std::optional<network::SharedHttpCacheClient::Response>
response) {
found_response = std::move(response);
run_loop_for_find.Quit();
}),
base::SequencedTaskRunner::GetCurrentDefault());
run_loop_for_find.Run();
return found_response.has_value();
}
mojo::Remote<network::mojom::URLLoaderFactory> CreateDirectURLLoaderFactory(
bool renderer_accessible_http_cache_write_enabled) {
RenderFrameHost* frame = shell()->web_contents()->GetPrimaryMainFrame();
const url::Origin& origin = frame->GetLastCommittedOrigin();
net::IsolationInfo isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kOther, origin, origin,
net::SiteForCookies::FromOrigin(origin));
network::mojom::URLLoaderFactoryParamsPtr params =
network::mojom::URLLoaderFactoryParams::New();
params->process_id = ToOriginatingProcessId(frame->GetProcess()->GetID());
params->request_initiator_origin_lock = origin;
params->isolation_info = isolation_info;
params->renderer_accessible_http_cache_write_enabled =
renderer_accessible_http_cache_write_enabled;
mojo::Remote<network::mojom::URLLoaderFactory> factory;
GetNetworkContext()->CreateURLLoaderFactory(
factory.BindNewPipeAndPassReceiver(), std::move(params));
return factory;
}
network::ResourceRequest CreateSubresourceRequest(
const GURL& url,
std::string_view method = "GET") {
network::ResourceRequest request;
request.url = url;
request.method = std::string(method);
request.destination = network::mojom::RequestDestination::kScript;
request.mode = network::mojom::RequestMode::kCors;
request.credentials_mode = network::mojom::CredentialsMode::kInclude;
request.request_initiator = shell()
->web_contents()
->GetPrimaryMainFrame()
->GetLastCommittedOrigin();
return request;
}
int LoadRequest(network::mojom::URLLoaderFactory* factory,
const network::ResourceRequest& request) {
mojo::Remote<network::mojom::URLLoader> loader;
network::TestURLLoaderClient client;
factory->CreateLoaderAndStart(
loader.BindNewPipeAndPassReceiver(), 0,
network::mojom::kURLLoadOptionNone, request, client.CreateRemote(),
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
client.RunUntilComplete();
return client.completion_status().error_code;
}
private:
std::unique_ptr<TestContentBrowserClient> test_content_browser_client_;
base::test::ScopedFeatureList feature_list_;
};
class RendererAccessibleHttpCacheBrowserTest
: public RendererAccessibleHttpCacheBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
RendererAccessibleHttpCacheBrowserTest()
: RendererAccessibleHttpCacheBrowserTestBase(GetParam()) {}
~RendererAccessibleHttpCacheBrowserTest() override = default;
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
return info.param ? "WalEnabled" : "WalDisabled";
}
};
INSTANTIATE_TEST_SUITE_P(
,
RendererAccessibleHttpCacheBrowserTest,
testing::Bool(),
&RendererAccessibleHttpCacheBrowserTest::DescribeParams);
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest, Basic) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
GURL resource_url = GetURL("/loader/cacheable_jsonp.js");
FetchScript(resource_url);
FlushSharedCacheEligibleEntries();
auto shared_http_cache_client = InitSharedHttpCacheClient(page_url);
network::ResourceRequest request = CreateSubresourceRequest(resource_url);
base::RunLoop run_loop_for_find;
std::optional<network::SharedHttpCacheClient::Response> found_response;
shared_http_cache_client->Find(
request, base::MakeRefCounted<network::BasicDataBufferFactory>(),
base::BindLambdaForTesting(
[&](std::optional<network::SharedHttpCacheClient::Response>
response) {
found_response = std::move(response);
run_loop_for_find.Quit();
}),
base::SequencedTaskRunner::GetCurrentDefault());
run_loop_for_find.Run();
ASSERT_TRUE(found_response.has_value());
EXPECT_TRUE(found_response->head);
EXPECT_TRUE(found_response->head->was_fetched_via_cache);
EXPECT_FALSE(found_response->head->request_time.is_null());
EXPECT_FALSE(found_response->head->response_time.is_null());
EXPECT_LE(found_response->head->request_time,
found_response->head->response_time);
EXPECT_FALSE(found_response->head->load_timing.request_start.is_null());
EXPECT_FALSE(found_response->head->load_timing.receive_headers_end.is_null());
EXPECT_GE(found_response->head->load_timing.receive_headers_end,
found_response->head->load_timing.request_start);
ASSERT_TRUE(found_response->body);
std::string expected_script_content =
LoadTestFile("loader/cacheable_jsonp.js");
EXPECT_EQ(GetStringFromBuffers(found_response->body),
expected_script_content);
ReloadInNewProcess(page_url);
ResourceLoadObserver observer(shell()->web_contents());
base::TimeTicks before = base::TimeTicks::Now();
FetchScript(resource_url);
base::TimeTicks after = base::TimeTicks::Now();
ASSERT_EQ(1U, observer.resource_load_entries().size());
const auto& entry = observer.resource_load_entries()[0];
EXPECT_EQ(resource_url, entry.original_url);
EXPECT_TRUE(entry.resource_load_info->was_cached);
const net::LoadTimingInfo& timing =
entry.resource_load_info->load_timing_info;
EXPECT_FALSE(timing.request_start.is_null());
EXPECT_FALSE(timing.receive_headers_end.is_null());
EXPECT_LE(before, timing.request_start);
EXPECT_LE(timing.request_start, timing.receive_headers_end);
EXPECT_LE(timing.receive_headers_end, after);
EXPECT_EQ(base::ByteSize(expected_script_content.size()),
entry.resource_load_info->raw_body_bytes);
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest,
NetworkServiceCrash) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
EXPECT_TRUE(frame->document_associated_data()
.renderer_accessible_http_cache_write_enabled());
// Simulate a Network Service crash.
SimulateNetworkServiceCrash();
frame->FlushNetworkAndNavigationInterfacesForTesting(
/*do_nothing_if_no_network_service_connection=*/false);
// After crash recovery, DocumentAssociatedData preserves the flag so that
// SubresourceLoaderFactoriesConfig::ForLastCommittedNavigation can restore it
// on the recreated URLLoaderFactory.
EXPECT_TRUE(frame->document_associated_data()
.renderer_accessible_http_cache_write_enabled());
GURL resource_url = GetURL("/loader/cacheable_jsonp.js");
FetchScript(resource_url);
FlushSharedCacheEligibleEntries();
auto shared_http_cache_client = InitSharedHttpCacheClient(page_url);
network::ResourceRequest request = CreateSubresourceRequest(resource_url);
base::RunLoop run_loop_for_find;
std::optional<network::SharedHttpCacheClient::Response> found_response;
shared_http_cache_client->Find(
request, base::MakeRefCounted<network::BasicDataBufferFactory>(),
base::BindLambdaForTesting(
[&](std::optional<network::SharedHttpCacheClient::Response>
response) {
found_response = std::move(response);
run_loop_for_find.Quit();
}),
base::SequencedTaskRunner::GetCurrentDefault());
run_loop_for_find.Run();
ASSERT_TRUE(found_response.has_value());
EXPECT_TRUE(found_response->head);
EXPECT_TRUE(found_response->head->was_fetched_via_cache);
EXPECT_FALSE(found_response->head->request_time.is_null());
EXPECT_FALSE(found_response->head->response_time.is_null());
EXPECT_LE(found_response->head->request_time,
found_response->head->response_time);
EXPECT_FALSE(found_response->head->load_timing.request_start.is_null());
EXPECT_FALSE(found_response->head->load_timing.receive_headers_end.is_null());
EXPECT_GE(found_response->head->load_timing.receive_headers_end,
found_response->head->load_timing.request_start);
ASSERT_TRUE(found_response->body);
std::string expected_script_content =
LoadTestFile("loader/cacheable_jsonp.js");
EXPECT_EQ(GetStringFromBuffers(found_response->body),
expected_script_content);
ReloadInNewProcess(page_url);
ResourceLoadObserver observer(shell()->web_contents());
base::TimeTicks before = base::TimeTicks::Now();
FetchScript(resource_url);
base::TimeTicks after = base::TimeTicks::Now();
ASSERT_EQ(1U, observer.resource_load_entries().size());
const auto& entry = observer.resource_load_entries()[0];
EXPECT_EQ(resource_url, entry.original_url);
EXPECT_TRUE(entry.resource_load_info->was_cached);
const net::LoadTimingInfo& timing =
entry.resource_load_info->load_timing_info;
EXPECT_FALSE(timing.request_start.is_null());
EXPECT_FALSE(timing.receive_headers_end.is_null());
EXPECT_LE(before, timing.request_start);
EXPECT_LE(timing.request_start, timing.receive_headers_end);
EXPECT_LE(timing.receive_headers_end, after);
EXPECT_EQ(base::ByteSize(expected_script_content.size()),
entry.resource_load_info->raw_body_bytes);
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest,
AccessControlAllowOrigin) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
GURL wildcard_url = GetURL("/custom_resource/acao_wildcard.js");
GURL origin_url = GetURL("/custom_resource/acao_origin.js");
FetchScript(wildcard_url);
FetchScript(origin_url);
FlushSharedCacheEligibleEntries();
auto shared_http_cache_client = InitSharedHttpCacheClient(page_url);
// Responses with Access-Control-Allow-Origin: * should be stored in the
// Shared Cache.
EXPECT_TRUE(
CheckResourceInSharedCache(shared_http_cache_client, wildcard_url));
// Responses with Access-Control-Allow-Origin: <origin> (not "*") must not be
// stored in the Shared Cache.
EXPECT_FALSE(
CheckResourceInSharedCache(shared_http_cache_client, origin_url));
ReloadInNewProcess(page_url);
FetchScript(wildcard_url);
FetchScript(origin_url);
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest,
CrossOriginResourcePolicy) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
GURL cross_origin_url = GetURL("/custom_resource/corp_cross_origin.js");
GURL same_origin_url = GetURL("/custom_resource/corp_same_origin.js");
FetchScript(cross_origin_url);
FetchScript(same_origin_url);
FlushSharedCacheEligibleEntries();
auto shared_http_cache_client = InitSharedHttpCacheClient(page_url);
// Responses with Cross-Origin-Resource-Policy: cross-origin should be stored
// in the Shared Cache.
EXPECT_TRUE(
CheckResourceInSharedCache(shared_http_cache_client, cross_origin_url));
// Responses with Cross-Origin-Resource-Policy: same-origin must not be stored
// in the Shared Cache.
EXPECT_FALSE(
CheckResourceInSharedCache(shared_http_cache_client, same_origin_url));
ReloadInNewProcess(page_url);
FetchScript(cross_origin_url);
FetchScript(same_origin_url);
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest, PartialContent) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
GURL partial_url = GetURL("/custom_resource/partial.js");
FetchScript(partial_url);
auto shared_http_cache_client =
EnsureSharedCacheCreatedAndInitClient(page_url);
// Partial responses (HTTP 206) must not be stored in the Shared Cache.
EXPECT_FALSE(
CheckResourceInSharedCache(shared_http_cache_client, partial_url));
ReloadInNewProcess(page_url);
FetchScript(partial_url);
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest, NonGetRequest) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
mojo::Remote<network::mojom::URLLoaderFactory> factory =
CreateDirectURLLoaderFactory(
/*renderer_accessible_http_cache_write_enabled=*/true);
GURL resource_url = GetURL("/custom_resource/acao_wildcard.js");
// A compromised renderer (or future renderer behavior changes) might issue a
// POST request for a static script resource.
network::ResourceRequest request =
CreateSubresourceRequest(resource_url, "POST");
EXPECT_EQ(net::OK, LoadRequest(factory.get(), request));
auto shared_http_cache_client =
EnsureSharedCacheCreatedAndInitClient(page_url);
// Non-GET responses must not be stored in the Shared Cache.
EXPECT_FALSE(
CheckResourceInSharedCache(shared_http_cache_client, resource_url));
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest, RangeRequest) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
mojo::Remote<network::mojom::URLLoaderFactory> factory =
CreateDirectURLLoaderFactory(
/*renderer_accessible_http_cache_write_enabled=*/true);
GURL resource_url = GetURL("/custom_resource/acao_wildcard.js");
// A compromised renderer (or future renderer behavior changes) might issue a
// request with a Range header for a static script resource.
network::ResourceRequest request = CreateSubresourceRequest(resource_url);
request.headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=0-10");
EXPECT_EQ(net::OK, LoadRequest(factory.get(), request));
auto shared_http_cache_client =
EnsureSharedCacheCreatedAndInitClient(page_url);
// Responses for range requests must not be stored in the Shared Cache.
EXPECT_FALSE(
CheckResourceInSharedCache(shared_http_cache_client, resource_url));
}
IN_PROC_BROWSER_TEST_P(RendererAccessibleHttpCacheBrowserTest,
RedirectedResponse) {
GURL page_url = GetURL("/loader/blank.html");
EXPECT_TRUE(NavigateToURL(shell()->web_contents(), page_url));
GURL redirect_url = GetURL("/custom_resource/redirect.js");
GURL redirect_target_url = GetURL("/custom_resource/acao_wildcard.js");
FetchScript(redirect_url);
auto shared_http_cache_client =
EnsureSharedCacheCreatedAndInitClient(page_url);
// Redirected responses must not be stored in the Shared Cache under either
// the initial URL or the redirect destination URL (since CorsURLLoader
// updates `request_.url` to the redirect destination before receiving the
// final response).
EXPECT_FALSE(
CheckResourceInSharedCache(shared_http_cache_client, redirect_url));
EXPECT_FALSE(CheckResourceInSharedCache(shared_http_cache_client,
redirect_target_url));
ReloadInNewProcess(page_url);
FetchScript(redirect_url);
}
} // namespace
} // namespace content