blob: 28556e56f2fd591c46ce6f48d39eb907876a499d [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/code_cache/generated_code_cache.h"
#include <optional>
#include "base/feature_list.h"
#include "base/i18n/time_formatting.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/renderer_host/code_cache_host_impl.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_process_host.h"
#include "content/public/browser/storage_partition.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/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/code_cache_util.h"
#include "third_party/blink/public/common/page/v8_compile_hints_histograms.h"
namespace content {
namespace {
bool SupportsSharedWorker() {
return base::FeatureList::IsEnabled(blink::features::kSharedWorker);
}
} // namespace
enum class CodeCacheTestCase {
kCachePartitioningEnabled,
kCachePartitioningDisabled,
};
std::string CodeCacheTestCaseToString(CodeCacheTestCase param) {
switch (param) {
case CodeCacheTestCase::kCachePartitioningEnabled:
return "CachePartitioningEnabled";
case CodeCacheTestCase::kCachePartitioningDisabled:
return "CachePartitioningDisabled";
}
}
enum class BackgroundResourceFetchTestCase {
kBackgroundResourceFetchEnabled,
kBackgroundResourceFetchDisabled,
};
std::string BackgroundResourceFetchTestCaseToString(
BackgroundResourceFetchTestCase param) {
switch (param) {
case BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled:
return "BackgroundResourceFetchEnabled";
case BackgroundResourceFetchTestCase::kBackgroundResourceFetchDisabled:
return "BackgroundResourceFetchDisabled";
}
}
class CodeCacheBrowserTest
: public ContentBrowserTest,
public testing::WithParamInterface<
std::pair<CodeCacheTestCase, BackgroundResourceFetchTestCase>> {
public:
CodeCacheBrowserTest() {
// Enable the split HTTP cache since the GeneratedCodeCache won't consider
// partitioning by NIK unless the HTTP cache does.
feature_split_cache_by_network_isolation_key_.InitAndEnableFeature(
net::features::kSplitCacheByNetworkIsolationKey);
// Enable third-party storage partitioning so that for the Shared Worker
// test, different Shared Workers are used in the third-party contexts with
// differing top-level sites.
feature_third_party_storage_partitioning_.InitAndEnableFeature(
net::features::kThirdPartyStoragePartitioning);
feature_split_code_cache_by_network_isolation_key_.InitWithFeatureState(
net::features::kSplitCodeCacheByNetworkIsolationKey,
IsCachePartitioningEnabled());
if (IsBackgroundResourceFetchEnabled()) {
feature_background_resource_fetch_.InitAndEnableFeature(
blink::features::kBackgroundResourceFetch);
} else {
feature_background_resource_fetch_.InitAndDisableFeature(
blink::features::kBackgroundResourceFetch);
}
}
bool IsCachePartitioningEnabled() const {
return GetParam().first == CodeCacheTestCase::kCachePartitioningEnabled;
}
bool IsBackgroundResourceFetchEnabled() const {
return GetParam().second ==
BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled;
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&CodeCacheBrowserTest::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);
// Worker scripts will fetch this once the cacheable resource has been
// loaded and the test logic (checking histograms) can continue.
if (absolute_url.path() == "/done.js") {
GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(done_callback_));
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->set_content("//done!");
http_response->set_content_type("application/javascript");
return http_response;
}
// Returns a JavaScript file that should be cacheable by the
// GeneratedCodeCache (>1024 characters).
if (absolute_url.path() == "/cacheable.js") {
if (trigger_validation_requests_ &&
base::Contains(request.headers, "If-Modified-Since")) {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_NOT_MODIFIED);
last_cache_js_response_code_ = net::HTTP_NOT_MODIFIED;
return http_response;
}
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
last_cache_js_response_code_ = net::HTTP_OK;
std::string content = "let variable = 'hello!';\n";
// Make sure the script is long enough to be eligible for caching.
for (int i = 0; i < 16; i++) {
content += std::string(64, '/') + '\n';
}
http_response->set_content(content);
http_response->set_content_type("application/javascript");
if (trigger_validation_requests_) {
http_response->AddCustomHeader("Age", "3000");
http_response->AddCustomHeader("Last-Modified",
base::TimeFormatHTTP(base::Time::Now()));
} else {
http_response->AddCustomHeader("Cache-Control", "max-age=100000");
}
return http_response;
}
// Returns an HTML file that will load /cacheable.js.
if (absolute_url.path() == "/cacheable.html") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
std::string content =
"<html><head><title>Title</title></head>"
"<script src='/cacheable.js'></script></html>";
http_response->set_content(content);
return http_response;
}
// Returns a JavaScript file that should itself be eligible for caching in
// the GeneratedCodeCache and that will load /cacheable.js via
// importScripts.
if (absolute_url.path() == "/worker.js") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
// Self-terminate the worker just after loading the scripts so that the
// parent context doesn't need to wait for the worker's termination when
// cleaning up the test.
std::string content = base::StringPrintf(
"importScripts('cacheable.js', 'done.js');"
"close();");
for (int i = 0; i < 16; i++) {
content += std::string(64, '/') + '\n';
}
http_response->set_content(content);
http_response->set_content_type("application/javascript");
return http_response;
}
// Return a page that will create a Shared Worker that uses /worker.js.
if (absolute_url.path() == "/shared-worker.html") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
std::string content =
"<html><head><title>Title</title></head>"
"<script>let w = new SharedWorker('worker.js');</script></html>";
http_response->set_content(content);
return http_response;
}
// Returns a JavaScript module file that should be cacheable by the
// GeneratedCodeCache (>1024 characters).
if (absolute_url.path() == "/cacheable_module.js") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
std::string content =
"const title = () => { return 'Module Loaded'; }\n"
"export default title\n";
// Make sure the script is long enough to be eligible for caching.
for (int i = 0; i < 16; i++) {
content += std::string(64, '/') + '\n';
}
http_response->set_content(content);
http_response->set_content_type("application/javascript");
http_response->AddCustomHeader("Cache-Control", "max-age=100000");
return http_response;
}
// Returns an HTML file that will load /cacheable_module.js.
if (absolute_url.path() == "/cacheable_module.html") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
std::string content =
"<html><head><title>Title</title></head>"
"<script type='module'>\n"
"import title from \"./cacheable_module.js\";\n"
"document.title = title();"
"</script></html>";
http_response->set_content(content);
return http_response;
}
return nullptr;
}
void LoadIframe(const GURL& url) {
// The script executed here isn't large enough to be eligible for the
// Generated Code Cache and therefore shouldn't affect any metrics by being
// run.
EXPECT_TRUE(
ExecJs(shell()->web_contents(),
JsReplace("const iframe = document.createElement('iframe');"
"iframe.src = $1;"
"document.body.appendChild(iframe);",
url)));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
protected:
void PurgeResourceCacheFromTheMainFrame() {
base::RunLoop loop;
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root()
->current_frame_host()
->GetProcess()
->GetRendererInterface()
->PurgeResourceCache(loop.QuitClosure());
loop.Run();
}
void PurgeResourceCacheFromTheFirstSubFrame() {
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
CHECK(root->child_count() != 0);
base::RunLoop loop;
root->child_at(root->child_count() - 1)
->current_frame_host()
->GetProcess()
->GetRendererInterface()
->PurgeResourceCache(loop.QuitClosure());
loop.Run();
}
GeneratedCodeCacheContext* GetGeneratedCodeCacheContext() {
return shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition()
->GetGeneratedCodeCacheContext();
}
base::OnceClosure done_callback_;
bool trigger_validation_requests_ = false;
std::optional<net::HttpStatusCode> last_cache_js_response_code_;
private:
base::test::ScopedFeatureList feature_split_cache_by_network_isolation_key_;
base::test::ScopedFeatureList feature_third_party_storage_partitioning_;
base::test::ScopedFeatureList
feature_split_code_cache_by_network_isolation_key_;
base::test::ScopedFeatureList feature_background_resource_fetch_;
};
INSTANTIATE_TEST_SUITE_P(
All,
CodeCacheBrowserTest,
testing::Values(
std::make_pair(
CodeCacheTestCase::kCachePartitioningEnabled,
BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled),
std::make_pair(
CodeCacheTestCase::kCachePartitioningEnabled,
BackgroundResourceFetchTestCase::kBackgroundResourceFetchDisabled),
std::make_pair(
CodeCacheTestCase::kCachePartitioningDisabled,
BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled),
std::make_pair(
CodeCacheTestCase::kCachePartitioningDisabled,
BackgroundResourceFetchTestCase::kBackgroundResourceFetchDisabled)),
[](const testing::TestParamInfo<
std::pair<CodeCacheTestCase, BackgroundResourceFetchTestCase>>& info) {
return CodeCacheTestCaseToString(info.param.first) +
BackgroundResourceFetchTestCaseToString(info.param.second);
});
IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest, CachingFromThirdPartyFrames) {
GURL a_com_parent_page =
embedded_test_server()->GetURL("a.com", "/empty.html");
GURL b_com_parent_page =
embedded_test_server()->GetURL("b.com", "/empty.html");
GURL c_com_cacheable_js_page =
embedded_test_server()->GetURL("c.com", "/cacheable.html");
{
// Navigate to the parent page and load an iframe that requests a cacheable
// javascript resource (/cacheable.js). This should result in one
// GeneratedCodeCache miss and one create.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
LoadIframe(c_com_cacheable_js_page);
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 1);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kCreate, 1);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 0);
PurgeResourceCacheFromTheFirstSubFrame();
}
{
// Navigate to the same test page again and check for a GeneratedCodeCache
// hit.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
LoadIframe(c_com_cacheable_js_page);
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 1);
PurgeResourceCacheFromTheFirstSubFrame();
}
{
// Navigate to a page with a different top-level site and verify that:
//
// - When cache partitioning is disabled, /cacheable.js is served from the
// GeneratedCodeCache, since resources will be cached per-origin.
//
// - When cache partitioning is enabled, /cacheable.js is not served from
// the GeneratedCodeCache, since resources are also partitioned by the
// top-level site.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), b_com_parent_page));
LoadIframe(c_com_cacheable_js_page);
FetchHistogramsFromChildProcesses();
// Note: We don't check the kCreate counts below because for some reason,
// the previous part of the test causes the /cacheable.js entry to be doomed
// and then re-created in this part of the test (so the kCreate count is
// always one more than we'd expect).
if (IsCachePartitioningEnabled()) {
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 1);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 0);
} else {
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 1);
}
}
}
IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest, CachingFromIFrame) {
GURL a_com_parent_page =
embedded_test_server()->GetURL("a.com", "/empty.html");
const std::string_view kLoadCacheableJSInIframeScript = R"(
(async () => {
await new Promise(resolve => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const script = iframe.contentWindow.document.createElement('script');
script.addEventListener('load', resolve);
script.src = '/cacheable.js';
iframe.contentWindow.document.body.appendChild(script);
});
})();
)";
{
// Navigate to the parent page and load an iframe that requests a cacheable
// javascript resource (/cacheable.js) in subframe.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
kLoadCacheableJSInIframeScript));
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 1);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kCreate, 1);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 0);
PurgeResourceCacheFromTheFirstSubFrame();
}
{
// Navigate to the same test page again, code cache will be produced.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
kLoadCacheableJSInIframeScript));
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 1);
PurgeResourceCacheFromTheFirstSubFrame();
}
{
// Navigate to the same test page again, code cache will be consumed.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
kLoadCacheableJSInIframeScript));
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 1);
PurgeResourceCacheFromTheFirstSubFrame();
}
}
IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest,
CachingFromThirdPartySharedWorkers) {
if (!SupportsSharedWorker()) {
return;
}
GURL a_com_parent_page =
embedded_test_server()->GetURL("a.com", "/empty.html");
GURL b_com_parent_page =
embedded_test_server()->GetURL("b.com", "/empty.html");
GURL c_com_worker_js_page =
embedded_test_server()->GetURL("c.com", "/shared-worker.html");
{
// Navigate to a parent page and create a cross-site iframe that creates a
// SharedWorker which attempts to load a cacheable script via importScripts.
// Create a callback that will get called when the worker requests /done.js,
// indicating that /worker.js and /cacheable.js have been loaded and we can
// proceed with checking histograms.
base::test::TestFuture<void> worker_done;
done_callback_ = worker_done.GetCallback();
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
LoadIframe(c_com_worker_js_page);
ASSERT_TRUE(worker_done.Wait());
FetchHistogramsFromChildProcesses();
// TODO(crbug.com/40628019): The kMiss and kCreate counts are zero
// here because for Dedicated Workers and Shared Workers the Generated Code
// Cache isn't used yet. Once it is, update these counts (there are two
// scripts that could be cached, depending on the implementation -
// worker.js, the script that contains the Shared Worker code, and
// cacheable.js, which is loaded by the Shared Worker via a call to
// importScripts).
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kCreate, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 0);
}
{
base::test::TestFuture<void> worker_done;
done_callback_ = worker_done.GetCallback();
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), a_com_parent_page));
LoadIframe(c_com_worker_js_page);
ASSERT_TRUE(worker_done.Wait());
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
// TODO(crbug.com/40628019): Once the Generated Code Cache is used for
// Shared Workers, check that the scripts loaded from the worker context
// were re-used from the test section above.
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 0);
}
{
base::test::TestFuture<void> worker_done;
done_callback_ = worker_done.GetCallback();
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), b_com_parent_page));
LoadIframe(c_com_worker_js_page);
ASSERT_TRUE(worker_done.Wait());
FetchHistogramsFromChildProcesses();
// TODO(crbug.com/40628019): Once the Generated Code Cache is used
// for Shared Workers, check that the worker scripts re-used in the test
// section above do not get re-used for this part of the test when
// `IsCachePartitioningEnabled()` returns true (but are re-used otherwise).
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kMiss, 0);
histogram_tester.ExpectBucketCount(
"SiteIsolatedCodeCache.JS.Behaviour",
GeneratedCodeCache::CacheEntryStatus::kHit, 0);
}
}
class CodeCacheSizeChecker {
public:
CodeCacheSizeChecker(GeneratedCodeCacheContext* cache_context,
const GURL& url,
const GURL& origin,
size_t expected_size)
: cache_context_(cache_context),
url_(url),
origin_(origin),
expected_size_(expected_size) {}
size_t Wait() {
base::test::TestFuture<void> done;
done_callback_ = done.GetCallback();
GeneratedCodeCacheContext::GetTaskRunner(cache_context_)
->PostTask(FROM_HERE,
base::BindOnce(&CodeCacheSizeChecker::GetCodeCache,
base::Unretained(this)));
CHECK(done.Wait());
return expected_size_;
}
private:
void GetCodeCache() {
net::NetworkIsolationKey nik = net::NetworkIsolationKey(
net::SchemefulSite(origin_), net::SchemefulSite(origin_));
cache_context_->generated_js_code_cache()->FetchEntry(
url_, GURL(), nik,
base::BindOnce(&CodeCacheSizeChecker::FetchCallback,
base::Unretained(this)));
}
void FetchCallback(const base::Time&, mojo_base::BigBuffer data) {
if (data.size() >= expected_size_) {
GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(done_callback_));
} else {
// Retries because the CodeCacheHost's IPC may delay.
GeneratedCodeCacheContext::GetTaskRunner(cache_context_)
->PostDelayedTask(FROM_HERE,
base::BindOnce(&CodeCacheSizeChecker::GetCodeCache,
base::Unretained(this)),
base::Microseconds(100));
}
}
scoped_refptr<GeneratedCodeCacheContext> cache_context_;
const GURL url_;
const GURL origin_;
const size_t expected_size_;
base::OnceClosure done_callback_;
};
IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest,
GeneratedCodeCacheSizeClassicScript) {
// With this, we can query the code cache in a unified way in platforms which
// use origin locks differently.
CodeCacheHostImpl::SetUseEmptySecondaryKeyForTesting();
GURL url = embedded_test_server()->GetURL("c.com", "/cacheable.html");
EXPECT_TRUE(NavigateToURL(shell(), url));
GeneratedCodeCacheContext* cache_context = GetGeneratedCodeCacheContext();
// Wait until compile hints were written into the cache.
const GURL& cacheable_script =
embedded_test_server()->GetURL("c.com", "/cacheable.js");
CodeCacheSizeChecker code_cache_size_checker(
cache_context, cacheable_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize);
EXPECT_EQ(blink::kCodeCacheTimestampCachedMetaSize,
code_cache_size_checker.Wait());
// Clear Blink side cache.
PurgeResourceCacheFromTheMainFrame();
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
// Navigate to the same page.
EXPECT_TRUE(NavigateToURL(shell(), url));
// We expect that the generated code cache is larger than the timestamp data.
CodeCacheSizeChecker code_cache_size_checker2(
cache_context, cacheable_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize + 1);
code_cache_size_checker2.Wait();
}
// Validation requests are updating response time in the http cache so we need
// to verify that such cases are handled correctly. This test triggers code that
// compares the timestamps between the http cache and code cache, which is used
// to determine whether the code cache is valid or not. If there is a mismatch
// in timestamps the code cache will be dropped.
IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest, KeepCodeCacheWhenNotModified) {
// With this, we can query the code cache in a unified way in platforms which
// use origin locks differently.
CodeCacheHostImpl::SetUseEmptySecondaryKeyForTesting();
// Vital part of this test since http 304 responses change the response time
// even though the content did not change.
trigger_validation_requests_ = true;
const GURL url = embedded_test_server()->GetURL("c.com", "/cacheable.html");
EXPECT_TRUE(NavigateToURL(shell(), url));
GeneratedCodeCacheContext* cache_context = GetGeneratedCodeCacheContext();
// Wait until compile hints were written into the cache.
const GURL cacheable_script =
embedded_test_server()->GetURL("c.com", "/cacheable.js");
// This is the size of the meta data header and the stored response time which
// is the only actual data when there is no generated code in the cache.
CodeCacheSizeChecker code_cache_size_checker(
cache_context, cacheable_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize);
EXPECT_EQ(blink::kCodeCacheTimestampCachedMetaSize,
code_cache_size_checker.Wait());
// Clear Blink side cache.
PurgeResourceCacheFromTheMainFrame();
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
// Navigate to the same page. This step will put compiled code in the cache.
EXPECT_TRUE(NavigateToURL(shell(), url));
// We expect that the generated code cache is larger than the timestamp data.
// This means that we are storing generated code in the cache in addition to
// the metadata, thereof the blink::kCodeCacheTimestampCachedMetaSize + 1
// below.
CodeCacheSizeChecker code_cache_size_checker2(
cache_context, cacheable_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize + 1);
code_cache_size_checker2.Wait();
ASSERT_TRUE(last_cache_js_response_code_.has_value());
ASSERT_EQ(net::HTTP_NOT_MODIFIED, last_cache_js_response_code_.value());
// Clear Blink side cache.
PurgeResourceCacheFromTheMainFrame();
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
last_cache_js_response_code_.reset();
// Navigate to the same page a third time. This time the code cache should be
// used and the data on disk should be kept even if we got a 304 response.
EXPECT_TRUE(NavigateToURL(shell(), url));
// We expect that the generated code cache is larger than the timestamp data.
CodeCacheSizeChecker code_cache_size_checker3(
cache_context, cacheable_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize + 1);
code_cache_size_checker3.Wait();
ASSERT_TRUE(last_cache_js_response_code_.has_value());
ASSERT_EQ(net::HTTP_NOT_MODIFIED, last_cache_js_response_code_.value());
}
IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest,
GeneratedCodeCacheSizeModuleScript) {
// With this, we can query the code cache in a unified way in platforms which
// use origin locks differently.
CodeCacheHostImpl::SetUseEmptySecondaryKeyForTesting();
GURL url = embedded_test_server()->GetURL("c.com", "/cacheable_module.html");
const std::u16string expected_title = u"Module Loaded";
{
TitleWatcher watcher(shell()->web_contents(), expected_title);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_EQ(expected_title, watcher.WaitAndGetTitle());
}
GeneratedCodeCacheContext* cache_context = GetGeneratedCodeCacheContext();
// Wait until compile hints were written into the cache.
const GURL& cacheable_module_script =
embedded_test_server()->GetURL("c.com", "/cacheable_module.js");
CodeCacheSizeChecker code_cache_size_checker(
cache_context, cacheable_module_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize);
EXPECT_EQ(blink::kCodeCacheTimestampCachedMetaSize,
code_cache_size_checker.Wait());
// Clear Blink side cache.
PurgeResourceCacheFromTheMainFrame();
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
// Navigate to the same page.
{
TitleWatcher watcher(shell()->web_contents(), expected_title);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_EQ(expected_title, watcher.WaitAndGetTitle());
}
// We expect that the generated code cache is larger than the timestamp data.
CodeCacheSizeChecker code_cache_size_checker2(
cache_context, cacheable_module_script,
embedded_test_server()->GetURL("c.com", "/"),
blink::kCodeCacheTimestampCachedMetaSize + 1);
code_cache_size_checker2.Wait();
}
class CompileHintsBrowserTest : public ContentBrowserTest {
public:
CompileHintsBrowserTest() = default;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&CompileHintsBrowserTest::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);
// Returns a JavaScript file that should be cacheable by the
// GeneratedCodeCache (>1024 characters).
if (absolute_url.path() == "/cacheable.js") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader("Cache-Control", "max-age=604800");
// The script has to include a function so that we can test compile hints
// with it.
std::string content = R"(
let variable = 'hello!';
function foo() {}
foo();
)";
// Make sure the script is long enough to be eligible for caching.
for (int i = 0; i < 16; i++) {
content += std::string(64, '/') + '\n';
}
http_response->set_content(content);
http_response->set_content_type("application/javascript");
return http_response;
}
// Returns an HTML file that will load /cacheable.js.
if (absolute_url.path() == "/cacheable.html") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
std::string content =
"<html><head><title>Title</title></head>"
"<script src='/cacheable.js'></script></html>";
http_response->set_content(content);
return http_response;
}
return nullptr;
}
};
class LocalCompileHintsBrowserTest : public CompileHintsBrowserTest {
public:
LocalCompileHintsBrowserTest() {
local_compile_hints_.InitAndEnableFeature(
blink::features::kLocalCompileHints);
interactive_detector_ignore_fcp_.InitAndEnableFeature(
blink::features::kInteractiveDetectorIgnoreFcp);
}
private:
base::test::ScopedFeatureList local_compile_hints_;
base::test::ScopedFeatureList interactive_detector_ignore_fcp_;
};
class NoLocalCompileHintsBrowserTest : public CompileHintsBrowserTest {
public:
NoLocalCompileHintsBrowserTest() {
local_compile_hints_.InitAndDisableFeature(
blink::features::kLocalCompileHints);
}
private:
base::test::ScopedFeatureList local_compile_hints_;
};
IN_PROC_BROWSER_TEST_F(NoLocalCompileHintsBrowserTest, NoCompileHints) {
// TODO(chromium:1495723): Migrate this test to use use counters once we no
// longer have the histograms.
// With this, we can query the code cache in a unified way in platforms which
// use origin locks differently.
CodeCacheHostImpl::SetUseEmptySecondaryKeyForTesting();
GURL cacheable_page =
embedded_test_server()->GetURL("c.com", "/cacheable.html");
GURL other_page = embedded_test_server()->GetURL("a.com", "/empty.html");
{
// Navigate to the page which requests a cacheable
// javascript resource (/cacheable.js).
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), cacheable_page));
FetchHistogramsFromChildProcesses();
EXPECT_EQ(
1, histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::
kNoCompileHintsClassicNonStreaming) +
histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::kNoCompileHintsStreaming));
}
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), other_page));
{
// Navigate to the same test page again and check that local compile hints
// weren't hit.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), cacheable_page));
FetchHistogramsFromChildProcesses();
EXPECT_EQ(
1, histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::
kNoCompileHintsClassicNonStreaming) +
histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::kNoCompileHintsStreaming));
}
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), other_page));
{
// Navigate to the same test page again and check for a code cache hit.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), cacheable_page));
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::kConsumeCodeCacheClassicNonStreaming,
1);
}
}
IN_PROC_BROWSER_TEST_F(LocalCompileHintsBrowserTest, LocalCompileHints) {
// TODO(chromium:1495723): Migrate this test to use use counters once we no
// longer have the histograms.
// With this, we can query the code cache in a unified way in platforms which
// use origin locks differently.
CodeCacheHostImpl::SetUseEmptySecondaryKeyForTesting();
GURL cacheable_page =
embedded_test_server()->GetURL("c.com", "/cacheable.html");
GURL other_page = embedded_test_server()->GetURL("a.com", "/empty.html");
{
// Navigate to the page which requests a cacheable
// javascript resource (/cacheable.js). Once the page is interactive, local
// compile hints will be generated.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), cacheable_page));
GeneratedCodeCacheContext* cache_context =
shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition()
->GetGeneratedCodeCacheContext();
// Wait until compile hints were written into the cache.
const GURL& cacheable_script =
embedded_test_server()->GetURL("c.com", "/cacheable.js");
constexpr int kCompileHintsCacheSize = 28; // Tag + actual data.
CodeCacheSizeChecker code_cache_size_checker(
cache_context, cacheable_script, GURL("http://c.com/"),
kCompileHintsCacheSize);
code_cache_size_checker.Wait();
FetchHistogramsFromChildProcesses();
EXPECT_EQ(1, histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::
kProduceCompileHintsClassicNonStreaming) +
histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::
kProduceCompileHintsStreaming));
EXPECT_EQ(1,
histogram_tester.GetBucketCount(
blink::v8_compile_hints::kLocalCompileHintsGeneratedHistogram,
blink::v8_compile_hints::LocalCompileHintsGenerated::kFinal));
}
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), other_page));
{
// Navigate to the same test page again and check for a local compile hints
// hit.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), cacheable_page));
FetchHistogramsFromChildProcesses();
EXPECT_EQ(1, histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::
kConsumeLocalCompileHintsClassicNonStreaming) +
histogram_tester.GetBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::
kConsumeLocalCompileHintsStreaming));
}
// Navigate away.
EXPECT_TRUE(NavigateToURL(shell(), other_page));
{
// Navigate to the same test page again and check for a code cache hit.
base::HistogramTester histogram_tester;
EXPECT_TRUE(NavigateToURL(shell(), cacheable_page));
FetchHistogramsFromChildProcesses();
histogram_tester.ExpectBucketCount(
blink::v8_compile_hints::kStatusHistogram,
blink::v8_compile_hints::Status::kConsumeCodeCacheClassicNonStreaming,
1);
}
}
} // namespace content