From f6bf008ffc4f980f0ea01bcb6f5cc9635536a3df Mon Sep 17 00:00:00 2001 From: Jan Varga Date: Sun, 28 Dec 2025 18:50:53 +0100 Subject: [PATCH] libservo: Add initial `NetworkManager::cache_entries` API (#41386) Add initial support in `NetworkManager` for listing entries currently stored in the HTTP cache. Each returned entry is identified by its cache key (URL). The necessary support has been added to the net crate to expose cache entry information. Testing: A new integration test has been added. --------- Signed-off-by: Jan Varga Signed-off-by: Martin Robinson Co-authored-by: Martin Robinson --- components/net/http_cache.rs | 10 +++- components/net/resource_thread.rs | 3 ++ components/servo/lib.rs | 1 + components/servo/network_manager.rs | 40 +++++++++++++++ components/servo/tests/network_manager.rs | 62 +++++++++++++++++++++-- components/shared/net/lib.rs | 21 ++++++++ 6 files changed, 132 insertions(+), 5 deletions(-) diff --git a/components/net/http_cache.rs b/components/net/http_cache.rs index 26c2b10c73a..9461b014fc5 100644 --- a/components/net/http_cache.rs +++ b/components/net/http_cache.rs @@ -22,7 +22,7 @@ use malloc_size_of_derive::MallocSizeOf; use net_traits::http_status::HttpStatus; use net_traits::request::Request; use net_traits::response::{HttpsState, Response, ResponseBody}; -use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming}; +use net_traits::{CacheEntryDescriptor, FetchMetadata, Metadata, ResourceFetchTiming}; use parking_lot::Mutex as ParkingLotMutex; use quick_cache::sync::{Cache, DefaultLifecycle, PlaceholderGuard}; use quick_cache::{DefaultHashBuilder, UnitWeighter}; @@ -807,6 +807,14 @@ impl HttpCache { } } + /// Returns descriptors for cache entries currently stored in this cache. + pub fn cache_entry_descriptors(&self) -> Vec { + self.entries + .iter() + .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string())) + .collect() + } + /// Clear the contents of this cache. pub fn clear(&self) { self.entries.clear(); diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 62105de4b82..c77526ed33a 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -526,6 +526,9 @@ impl ResourceChannelManager { history_states.remove(&history_state); } }, + CoreResourceMsg::GetCacheEntries(sender) => { + let _ = sender.send(http_state.http_cache.cache_entry_descriptors()); + }, CoreResourceMsg::ClearCache(sender) => { http_state.http_cache.clear(); if let Some(sender) = sender { diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 26db33c0d93..7d7ac5ffb6f 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -61,6 +61,7 @@ pub use webrender_api::units::{ DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint, DeviceVector2D, }; +pub use crate::network_manager::{CacheEntry, NetworkManager}; pub use crate::servo::{Servo, ServoBuilder, run_content_process}; pub use crate::servo_delegate::{ServoDelegate, ServoError}; pub use crate::site_data_manager::{SiteData, SiteDataManager, StorageType}; diff --git a/components/servo/network_manager.rs b/components/servo/network_manager.rs index 80fc52b570b..4f28a865a52 100644 --- a/components/servo/network_manager.rs +++ b/components/servo/network_manager.rs @@ -2,8 +2,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::collections::HashSet; + use net_traits::ResourceThreads; +#[derive(Clone, Debug, PartialEq)] +pub struct CacheEntry { + key: String, +} + +impl CacheEntry { + pub fn new(key: String) -> Self { + Self { key } + } + + pub fn key(&self) -> &str { + &self.key + } +} + /// Provides APIs for managing network-related state. /// /// `NetworkManager` is responsible for data owned by the networking layer, @@ -25,6 +42,29 @@ impl NetworkManager { } } + /// Returns cache entries currently stored in the HTTP cache. + /// + /// The returned list contains one [`CacheEntry`] per unique cache key + /// (URL) for which the networking layer currently maintains cached + /// responses. + /// + /// Both public and private browsing contexts are included in the result. + /// + /// Note: The networking layer currently only implements an in-memory HTTP + /// cache. Support for an on-disk cache is under development. + pub fn cache_entries(&self) -> Vec { + let public_entries = self.public_resource_threads.cache_entries(); + let private_entries = self.private_resource_threads.cache_entries(); + + let unique_keys: HashSet = public_entries + .into_iter() + .chain(private_entries) + .map(|entry| entry.key) + .collect(); + + unique_keys.into_iter().map(CacheEntry::new).collect() + } + /// Clears the network (HTTP) cache. /// /// This removes all cached network responses maintained by the networking diff --git a/components/servo/tests/network_manager.rs b/components/servo/tests/network_manager.rs index 1dcaf18d3ee..3889252b5d6 100644 --- a/components/servo/tests/network_manager.rs +++ b/components/servo/tests/network_manager.rs @@ -8,12 +8,57 @@ use std::rc::Rc; use http_body_util::combinators::BoxBody; use hyper::body::{Bytes, Incoming}; +use hyper::header::{self, HeaderValue}; use hyper::{Request as HyperRequest, Response as HyperResponse}; use net::test_util::{make_body, make_server}; -use servo::WebViewBuilder; +use servo::{CacheEntry, WebViewBuilder}; use crate::common::{ServoTest, WebViewDelegateImpl}; +#[test] +fn test_cache_entries() { + let servo_test = ServoTest::new(); + let servo = servo_test.servo(); + let delegate = Rc::new(WebViewDelegateImpl::default()); + let webview = WebViewBuilder::new(servo, servo_test.rendering_context.clone()) + .delegate(delegate.clone()) + .build(); + let delegate_clone = delegate.clone(); + servo_test.spin(move || !delegate_clone.url_changed.get()); + + let network_manager = servo.network_manager(); + + let cache_entries = network_manager.cache_entries(); + assert_eq!(cache_entries.len(), 0); + + static MESSAGE: &'static [u8] = b"\nHello"; + let handler = + move |_: HyperRequest, + response: &mut HyperResponse>| { + response.headers_mut().insert( + header::CACHE_CONTROL, + HeaderValue::from_static("max-age=3600"), + ); + *response.body_mut() = make_body(MESSAGE.to_vec()); + }; + + let (server, url) = make_server(handler); + let port = url.port().unwrap(); + + delegate.reset(); + webview.load(url.clone().into_url()); + let delegate_clone = delegate.clone(); + servo_test.spin(move || !delegate_clone.url_changed.get()); + + let _ = server.close(); + + let cache_entries = network_manager.cache_entries(); + assert_eq!( + &cache_entries, + &[CacheEntry::new(format!("http://localhost:{port}/")),] + ); +} + #[test] fn test_clear_cache() { let servo_test = ServoTest::new(); @@ -23,6 +68,10 @@ fn test_clear_cache() { let handler = move |_: HyperRequest, response: &mut HyperResponse>| { + response.headers_mut().insert( + header::CACHE_CONTROL, + HeaderValue::from_static("max-age=3600"), + ); *response.body_mut() = make_body(MESSAGE.to_vec()); }; let (server, url) = make_server(handler); @@ -38,8 +87,13 @@ fn test_clear_cache() { let _ = server.close(); - servo_test.servo().network_manager().clear_cache(); + let network_manager = servo_test.servo().network_manager(); - // TODO: Check that the cache was actually cleared once there's a way to - // check it. + let cache_entries = network_manager.cache_entries(); + assert_eq!(cache_entries.len(), 1); + + network_manager.clear_cache(); + + let cache_entries = network_manager.cache_entries(); + assert_eq!(cache_entries.len(), 0); } diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs index 6d0949af2b9..fe034ba051f 100644 --- a/components/shared/net/lib.rs +++ b/components/shared/net/lib.rs @@ -504,6 +504,14 @@ impl ResourceThreads { ResourceThreads { core_thread } } + pub fn cache_entries(&self) -> Vec { + let (sender, receiver) = generic_channel::channel().unwrap(); + let _ = self + .core_thread + .send(CoreResourceMsg::GetCacheEntries(sender)); + receiver.recv().unwrap() + } + pub fn clear_cache(&self) { // NOTE: Messages used in these methods are currently handled // synchronously on the backend without consulting other threads, so @@ -624,6 +632,8 @@ pub enum CoreResourceMsg { SetHistoryState(HistoryStateId, Vec), /// Removes history states for the given ids RemoveHistoryStates(Vec), + /// Gets a list of origin descriptors derived from entries in the cache + GetCacheEntries(GenericSender>), /// Clear the network cache. ClearCache(Option>), /// Send the service worker network mediator for an origin to CoreResourceThread @@ -646,6 +656,17 @@ impl SiteDescriptor { } } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CacheEntryDescriptor { + pub key: String, +} + +impl CacheEntryDescriptor { + pub fn new(key: String) -> Self { + Self { key } + } +} + // FIXME: https://github.com/servo/servo/issues/34591 #[expect(clippy::large_enum_variant)] enum ToFetchThreadMessage {