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 {