From c0b19ff9815517989b03304463102142c278e9aa Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 15 May 2026 16:34:54 +0200 Subject: [PATCH] RequestServer: Send bytecode cache sidecars as files Map JavaScript bytecode cache sidecars from the HTTP disk cache instead of copying them into anonymous shared buffers while handing response headers to WebContent. Store the mapped data as ImmutableBytes on the fetch response so script fetching can decode directly from the mapped sidecar bytes. Add LibHTTP coverage for retrieving associated cache data as a mappable file, alongside the existing byte-buffer retrieval API. --- Libraries/LibHTTP/Cache/DiskCache.cpp | 36 +++++++++++++++++++ Libraries/LibHTTP/Cache/DiskCache.h | 1 + Libraries/LibRequests/Request.cpp | 2 +- Libraries/LibRequests/Request.h | 9 +++-- Libraries/LibRequests/RequestClient.cpp | 28 ++++++++++++++- Libraries/LibRequests/RequestClient.h | 2 +- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 2 +- .../Fetch/Infrastructure/HTTP/Responses.h | 8 ++--- Libraries/LibWeb/Loader/ResourceLoader.h | 4 +-- Libraries/LibWebView/Autocomplete.cpp | 2 +- Libraries/LibWebView/FileDownloader.cpp | 2 +- Services/RequestServer/Request.cpp | 15 ++++---- Services/RequestServer/RequestClient.ipc | 3 +- Tests/LibHTTP/TestDiskCache.cpp | 5 +++ 14 files changed, 91 insertions(+), 28 deletions(-) diff --git a/Libraries/LibHTTP/Cache/DiskCache.cpp b/Libraries/LibHTTP/Cache/DiskCache.cpp index 00714a195a..dee87eb7e8 100644 --- a/Libraries/LibHTTP/Cache/DiskCache.cpp +++ b/Libraries/LibHTTP/Cache/DiskCache.cpp @@ -286,6 +286,42 @@ ErrorOr> DiskCache::retrieve_associated_data(URL::URL const return TRY(file.value()->read_until_eof()); } +ErrorOr> DiskCache::retrieve_associated_data_file(URL::URL const& url, StringView method, HeaderList const& request_headers, Optional vary_key, CacheEntryAssociatedData associated_data) +{ + if (!is_cacheable(method, request_headers)) + return Optional {}; + + auto serialized_url = serialize_url_for_cache_storage(url); + auto cache_key = create_cache_key(serialized_url, method, m_partitioned_cache_key); + if (!vary_key.has_value()) { + auto index_entry = m_index.find_entry(cache_key, request_headers); + if (!index_entry.has_value()) + return Optional {}; + vary_key = index_entry->vary_key; + } + + if (!m_index.has_entry(cache_key, *vary_key)) + return Optional {}; + + auto path = path_for_cache_entry_associated_data(m_cache_directory, cache_key, *vary_key, associated_data); + auto file = Core::File::open(path.string(), Core::File::OpenMode::Read); + if (file.is_error()) { + if (file.error().is_errno() && file.error().code() == ENOENT) + return Optional {}; + return file.release_error(); + } + + auto size = TRY(file.value()->size()); + if (!AK::is_within_range(size)) + return Error::from_errno(EOVERFLOW); + + return CacheEntryBodyFile { + .fd = file.value()->leak_fd(), + .offset = 0, + .size = static_cast(size), + }; +} + bool DiskCache::check_if_cache_has_open_entry(CacheRequest& request, u64 cache_key, URL::URL const& url, CheckReaderEntries check_reader_entries) { // FIXME: We purposefully do not use the vary key here, as we do not yet have it when creating a CacheEntryWriter diff --git a/Libraries/LibHTTP/Cache/DiskCache.h b/Libraries/LibHTTP/Cache/DiskCache.h index b95e34037c..9f17ca63c2 100644 --- a/Libraries/LibHTTP/Cache/DiskCache.h +++ b/Libraries/LibHTTP/Cache/DiskCache.h @@ -56,6 +56,7 @@ public: ErrorOr store_associated_data(URL::URL const&, StringView method, HeaderList const& request_headers, Optional vary_key, CacheEntryAssociatedData, ReadonlyBytes); ErrorOr> retrieve_associated_data(URL::URL const&, StringView method, HeaderList const& request_headers, Optional vary_key, CacheEntryAssociatedData); + ErrorOr> retrieve_associated_data_file(URL::URL const&, StringView method, HeaderList const& request_headers, Optional vary_key, CacheEntryAssociatedData); void remove_entries_exceeding_cache_limit(); void set_maximum_disk_cache_size(u64 maximum_disk_cache_size); diff --git a/Libraries/LibRequests/Request.cpp b/Libraries/LibRequests/Request.cpp index 45b3f77cf8..727dd66c9c 100644 --- a/Libraries/LibRequests/Request.cpp +++ b/Libraries/LibRequests/Request.cpp @@ -188,7 +188,7 @@ void Request::did_finish(Badge, u64 total_size, RequestTimingInfo on_finish(total_size, timing_info, effective_network_error); } -void Request::did_receive_headers(Badge, NonnullRefPtr response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key) +void Request::did_receive_headers(Badge, NonnullRefPtr response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key) { if (on_headers_received) on_headers_received(move(response_headers), response_code, reason_phrase, move(javascript_bytecode), javascript_bytecode_cache_vary_key); diff --git a/Libraries/LibRequests/Request.h b/Libraries/LibRequests/Request.h index 7544955f8e..d2e221b351 100644 --- a/Libraries/LibRequests/Request.h +++ b/Libraries/LibRequests/Request.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -90,13 +89,13 @@ public: int fd() const { return m_fd; } bool stop(); - using BufferedRequestFinished = Function const& network_error, NonnullRefPtr response_headers, Optional response_code, Optional reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key, Core::ImmutableBytes payload)>; + using BufferedRequestFinished = Function const& network_error, NonnullRefPtr response_headers, Optional response_code, Optional reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key, Core::ImmutableBytes payload)>; // Configure the request such that the entirety of the response data is buffered. The callback receives that data and // the response headers all at once. Using this method is mutually exclusive with `set_unbuffered_data_received_callback`. void set_buffered_request_finished_callback(BufferedRequestFinished); - using HeadersReceived = Function response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key)>; + using HeadersReceived = Function response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key)>; using DataReceived = Function; using CachedBodyAvailable = Function; using RequestFinished = Function network_error)>; @@ -108,7 +107,7 @@ public: Function on_certificate_requested; void did_finish(Badge, u64 total_size, RequestTimingInfo const& timing_info, Optional const& network_error); - void did_receive_headers(Badge, NonnullRefPtr response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key); + void did_receive_headers(Badge, NonnullRefPtr response_headers, Optional response_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key); void did_request_certificates(Badge); RefPtr& write_notifier(Badge) { return m_write_notifier; } @@ -143,7 +142,7 @@ private: NonnullRefPtr response_headers; Optional response_code; Optional reason_phrase; - Optional javascript_bytecode; + Optional javascript_bytecode; Optional javascript_bytecode_cache_vary_key; Optional payload; }; diff --git a/Libraries/LibRequests/RequestClient.cpp b/Libraries/LibRequests/RequestClient.cpp index 1a40b00108..7cbe8d7e05 100644 --- a/Libraries/LibRequests/RequestClient.cpp +++ b/Libraries/LibRequests/RequestClient.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -12,6 +13,27 @@ namespace Requests { +static Optional map_javascript_bytecode_file(int fd, u64 size) +{ + ArmedScopeGuard close_fd = [fd] { + (void)Core::System::close(fd); + }; + + if (!AK::is_within_range(size)) { + dbgln("RequestClient: Received JavaScript bytecode cache file outside mappable range"); + return {}; + } + + close_fd.disarm(); + auto payload = Core::ImmutableBytes::map_from_fd_range_and_close(fd, "javascript bytecode cache"sv, 0, static_cast(size)); + if (payload.is_error()) { + dbgln("RequestClient: Failed to map JavaScript bytecode cache file: {}", payload.error()); + return {}; + } + + return payload.release_value(); +} + RequestClient::RequestClient(NonnullOwnPtr transport) : IPC::ConnectionToServer(*this, move(transport)) { @@ -162,8 +184,12 @@ void RequestClient::request_finished(u64 request_id, u64 total_size, RequestTimi } } -void RequestClient::headers_became_available(u64 request_id, Vector response_headers, Optional status_code, Optional reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key) +void RequestClient::headers_became_available(u64 request_id, Vector response_headers, Optional status_code, Optional reason_phrase, Optional javascript_bytecode_file, u64 javascript_bytecode_size, Optional javascript_bytecode_cache_vary_key) { + Optional javascript_bytecode; + if (javascript_bytecode_file.has_value()) + javascript_bytecode = map_javascript_bytecode_file(javascript_bytecode_file->take_fd(), javascript_bytecode_size); + if (auto request = m_requests.get(request_id); request.has_value()) (*request)->did_receive_headers({}, HTTP::HeaderList::create(move(response_headers)), status_code, reason_phrase, move(javascript_bytecode), javascript_bytecode_cache_vary_key); else diff --git a/Libraries/LibRequests/RequestClient.h b/Libraries/LibRequests/RequestClient.h index e6b78746fc..24e7a5be4e 100644 --- a/Libraries/LibRequests/RequestClient.h +++ b/Libraries/LibRequests/RequestClient.h @@ -57,7 +57,7 @@ private: virtual void request_body_file_available(u64 request_id, IPC::File, u64 offset, u64 size) override; virtual void request_cached_body_file_available(u64 request_id, IPC::File, u64 offset, u64 size) override; virtual void request_finished(u64 request_id, u64, RequestTimingInfo, Optional) override; - virtual void headers_became_available(u64 request_id, Vector, Optional, Optional, Optional, Optional) override; + virtual void headers_became_available(u64 request_id, Vector, Optional, Optional, Optional, u64 javascript_bytecode_size, Optional) override; virtual void retrieve_http_cookie(int client_id, u64 request_id, RequestServer::RequestType request_type, URL::URL url) override; diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 9ba8d5fed6..beca7aef56 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -2188,7 +2188,7 @@ GC::Ref nonstandard_resource_loader_file_or_http_network_fetch( // 13. Set up stream with byte reading support with pullAlgorithm set to pullAlgorithm, cancelAlgorithm set to cancelAlgorithm. stream->set_up_with_byte_reading_support(pull_algorithm, cancel_algorithm); - auto on_headers_received = GC::create_function(vm.heap(), [&vm, pending_response, stream, request, fetched_data_receiver](HTTP::HeaderList const& response_headers, Optional status_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key) { + auto on_headers_received = GC::create_function(vm.heap(), [&vm, pending_response, stream, request, fetched_data_receiver](HTTP::HeaderList const& response_headers, Optional status_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key) { if (pending_response->is_resolved()) { // RequestServer will send us the response headers twice, the second time being for HTTP trailers. This // fetch algorithm is not interested in trailers, so just drop them here. diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h index cd700f8978..a2c66ce0ef 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -106,8 +106,8 @@ public: [[nodiscard]] virtual BodyInfo const& body_info() const { return m_body_info; } virtual void set_body_info(BodyInfo body_info) { m_body_info = move(body_info); } - [[nodiscard]] Optional const& javascript_bytecode_cache() const { return m_javascript_bytecode_cache; } - void set_javascript_bytecode_cache(Optional javascript_bytecode_cache) { m_javascript_bytecode_cache = move(javascript_bytecode_cache); } + [[nodiscard]] Optional const& javascript_bytecode_cache() const { return m_javascript_bytecode_cache; } + void set_javascript_bytecode_cache(Optional javascript_bytecode_cache) { m_javascript_bytecode_cache = move(javascript_bytecode_cache); } [[nodiscard]] Optional javascript_bytecode_cache_vary_key() const { return m_javascript_bytecode_cache_vary_key; } void set_javascript_bytecode_cache_vary_key(Optional javascript_bytecode_cache_vary_key) { m_javascript_bytecode_cache_vary_key = javascript_bytecode_cache_vary_key; } @@ -201,7 +201,7 @@ private: MonotonicTime m_monotonic_response_time; Optional m_network_error_message; - Optional m_javascript_bytecode_cache; + Optional m_javascript_bytecode_cache; Optional m_javascript_bytecode_cache_vary_key; public: diff --git a/Libraries/LibWeb/Loader/ResourceLoader.h b/Libraries/LibWeb/Loader/ResourceLoader.h index 7ca8a18ac3..ff3bdf5c49 100644 --- a/Libraries/LibWeb/Loader/ResourceLoader.h +++ b/Libraries/LibWeb/Loader/ResourceLoader.h @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -33,7 +33,7 @@ public: void set_client(NonnullRefPtr); - using OnHeadersReceived = GC::Function status_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key)>; + using OnHeadersReceived = GC::Function status_code, Optional const& reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key)>; using OnDataReceived = GC::Function; using OnCachedBodyAvailable = GC::Function; using OnComplete = GC::Function error_message)>; diff --git a/Libraries/LibWebView/Autocomplete.cpp b/Libraries/LibWebView/Autocomplete.cpp index 701e6ab253..79c562318e 100644 --- a/Libraries/LibWebView/Autocomplete.cpp +++ b/Libraries/LibWebView/Autocomplete.cpp @@ -304,7 +304,7 @@ void Autocomplete::query_autocomplete_engine(String query, size_t max_suggestion m_request = Application::request_server_client().start_request("GET"sv, *url); m_request->set_buffered_request_finished_callback( - [this, engine = engine.release_value(), query = m_query, literal_suggestion, search_suggestion](u64, Requests::RequestTimingInfo const&, Optional const& network_error, HTTP::HeaderList const& response_headers, Optional response_code, Optional const& reason_phrase, Optional, Optional, Core::ImmutableBytes payload) { + [this, engine = engine.release_value(), query = m_query, literal_suggestion, search_suggestion](u64, Requests::RequestTimingInfo const&, Optional const& network_error, HTTP::HeaderList const& response_headers, Optional response_code, Optional const& reason_phrase, Optional, Optional, Core::ImmutableBytes payload) { Core::deferred_invoke([this]() { m_request.clear(); }); if (m_query != query) { diff --git a/Libraries/LibWebView/FileDownloader.cpp b/Libraries/LibWebView/FileDownloader.cpp index 5dbb75f512..a4db696f77 100644 --- a/Libraries/LibWebView/FileDownloader.cpp +++ b/Libraries/LibWebView/FileDownloader.cpp @@ -42,7 +42,7 @@ void FileDownloader::download_file(URL::URL const& url, LexicalPath destination) auto request_id = next_request_id++; request->set_buffered_request_finished_callback( - [this, request_id, destination = move(destination)](u64, Requests::RequestTimingInfo const&, Optional const& network_error, HTTP::HeaderList const&, Optional response_code, Optional const& reason_phrase, Optional, Optional, Core::ImmutableBytes payload) { + [this, request_id, destination = move(destination)](u64, Requests::RequestTimingInfo const&, Optional const& network_error, HTTP::HeaderList const&, Optional response_code, Optional const& reason_phrase, Optional, Optional, Core::ImmutableBytes payload) { Core::deferred_invoke([this, request_id]() { m_requests.remove(request_id); }); if (network_error.has_value()) { diff --git a/Services/RequestServer/Request.cpp b/Services/RequestServer/Request.cpp index c75e6fccbe..5f193c0b8a 100644 --- a/Services/RequestServer/Request.cpp +++ b/Services/RequestServer/Request.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -1198,24 +1197,22 @@ void Request::transfer_headers_to_client_if_needed() } } - Optional javascript_bytecode; + Optional javascript_bytecode; + u64 javascript_bytecode_size { 0 }; Optional javascript_bytecode_cache_vary_key; if (m_cache_status == CacheStatus::ReadFromCache && m_disk_cache.has_value()) { VERIFY(m_cache_entry_reader.has_value()); javascript_bytecode_cache_vary_key = m_cache_entry_reader->vary_key(); - auto data = m_disk_cache->retrieve_associated_data(m_url, m_method, *m_request_headers, javascript_bytecode_cache_vary_key, HTTP::CacheEntryAssociatedData::JavaScriptBytecode); + auto data = m_disk_cache->retrieve_associated_data_file(m_url, m_method, *m_request_headers, javascript_bytecode_cache_vary_key, HTTP::CacheEntryAssociatedData::JavaScriptBytecode); if (!data.is_error() && data.value().has_value()) { - auto buffer = Core::AnonymousBuffer::create_with_size(data.value()->size()); - if (!buffer.is_error()) { - memcpy(buffer.value().data(), data.value()->data(), data.value()->size()); - javascript_bytecode = buffer.release_value(); - } + javascript_bytecode_size = data.value()->size; + javascript_bytecode = IPC::File::adopt_fd(data.value()->fd); } } else if (m_cache_status == CacheStatus::WrittenToCache && m_cache_entry_writer.has_value()) { javascript_bytecode_cache_vary_key = m_cache_entry_writer->vary_key(); } - m_client.async_headers_became_available(m_request_id, m_response_headers->headers(), m_status_code, m_reason_phrase, move(javascript_bytecode), javascript_bytecode_cache_vary_key); + m_client.async_headers_became_available(m_request_id, m_response_headers->headers(), m_status_code, m_reason_phrase, move(javascript_bytecode), javascript_bytecode_size, javascript_bytecode_cache_vary_key); } ErrorOr Request::write_queued_bytes_without_blocking() diff --git a/Services/RequestServer/RequestClient.ipc b/Services/RequestServer/RequestClient.ipc index a517bb2671..837e20e340 100644 --- a/Services/RequestServer/RequestClient.ipc +++ b/Services/RequestServer/RequestClient.ipc @@ -1,4 +1,3 @@ -#include #include #include #include @@ -12,7 +11,7 @@ endpoint RequestClient request_body_file_available(u64 request_id, IPC::File fd, u64 offset, u64 size) =| request_cached_body_file_available(u64 request_id, IPC::File fd, u64 offset, u64 size) =| request_finished(u64 request_id, u64 total_size, Requests::RequestTimingInfo timing_info, Optional network_error) =| - headers_became_available(u64 request_id, Vector response_headers, Optional status_code, Optional reason_phrase, Optional javascript_bytecode, Optional javascript_bytecode_cache_vary_key) =| + headers_became_available(u64 request_id, Vector response_headers, Optional status_code, Optional reason_phrase, Optional javascript_bytecode, u64 javascript_bytecode_size, Optional javascript_bytecode_cache_vary_key) =| retrieve_http_cookie(int client_id, u64 request_id, ::RequestServer::RequestType request_type, URL::URL url) =| diff --git a/Tests/LibHTTP/TestDiskCache.cpp b/Tests/LibHTTP/TestDiskCache.cpp index 11f4ba86d5..0f3a1daa6d 100644 --- a/Tests/LibHTTP/TestDiskCache.cpp +++ b/Tests/LibHTTP/TestDiskCache.cpp @@ -75,6 +75,11 @@ TEST_CASE(associated_data_round_trips_with_cache_entry) VERIFY(retrieved_bytecode.has_value()); EXPECT_EQ(retrieved_bytecode->bytes(), bytecode.bytes()); + auto retrieved_bytecode_file = TRY_OR_FAIL(disk_cache.retrieve_associated_data_file(url, "GET"sv, *request_headers, {}, HTTP::CacheEntryAssociatedData::JavaScriptBytecode)); + VERIFY(retrieved_bytecode_file.has_value()); + auto mapped_bytecode = TRY_OR_FAIL(Core::ImmutableBytes::map_from_fd_range_and_close(retrieved_bytecode_file->fd, "bytecode"sv, retrieved_bytecode_file->offset, retrieved_bytecode_file->size)); + EXPECT_EQ(mapped_bytecode.bytes(), bytecode.bytes()); + disk_cache.remove_entries_accessed_since(UnixDateTime::earliest()); retrieved_bytecode = TRY_OR_FAIL(disk_cache.retrieve_associated_data(url, "GET"sv, *request_headers, {}, HTTP::CacheEntryAssociatedData::JavaScriptBytecode));