LibWeb: Preserve JS bytecode in memory cache
Store JavaScript bytecode side data in the WebContent HTTP memory cache and replay it when serving cached responses. Also update an already-complete memory-cache entry when asynchronous bytecode cache generation finishes, so the first source-only response does not keep shadowing the disk-cache sidecar during same-process navigations. Keep the HTTP memory-cache backfill keyed with the request headers that populated the memory-cache entry, so Vary responses still receive their generated bytecode sidecar. Add LibHTTP coverage for round-tripping bytecode side data through a memory-cache entry, attaching it after the response body has already been cached, and matching Vary headers during updates. Add LibWeb coverage for preserving the memory-cache request headers when cloning responses.
This commit is contained in:
parent
221219e00c
commit
72720bc229
11 changed files with 242 additions and 5 deletions
|
|
@ -15,6 +15,14 @@ NonnullRefPtr<MemoryCache> MemoryCache::create()
|
|||
return adopt_ref(*new MemoryCache());
|
||||
}
|
||||
|
||||
// A stored response satisfies a request only if the request header fields nominated by the response's Vary header
|
||||
// match those of the request that produced the entry, per RFC 9111 4.1. open_entry() and update_javascript_bytecode_cache()
|
||||
// must select entries identically, so share the predicate.
|
||||
static bool entry_matches_request(HeaderList const& request_headers, MemoryCache::Entry const& entry)
|
||||
{
|
||||
return create_vary_key(request_headers, entry.response_headers) == entry.vary_key;
|
||||
}
|
||||
|
||||
// https://httpwg.org/specs/rfc9111.html#constructing.responses.from.caches
|
||||
Optional<MemoryCache::Entry const&> MemoryCache::open_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, CacheMode cache_mode)
|
||||
{
|
||||
|
|
@ -38,7 +46,7 @@ Optional<MemoryCache::Entry const&> MemoryCache::open_entry(URL::URL const& url,
|
|||
|
||||
// - request header fields nominated by the stored response (if any) match those presented (see Section 4.1), and
|
||||
auto cache_entry = find_value(*cache_entries, [&](auto const& entry) {
|
||||
return create_vary_key(request_headers, entry.response_headers) == entry.vary_key;
|
||||
return entry_matches_request(request_headers, entry);
|
||||
});
|
||||
if (!cache_entry.has_value()) {
|
||||
dbgln_if(HTTP_MEMORY_CACHE_DEBUG, "\033[37m[memory]\033[0m \033[35;1mVary mismatch for\033[0m {}", url);
|
||||
|
|
@ -75,7 +83,7 @@ Optional<MemoryCache::Entry const&> MemoryCache::open_entry(URL::URL const& url,
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
void MemoryCache::create_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, UnixDateTime request_time, u32 status_code, ByteString reason_phrase, HeaderList const& response_headers)
|
||||
void MemoryCache::create_entry(URL::URL const& url, StringView method, HeaderList const& request_headers, UnixDateTime request_time, u32 status_code, ByteString reason_phrase, HeaderList const& response_headers, Optional<Core::ImmutableBytes> javascript_bytecode_cache, Optional<u64> javascript_bytecode_cache_vary_key)
|
||||
{
|
||||
if (!is_cacheable(method, request_headers))
|
||||
return;
|
||||
|
|
@ -99,6 +107,8 @@ void MemoryCache::create_entry(URL::URL const& url, StringView method, HeaderLis
|
|||
.request_headers = move(request_headers_copy),
|
||||
.response_headers = move(response_headers_copy),
|
||||
.response_body = {},
|
||||
.javascript_bytecode_cache = move(javascript_bytecode_cache),
|
||||
.javascript_bytecode_cache_vary_key = javascript_bytecode_cache_vary_key,
|
||||
.request_time = request_time,
|
||||
.response_time = UnixDateTime::now(),
|
||||
};
|
||||
|
|
@ -139,4 +149,33 @@ void MemoryCache::finalize_entry(URL::URL const& url, StringView method, HeaderL
|
|||
}
|
||||
}
|
||||
|
||||
void MemoryCache::update_javascript_bytecode_cache(URL::URL const& url, StringView method, HeaderList const& request_headers, u64 vary_key, Core::ImmutableBytes javascript_bytecode_cache)
|
||||
{
|
||||
if (!is_cacheable(method, request_headers))
|
||||
return;
|
||||
|
||||
auto serialized_url = serialize_url_for_cache_storage(url);
|
||||
auto cache_key = create_cache_key(serialized_url, method);
|
||||
|
||||
// Attach the generated bytecode to every cache entry this request would select. Match entries the same way
|
||||
// open_entry() does, by re-deriving the vary key from the request and the entry's stored response headers, rather
|
||||
// than trusting the supplied vary key (which was computed in another process and can diverge for Vary responses).
|
||||
// The supplied vary key is still kept as javascript_bytecode_cache_vary_key so it can be replayed onto a served
|
||||
// response.
|
||||
auto update_entries = [&](Vector<Entry>& entries) {
|
||||
for (auto& entry : entries) {
|
||||
if (!entry_matches_request(request_headers, entry))
|
||||
continue;
|
||||
entry.javascript_bytecode_cache = javascript_bytecode_cache;
|
||||
entry.javascript_bytecode_cache_vary_key = vary_key;
|
||||
}
|
||||
};
|
||||
|
||||
if (auto cache_entries = m_complete_entries.get(cache_key); cache_entries.has_value())
|
||||
update_entries(*cache_entries);
|
||||
|
||||
if (auto cache_entries = m_pending_entries.get(cache_key); cache_entries.has_value())
|
||||
update_entries(*cache_entries);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public:
|
|||
NonnullRefPtr<HeaderList> request_headers;
|
||||
NonnullRefPtr<HeaderList> response_headers;
|
||||
Core::ImmutableBytes response_body;
|
||||
Optional<Core::ImmutableBytes> javascript_bytecode_cache;
|
||||
Optional<u64> javascript_bytecode_cache_vary_key;
|
||||
|
||||
UnixDateTime request_time;
|
||||
UnixDateTime response_time;
|
||||
|
|
@ -37,8 +39,9 @@ public:
|
|||
|
||||
Optional<Entry const&> open_entry(URL::URL const&, StringView method, HeaderList const& request_headers, CacheMode);
|
||||
|
||||
void create_entry(URL::URL const&, StringView method, HeaderList const& request_headers, UnixDateTime request_time, u32 status_code, ByteString reason_phrase, HeaderList const& response_headers);
|
||||
void create_entry(URL::URL const&, StringView method, HeaderList const& request_headers, UnixDateTime request_time, u32 status_code, ByteString reason_phrase, HeaderList const& response_headers, Optional<Core::ImmutableBytes> javascript_bytecode_cache = {}, Optional<u64> javascript_bytecode_cache_vary_key = {});
|
||||
void finalize_entry(URL::URL const&, StringView method, HeaderList const& request_headers, u32 status_code, HeaderList const& response_headers, Core::ImmutableBytes response_body);
|
||||
void update_javascript_bytecode_cache(URL::URL const&, StringView method, HeaderList const& request_headers, u64 vary_key, Core::ImmutableBytes javascript_bytecode_cache);
|
||||
|
||||
private:
|
||||
HashMap<u64, Vector<Entry>, IdentityHashTraits<u64>> m_pending_entries;
|
||||
|
|
|
|||
|
|
@ -93,6 +93,14 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
HTTP::MemoryCache* get_if_exists(Infrastructure::NetworkPartitionKey const& key)
|
||||
{
|
||||
auto it = m_cache.find(key);
|
||||
if (it == m_cache.end())
|
||||
return nullptr;
|
||||
return it->value.ptr();
|
||||
}
|
||||
|
||||
static HTTPCache& the()
|
||||
{
|
||||
static HTTPCache& cache = *new HTTPCache;
|
||||
|
|
@ -138,6 +146,9 @@ static GC::Ptr<Infrastructure::Response> select_response_from_cache(JS::Realm& r
|
|||
response->set_status(cache_entry->status_code);
|
||||
response->set_status_message(cache_entry->reason_phrase);
|
||||
response->set_header_list(cache_entry->response_headers);
|
||||
response->set_javascript_bytecode_cache(cache_entry->javascript_bytecode_cache);
|
||||
response->set_javascript_bytecode_cache_vary_key(cache_entry->javascript_bytecode_cache_vary_key);
|
||||
response->set_javascript_bytecode_cache_memory_cache_request_headers(HTTP::HeaderList::create(cache_entry->request_headers->headers()));
|
||||
|
||||
auto [response_body, _] = safely_extract_body(realm, cache_entry->response_body.bytes());
|
||||
response->set_body(response_body);
|
||||
|
|
@ -145,14 +156,15 @@ static GC::Ptr<Infrastructure::Response> select_response_from_cache(JS::Realm& r
|
|||
return response;
|
||||
}
|
||||
|
||||
static void store_response_in_cache(HTTP::MemoryCache& http_cache, Infrastructure::Request const& request, Infrastructure::Response const& response)
|
||||
static void store_response_in_cache(HTTP::MemoryCache& http_cache, Infrastructure::Request const& request, Infrastructure::Response& response)
|
||||
{
|
||||
if (!g_http_memory_cache_enabled)
|
||||
return;
|
||||
if (request.cache_mode() == HTTP::CacheMode::NoStore)
|
||||
return;
|
||||
|
||||
http_cache.create_entry(request.current_url(), request.method(), request.header_list(), request.request_time(), response.status(), response.status_message(), response.header_list());
|
||||
response.set_javascript_bytecode_cache_memory_cache_request_headers(HTTP::HeaderList::create(request.header_list()->headers()));
|
||||
http_cache.create_entry(request.current_url(), request.method(), request.header_list(), request.request_time(), response.status(), response.status_message(), response.header_list(), response.javascript_bytecode_cache(), response.javascript_bytecode_cache_vary_key());
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-fetch
|
||||
|
|
@ -2610,4 +2622,14 @@ void clear_http_memory_cache()
|
|||
HTTPCache::the().clear_cache();
|
||||
}
|
||||
|
||||
void update_javascript_bytecode_cache_in_http_memory_cache(Infrastructure::NetworkPartitionKey const& partition_key, URL::URL const& url, ByteString const& method, HTTP::HeaderList const& request_headers, u64 vary_key, Core::ImmutableBytes javascript_bytecode_cache)
|
||||
{
|
||||
if (!g_http_memory_cache_enabled)
|
||||
return;
|
||||
|
||||
// Only back-fill into a partition that already has a cache; do not create an empty one just to drop bytecode into.
|
||||
if (auto* http_cache = HTTPCache::the().get_if_exists(partition_key))
|
||||
http_cache->update_javascript_bytecode_cache(url, method, request_headers, vary_key, move(javascript_bytecode_cache));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@
|
|||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibCore/ImmutableBytes.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibHTTP/Cookie/IncludeCredentials.h>
|
||||
#include <LibHTTP/Forward.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibURL/Forward.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/NetworkPartitionKey.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::Fetch::Fetching {
|
||||
|
|
@ -60,5 +63,6 @@ void append_fetch_metadata_headers_for_request(Infrastructure::Request&);
|
|||
WEB_API void set_http_memory_cache_enabled(bool enabled);
|
||||
WEB_API bool http_memory_cache_enabled();
|
||||
WEB_API void clear_http_memory_cache();
|
||||
void update_javascript_bytecode_cache_in_http_memory_cache(Infrastructure::NetworkPartitionKey const&, URL::URL const&, ByteString const& method, HTTP::HeaderList const& request_headers, u64 vary_key, Core::ImmutableBytes);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,6 +182,8 @@ GC::Ref<Response> Response::clone(JS::Realm& realm) const
|
|||
new_response->set_body_info(m_body_info);
|
||||
new_response->set_javascript_bytecode_cache(m_javascript_bytecode_cache);
|
||||
new_response->set_javascript_bytecode_cache_vary_key(m_javascript_bytecode_cache_vary_key);
|
||||
if (m_javascript_bytecode_cache_memory_cache_request_headers.has_value())
|
||||
new_response->set_javascript_bytecode_cache_memory_cache_request_headers(HTTP::HeaderList::create((*m_javascript_bytecode_cache_memory_cache_request_headers)->headers()));
|
||||
// FIXME: service worker timing info
|
||||
|
||||
// 3. If response’s body is non-null, then set newResponse’s body to the result of cloning response’s body.
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ public:
|
|||
void set_javascript_bytecode_cache(Optional<Core::ImmutableBytes> javascript_bytecode_cache) { m_javascript_bytecode_cache = move(javascript_bytecode_cache); }
|
||||
[[nodiscard]] Optional<u64> javascript_bytecode_cache_vary_key() const { return m_javascript_bytecode_cache_vary_key; }
|
||||
void set_javascript_bytecode_cache_vary_key(Optional<u64> javascript_bytecode_cache_vary_key) { m_javascript_bytecode_cache_vary_key = javascript_bytecode_cache_vary_key; }
|
||||
[[nodiscard]] Optional<NonnullRefPtr<HTTP::HeaderList>> const& javascript_bytecode_cache_memory_cache_request_headers() const { return m_javascript_bytecode_cache_memory_cache_request_headers; }
|
||||
void set_javascript_bytecode_cache_memory_cache_request_headers(Optional<NonnullRefPtr<HTTP::HeaderList>> request_headers) { m_javascript_bytecode_cache_memory_cache_request_headers = move(request_headers); }
|
||||
|
||||
[[nodiscard]] RedirectTaint redirect_taint() const { return m_redirect_taint; }
|
||||
void set_redirect_taint(RedirectTaint redirect_taint) { m_redirect_taint = redirect_taint; }
|
||||
|
|
@ -203,6 +205,7 @@ private:
|
|||
Optional<String> m_network_error_message;
|
||||
Optional<Core::ImmutableBytes> m_javascript_bytecode_cache;
|
||||
Optional<u64> m_javascript_bytecode_cache_vary_key;
|
||||
Optional<NonnullRefPtr<HTTP::HeaderList>> m_javascript_bytecode_cache_memory_cache_request_headers;
|
||||
|
||||
public:
|
||||
[[nodiscard]] ByteString const& method() const { return m_method; }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include <LibWeb/Fetch/Infrastructure/HTTP/MIME.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/NetworkPartitionKey.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/URL.h>
|
||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||
#include <LibWeb/HTML/HTMLScriptElement.h>
|
||||
|
|
@ -59,7 +60,9 @@ struct BytecodeCacheContext {
|
|||
URL::URL url;
|
||||
ByteString method;
|
||||
NonnullRefPtr<HTTP::HeaderList> request_headers;
|
||||
RefPtr<HTTP::HeaderList> memory_cache_request_headers;
|
||||
u64 vary_key { 0 };
|
||||
Optional<Fetch::Infrastructure::NetworkPartitionKey> memory_cache_partition_key;
|
||||
};
|
||||
|
||||
using BytecodeCacheSourceHash = ::Crypto::Hash::Digest<::Crypto::Hash::SHA256::DigestSize * 8>;
|
||||
|
|
@ -156,11 +159,17 @@ static Optional<BytecodeCacheContext> bytecode_cache_context_for_request(Fetch::
|
|||
if (!vary_key.has_value())
|
||||
return {};
|
||||
|
||||
RefPtr<HTTP::HeaderList> memory_cache_request_headers;
|
||||
if (auto const& response_request_headers = response.javascript_bytecode_cache_memory_cache_request_headers(); response_request_headers.has_value())
|
||||
memory_cache_request_headers = HTTP::HeaderList::create((*response_request_headers)->headers());
|
||||
|
||||
return BytecodeCacheContext {
|
||||
.url = response_url,
|
||||
.method = request.method(),
|
||||
.request_headers = HTTP::HeaderList::create(request.header_list()->headers()),
|
||||
.memory_cache_request_headers = move(memory_cache_request_headers),
|
||||
.vary_key = *vary_key,
|
||||
.memory_cache_partition_key = Fetch::Infrastructure::determine_the_network_partition_key(request),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +197,8 @@ static void schedule_bytecode_cache_generation(NonnullRefPtr<JS::SourceCode cons
|
|||
if (!ResourceLoader::is_initialized() || !ResourceLoader::the().request_client())
|
||||
return;
|
||||
(void)ResourceLoader::the().request_client()->store_cache_associated_data(cache_context.url, cache_context.method, *cache_context.request_headers, cache_context.vary_key, HTTP::CacheEntryAssociatedData::JavaScriptBytecode, immutable_blob.bytes());
|
||||
if (cache_context.memory_cache_partition_key.has_value() && cache_context.memory_cache_request_headers)
|
||||
Fetch::Fetching::update_javascript_bytecode_cache_in_http_memory_cache(*cache_context.memory_cache_partition_key, cache_context.url, cache_context.method, *cache_context.memory_cache_request_headers, cache_context.vary_key, immutable_blob);
|
||||
});
|
||||
|
||||
Threading::ThreadPool::the().submit([filename = move(filename), source_code = move(source_code), type, line_number_offset, callback, &main_thread_event_loop, source_hash]() mutable {
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@ endforeach()
|
|||
|
||||
ladybird_test("TestCacheIndex.cpp" LibWeb LIBS LibHTTP LibDatabase)
|
||||
ladybird_test("TestDiskCache.cpp" LibWeb LIBS LibHTTP LibURL)
|
||||
ladybird_test("TestMemoryCache.cpp" LibWeb LIBS LibHTTP LibURL)
|
||||
|
|
|
|||
119
Tests/LibHTTP/TestMemoryCache.cpp
Normal file
119
Tests/LibHTTP/TestMemoryCache.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/ImmutableBytes.h>
|
||||
#include <LibHTTP/Cache/MemoryCache.h>
|
||||
#include <LibHTTP/Cache/Utilities.h>
|
||||
#include <LibHTTP/HeaderList.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
#include <LibURL/Parser.h>
|
||||
|
||||
static URL::URL parse_url(StringView url)
|
||||
{
|
||||
return URL::Parser::basic_parse(url).release_value();
|
||||
}
|
||||
|
||||
static NonnullRefPtr<HTTP::HeaderList> create_cacheable_request_headers()
|
||||
{
|
||||
return HTTP::HeaderList::create({
|
||||
{ "Accept"sv, "*/*"sv },
|
||||
});
|
||||
}
|
||||
|
||||
static NonnullRefPtr<HTTP::HeaderList> create_cacheable_response_headers()
|
||||
{
|
||||
return HTTP::HeaderList::create({
|
||||
{ "Cache-Control"sv, "max-age=60"sv },
|
||||
});
|
||||
}
|
||||
|
||||
static NonnullRefPtr<HTTP::HeaderList> create_vary_user_agent_response_headers()
|
||||
{
|
||||
return HTTP::HeaderList::create({
|
||||
{ "Cache-Control"sv, "max-age=60"sv },
|
||||
{ "Vary"sv, "User-Agent"sv },
|
||||
});
|
||||
}
|
||||
|
||||
static Core::ImmutableBytes immutable_bytes(StringView bytes)
|
||||
{
|
||||
return MUST(Core::ImmutableBytes::copy(bytes.bytes()));
|
||||
}
|
||||
|
||||
TEST_CASE(javascript_bytecode_cache_round_trips_with_memory_cache_entry)
|
||||
{
|
||||
auto cache = HTTP::MemoryCache::create();
|
||||
auto url = parse_url("https://example.com/script.js"sv);
|
||||
auto request_headers = create_cacheable_request_headers();
|
||||
auto response_headers = create_cacheable_response_headers();
|
||||
auto bytecode = immutable_bytes("cached bytecode"sv);
|
||||
|
||||
cache->create_entry(url, "GET"sv, *request_headers, UnixDateTime::now(), 200, "OK"sv, *response_headers, bytecode, 0);
|
||||
cache->finalize_entry(url, "GET"sv, *request_headers, 200, *response_headers, immutable_bytes("console.log('hello');"sv));
|
||||
|
||||
auto entry = cache->open_entry(url, "GET"sv, *request_headers, HTTP::CacheMode::Default);
|
||||
VERIFY(entry.has_value());
|
||||
VERIFY(entry->javascript_bytecode_cache.has_value());
|
||||
EXPECT_EQ(entry->javascript_bytecode_cache->bytes(), bytecode.bytes());
|
||||
EXPECT_EQ(entry->javascript_bytecode_cache_vary_key, Optional<u64> { 0 });
|
||||
}
|
||||
|
||||
TEST_CASE(javascript_bytecode_cache_update_matches_memory_cache_vary_headers)
|
||||
{
|
||||
auto cache = HTTP::MemoryCache::create();
|
||||
auto url = parse_url("https://example.com/script.js"sv);
|
||||
auto script_request_headers = create_cacheable_request_headers();
|
||||
auto cache_request_headers = HTTP::HeaderList::create({
|
||||
{ "Accept"sv, "*/*"sv },
|
||||
{ "User-Agent"sv, "Ladybird"sv },
|
||||
});
|
||||
auto response_headers = create_vary_user_agent_response_headers();
|
||||
auto bytecode = immutable_bytes("generated bytecode"sv);
|
||||
auto vary_key = HTTP::create_vary_key(*cache_request_headers, *response_headers);
|
||||
|
||||
cache->create_entry(url, "GET"sv, *cache_request_headers, UnixDateTime::now(), 200, "OK"sv, *response_headers);
|
||||
cache->finalize_entry(url, "GET"sv, *cache_request_headers, 200, *response_headers, immutable_bytes("console.log('hello');"sv));
|
||||
|
||||
cache->update_javascript_bytecode_cache(url, "GET"sv, *script_request_headers, vary_key, bytecode);
|
||||
|
||||
auto entry = cache->open_entry(url, "GET"sv, *cache_request_headers, HTTP::CacheMode::Default);
|
||||
VERIFY(entry.has_value());
|
||||
EXPECT(!entry->javascript_bytecode_cache.has_value());
|
||||
EXPECT(!entry->javascript_bytecode_cache_vary_key.has_value());
|
||||
|
||||
cache->update_javascript_bytecode_cache(url, "GET"sv, *cache_request_headers, vary_key, bytecode);
|
||||
|
||||
entry = cache->open_entry(url, "GET"sv, *cache_request_headers, HTTP::CacheMode::Default);
|
||||
VERIFY(entry.has_value());
|
||||
VERIFY(entry->javascript_bytecode_cache.has_value());
|
||||
EXPECT_EQ(entry->javascript_bytecode_cache->bytes(), bytecode.bytes());
|
||||
EXPECT_EQ(entry->javascript_bytecode_cache_vary_key, Optional<u64> { vary_key });
|
||||
}
|
||||
|
||||
TEST_CASE(javascript_bytecode_cache_can_be_added_after_memory_cache_entry_is_complete)
|
||||
{
|
||||
auto cache = HTTP::MemoryCache::create();
|
||||
auto url = parse_url("https://example.com/script.js"sv);
|
||||
auto request_headers = create_cacheable_request_headers();
|
||||
auto response_headers = create_cacheable_response_headers();
|
||||
auto bytecode = immutable_bytes("generated bytecode"sv);
|
||||
|
||||
cache->create_entry(url, "GET"sv, *request_headers, UnixDateTime::now(), 200, "OK"sv, *response_headers);
|
||||
cache->finalize_entry(url, "GET"sv, *request_headers, 200, *response_headers, immutable_bytes("console.log('hello');"sv));
|
||||
|
||||
auto entry = cache->open_entry(url, "GET"sv, *request_headers, HTTP::CacheMode::Default);
|
||||
VERIFY(entry.has_value());
|
||||
EXPECT(!entry->javascript_bytecode_cache.has_value());
|
||||
EXPECT(!entry->javascript_bytecode_cache_vary_key.has_value());
|
||||
|
||||
cache->update_javascript_bytecode_cache(url, "GET"sv, *request_headers, 0, bytecode);
|
||||
|
||||
entry = cache->open_entry(url, "GET"sv, *request_headers, HTTP::CacheMode::Default);
|
||||
VERIFY(entry.has_value());
|
||||
VERIFY(entry->javascript_bytecode_cache.has_value());
|
||||
EXPECT_EQ(entry->javascript_bytecode_cache->bytes(), bytecode.bytes());
|
||||
EXPECT_EQ(entry->javascript_bytecode_cache_vary_key, Optional<u64> { 0 });
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ set(TEST_SOURCES
|
|||
TestCSSSyntaxParser.cpp
|
||||
TestCSSTokenizer.cpp
|
||||
TestCSSTokenStream.cpp
|
||||
TestFetchResponse.cpp
|
||||
TestFetchURL.cpp
|
||||
TestHTMLTokenizer.cpp
|
||||
TestMicrosyntax.cpp
|
||||
|
|
@ -27,6 +28,7 @@ ladybird_utility(css-tokenizer SOURCES css-tokenizer.cpp LIBS LibFileSystem LibM
|
|||
|
||||
target_link_libraries(TestContentBlocker PRIVATE LibURL)
|
||||
target_link_libraries(TestControlMessageQueue PRIVATE LibSync)
|
||||
target_link_libraries(TestFetchResponse PRIVATE LibGC LibHTTP LibJS)
|
||||
target_link_libraries(TestFetchURL PRIVATE LibURL)
|
||||
target_link_libraries(TestSecureContexts PRIVATE LibURL)
|
||||
target_link_libraries(TestSourceHighlighter PRIVATE LibURL LibWebView)
|
||||
|
|
|
|||
31
Tests/LibWeb/TestFetchResponse.cpp
Normal file
31
Tests/LibWeb/TestFetchResponse.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibHTTP/HeaderList.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
||||
|
||||
TEST_CASE(javascript_bytecode_cache_memory_cache_request_headers_are_cloned)
|
||||
{
|
||||
auto vm = JS::VM::create();
|
||||
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
||||
auto& realm = *root_execution_context->realm;
|
||||
|
||||
auto request_headers = HTTP::HeaderList::create({
|
||||
{ "User-Agent"sv, "Ladybird"sv },
|
||||
});
|
||||
auto response = Web::Fetch::Infrastructure::Response::create(*vm);
|
||||
response->set_javascript_bytecode_cache_memory_cache_request_headers(request_headers);
|
||||
|
||||
auto cloned_response = response->clone(realm);
|
||||
auto const& cloned_request_headers = cloned_response->javascript_bytecode_cache_memory_cache_request_headers();
|
||||
request_headers->set({ "User-Agent"sv, "Changed"sv });
|
||||
|
||||
VERIFY(cloned_request_headers.has_value());
|
||||
EXPECT_EQ((*cloned_request_headers)->get("User-Agent"sv), Optional<ByteString> { "Ladybird" });
|
||||
}
|
||||
Loading…
Reference in a new issue