From cf2b93f18a1c47da866d832e87789e4ce18d5f36 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Fri, 21 Feb 2025 15:36:42 +0100 Subject: [PATCH] libservo: Convert `intercept_web_resource_load` into `load_web_resource` (#35564) Rework the `WebViewDelegate::intercept_web_resource_load` into `WebViewDelegate::load_web_resource` and clean up internal messaging. The main thing here is adding objects which manage the response to these delegate methods. Now we have `WebResourceLoad` and `InterceptedWebResourceLoad` which make it much harder to misuse the API. In addition, the internal messaging for this is cleaned up. Canceling and finishing the load are unrelated to the HTTP body so they are no longer subtypes of an HttpBodyData message. Processing of messages is made a bit more efficient by collecting all body chunks in a vector and only flattening the chunks at the end. Finally, "interceptor" is a much more common spelling than "intercepter" so I've gone ahead and made this change everywhere. Signed-off-by: Martin Robinson --- Cargo.lock | 1 + components/malloc_size_of/lib.rs | 8 ++ components/net/fetch/methods.rs | 6 +- components/net/lib.rs | 2 +- components/net/request_intercepter.rs | 105 ------------------------- components/net/request_interceptor.rs | 86 +++++++++++++++++++++ components/net/resource_thread.rs | 10 +-- components/net/tests/fetch.rs | 24 +++--- components/net/tests/main.rs | 4 +- components/servo/lib.rs | 25 +++--- components/servo/servo_delegate.rs | 27 +++---- components/servo/webview_delegate.rs | 106 ++++++++++++++++++++++---- components/shared/embedder/Cargo.toml | 1 + components/shared/embedder/lib.rs | 48 ++++-------- 14 files changed, 245 insertions(+), 208 deletions(-) delete mode 100644 components/net/request_intercepter.rs create mode 100644 components/net/request_interceptor.rs diff --git a/Cargo.lock b/Cargo.lock index 5d8e04f5d23..591883b31d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1865,6 +1865,7 @@ dependencies = [ "serde", "servo_malloc_size_of", "servo_url", + "url", "webrender_api", "webxr-api", ] diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 50d4ce30718..241dba8eba9 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -648,6 +648,14 @@ impl MallocSizeOf for url::Host { } } +impl MallocSizeOf for url::Url { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + // TODO: This is an estimate, but a real size should be calculated in `rust-url` once + // it has support for `malloc_size_of`. + self.to_string().size_of(ops) + } +} + impl MallocSizeOf for euclid::Vector2D { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { self.x.size_of(ops) + self.y.size_of(ops) diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 9990b3f8478..cd23e61d60d 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -43,7 +43,7 @@ use crate::fetch::headers::determine_nosniff; use crate::filemanager_thread::FileManager; use crate::http_loader::{determine_requests_referrer, http_fetch, set_default_accept, HttpState}; use crate::protocols::ProtocolRegistry; -use crate::request_intercepter::RequestIntercepter; +use crate::request_interceptor::RequestInterceptor; use crate::subresource_integrity::is_response_integrity_valid; pub type Target<'a> = &'a mut (dyn FetchTaskTarget + Send); @@ -61,7 +61,7 @@ pub struct FetchContext { pub devtools_chan: Option>>>, pub filemanager: Arc>, pub file_token: FileTokenCheck, - pub request_intercepter: Arc>, + pub request_interceptor: Arc>, pub cancellation_listener: Arc, pub timing: ServoArc>, pub protocols: Arc, @@ -328,7 +328,7 @@ pub async fn main_fetch( // Intercept the request and maybe override the response. context - .request_intercepter + .request_interceptor .lock() .unwrap() .intercept_request(request, &mut response, context); diff --git a/components/net/lib.rs b/components/net/lib.rs index d97cf1ccd1a..0576c017e59 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -18,7 +18,7 @@ pub mod image_cache; pub mod local_directory_listing; pub mod mime_classifier; pub mod protocols; -pub mod request_intercepter; +pub mod request_interceptor; pub mod resource_thread; mod storage_thread; pub mod subresource_integrity; diff --git a/components/net/request_intercepter.rs b/components/net/request_intercepter.rs deleted file mode 100644 index 9ef07fe9f3a..00000000000 --- a/components/net/request_intercepter.rs +++ /dev/null @@ -1,105 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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 content_security_policy::Destination; -use embedder_traits::{ - EmbedderMsg, EmbedderProxy, HttpBodyData, WebResourceRequest, WebResourceResponseMsg, -}; -use ipc_channel::ipc; -use net_traits::http_status::HttpStatus; -use net_traits::request::Request; -use net_traits::response::{Response, ResponseBody}; -use net_traits::NetworkError; - -use crate::fetch::methods::FetchContext; - -#[derive(Clone)] -pub struct RequestIntercepter { - embedder_proxy: EmbedderProxy, -} - -impl RequestIntercepter { - pub fn new(embedder_proxy: EmbedderProxy) -> RequestIntercepter { - RequestIntercepter { embedder_proxy } - } - - pub fn intercept_request( - &self, - request: &mut Request, - response: &mut Option, - context: &FetchContext, - ) { - let (tx, rx) = ipc::channel().unwrap(); - let is_for_main_frame = matches!(request.destination, Destination::Document); - let req = WebResourceRequest::new( - request.method.clone(), - request.headers.clone(), - request.url(), - is_for_main_frame, - request.redirect_count > 0, - ); - - self.embedder_proxy.send(EmbedderMsg::WebResourceRequested( - request.target_webview_id, - req, - tx, - )); - let mut response_received = false; - - // TODO: use done_chan and run in CoreResourceThreadPool. - while let Ok(msg) = rx.recv() { - match msg { - WebResourceResponseMsg::Start(webresource_response) => { - response_received = true; - let timing = context.timing.lock().unwrap().clone(); - let mut res = Response::new(webresource_response.url.clone(), timing); - res.headers = webresource_response.headers; - res.status = HttpStatus::new( - webresource_response.status_code, - webresource_response.status_message, - ); - *res.body.lock().unwrap() = ResponseBody::Receiving(Vec::new()); - *response = Some(res); - }, - WebResourceResponseMsg::Body(data) => { - if !response_received { - panic!("Receive body before initializing a Response!"); - } - if let Some(ref mut res) = *response { - match data { - HttpBodyData::Chunk(chunk) => { - if let ResponseBody::Receiving(ref mut body) = - *res.body.lock().unwrap() - { - body.extend_from_slice(&chunk); - } else { - panic!("Receive Playload Message when Response body is not in Receiving state!"); - } - }, - HttpBodyData::Done => { - let mut res_body = res.body.lock().unwrap(); - if let ResponseBody::Receiving(ref mut body) = *res_body { - let completed_body = std::mem::take(body); - *res_body = ResponseBody::Done(completed_body); - } else { - panic!("Receive Done Message when Response body is not in Receiving state!"); - } - break; - }, - HttpBodyData::Cancelled => { - *response = - Some(Response::network_error(NetworkError::LoadCancelled)); - break; - }, - } - } - }, - WebResourceResponseMsg::None => { - // Will not intercept the response. Continue. - break; - }, - } - } - } -} diff --git a/components/net/request_interceptor.rs b/components/net/request_interceptor.rs new file mode 100644 index 00000000000..366f5cd26d0 --- /dev/null +++ b/components/net/request_interceptor.rs @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 content_security_policy::Destination; +use embedder_traits::{EmbedderMsg, EmbedderProxy, WebResourceRequest, WebResourceResponseMsg}; +use ipc_channel::ipc; +use log::error; +use net_traits::http_status::HttpStatus; +use net_traits::request::Request; +use net_traits::response::{Response, ResponseBody}; +use net_traits::NetworkError; + +use crate::fetch::methods::FetchContext; + +#[derive(Clone)] +pub struct RequestInterceptor { + embedder_proxy: EmbedderProxy, +} + +impl RequestInterceptor { + pub fn new(embedder_proxy: EmbedderProxy) -> RequestInterceptor { + RequestInterceptor { embedder_proxy } + } + + pub fn intercept_request( + &self, + request: &mut Request, + response: &mut Option, + context: &FetchContext, + ) { + let (sender, receiver) = ipc::channel().unwrap(); + let is_for_main_frame = matches!(request.destination, Destination::Document); + let web_resource_request = WebResourceRequest { + method: request.method.clone(), + url: request.url().into_url(), + headers: request.headers.clone(), + is_for_main_frame, + is_redirect: request.redirect_count > 0, + }; + + self.embedder_proxy.send(EmbedderMsg::WebResourceRequested( + request.target_webview_id, + web_resource_request, + sender, + )); + + // TODO: use done_chan and run in CoreResourceThreadPool. + let mut accumulated_body = Vec::new(); + while let Ok(message) = receiver.recv() { + match message { + WebResourceResponseMsg::Start(webresource_response) => { + let timing = context.timing.lock().unwrap().clone(); + let mut response_override = + Response::new(webresource_response.url.into(), timing); + response_override.headers = webresource_response.headers; + response_override.status = HttpStatus::new( + webresource_response.status_code, + webresource_response.status_message, + ); + *response = Some(response_override); + }, + WebResourceResponseMsg::SendBodyData(data) => { + accumulated_body.push(data); + }, + WebResourceResponseMsg::FinishLoad => { + if accumulated_body.is_empty() { + break; + } + let Some(response) = response.as_mut() else { + error!("Received unexpected FinishLoad message"); + break; + }; + *response.body.lock().unwrap() = + ResponseBody::Done(accumulated_body.into_iter().flatten().collect()); + break; + }, + WebResourceResponseMsg::CancelLoad => { + *response = Some(Response::network_error(NetworkError::LoadCancelled)); + break; + }, + WebResourceResponseMsg::DoNotIntercept => break, + } + } + } +} diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index e16c5f541f3..8306e4049f1 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -54,7 +54,7 @@ use crate::hsts::HstsList; use crate::http_cache::HttpCache; use crate::http_loader::{http_redirect_fetch, HttpState}; use crate::protocols::ProtocolRegistry; -use crate::request_intercepter::RequestIntercepter; +use crate::request_interceptor::RequestInterceptor; use crate::storage_thread::StorageThreadFactory; use crate::websocket_loader; @@ -553,7 +553,7 @@ pub struct CoreResourceManager { devtools_sender: Option>, sw_managers: HashMap>, filemanager: FileManager, - request_intercepter: RequestIntercepter, + request_interceptor: RequestInterceptor, thread_pool: Arc, ca_certificates: CACertificates, ignore_certificate_errors: bool, @@ -706,7 +706,7 @@ impl CoreResourceManager { devtools_sender, sw_managers: Default::default(), filemanager: FileManager::new(embedder_proxy.clone(), Arc::downgrade(&pool_handle)), - request_intercepter: RequestIntercepter::new(embedder_proxy), + request_interceptor: RequestInterceptor::new(embedder_proxy), thread_pool: pool_handle, ca_certificates, ignore_certificate_errors, @@ -749,7 +749,7 @@ impl CoreResourceManager { let ua = self.user_agent.clone(); let dc = self.devtools_sender.clone(); let filemanager = self.filemanager.clone(); - let request_intercepter = self.request_intercepter.clone(); + let request_interceptor = self.request_interceptor.clone(); let timing_type = match request_builder.destination { Destination::Document => ResourceTimingType::Navigation, @@ -791,7 +791,7 @@ impl CoreResourceManager { devtools_chan: dc.map(|dc| Arc::new(Mutex::new(dc))), filemanager: Arc::new(Mutex::new(filemanager)), file_token, - request_intercepter: Arc::new(Mutex::new(request_intercepter)), + request_interceptor: Arc::new(Mutex::new(request_interceptor)), cancellation_listener, timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new(request.timing_type()))), protocols, diff --git a/components/net/tests/fetch.rs b/components/net/tests/fetch.rs index b5af2b640d5..27fff40e897 100644 --- a/components/net/tests/fetch.rs +++ b/components/net/tests/fetch.rs @@ -30,7 +30,7 @@ use net::fetch::methods::{self, FetchContext}; use net::filemanager_thread::FileManager; use net::hsts::HstsEntry; use net::protocols::ProtocolRegistry; -use net::request_intercepter::RequestIntercepter; +use net::request_interceptor::RequestInterceptor; use net::resource_thread::CoreResourceThreadPool; use net_traits::filemanager_thread::FileTokenCheck; use net_traits::http_status::HttpStatus; @@ -708,7 +708,7 @@ fn test_fetch_with_hsts() { Weak::new(), ))), file_token: FileTokenCheck::NotRequired, - request_intercepter: Arc::new(Mutex::new(RequestIntercepter::new(embedder_proxy))), + request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))), cancellation_listener: Arc::new(Default::default()), timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new( ResourceTimingType::Navigation, @@ -768,7 +768,7 @@ fn test_load_adds_host_to_hsts_list_when_url_is_https() { Weak::new(), ))), file_token: FileTokenCheck::NotRequired, - request_intercepter: Arc::new(Mutex::new(RequestIntercepter::new(embedder_proxy))), + request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))), cancellation_listener: Arc::new(Default::default()), timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new( ResourceTimingType::Navigation, @@ -830,7 +830,7 @@ fn test_fetch_self_signed() { Weak::new(), ))), file_token: FileTokenCheck::NotRequired, - request_intercepter: Arc::new(Mutex::new(RequestIntercepter::new(embedder_proxy))), + request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))), cancellation_listener: Arc::new(Default::default()), timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new( ResourceTimingType::Navigation, @@ -1363,17 +1363,13 @@ fn test_fetch_request_intercepted() { .status_message(STATUS_MESSAGE.to_vec()); let msg = embedder_traits::WebResourceResponseMsg::Start(response); let _ = response_sender.send(msg); - let msg2 = embedder_traits::WebResourceResponseMsg::Body( - embedder_traits::HttpBodyData::Chunk(BODY_PART1.to_vec()), - ); + let msg2 = + embedder_traits::WebResourceResponseMsg::SendBodyData(BODY_PART1.to_vec()); let _ = response_sender.send(msg2); - let msg3 = embedder_traits::WebResourceResponseMsg::Body( - embedder_traits::HttpBodyData::Chunk(BODY_PART2.to_vec()), - ); + let msg3 = + embedder_traits::WebResourceResponseMsg::SendBodyData(BODY_PART2.to_vec()); let _ = response_sender.send(msg3); - let _ = response_sender.send(embedder_traits::WebResourceResponseMsg::Body( - embedder_traits::HttpBodyData::Done, - )); + let _ = response_sender.send(embedder_traits::WebResourceResponseMsg::FinishLoad); }, _ => unreachable!(), } @@ -1388,7 +1384,7 @@ fn test_fetch_request_intercepted() { Weak::new(), ))), file_token: FileTokenCheck::NotRequired, - request_intercepter: Arc::new(Mutex::new(RequestIntercepter::new(embedder_proxy))), + request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))), cancellation_listener: Arc::new(Default::default()), timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new( ResourceTimingType::Navigation, diff --git a/components/net/tests/main.rs b/components/net/tests/main.rs index 99bb918091a..531fefb1eb0 100644 --- a/components/net/tests/main.rs +++ b/components/net/tests/main.rs @@ -42,7 +42,7 @@ use net::fetch::cors_cache::CorsCache; use net::fetch::methods::{self, FetchContext}; use net::filemanager_thread::FileManager; use net::protocols::ProtocolRegistry; -use net::request_intercepter::RequestIntercepter; +use net::request_interceptor::RequestInterceptor; use net::resource_thread::CoreResourceThreadPool; use net::test::HttpState; use net_traits::filemanager_thread::FileTokenCheck; @@ -177,7 +177,7 @@ fn new_fetch_context( pool_handle.unwrap_or_else(|| Weak::new()), ))), file_token: FileTokenCheck::NotRequired, - request_intercepter: Arc::new(Mutex::new(RequestIntercepter::new(sender))), + request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(sender))), cancellation_listener: Arc::new(Default::default()), timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new( ResourceTimingType::Navigation, diff --git a/components/servo/lib.rs b/components/servo/lib.rs index cabc2c40295..26ab938982f 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -126,7 +126,7 @@ pub use crate::servo_delegate::{ServoDelegate, ServoError}; pub use crate::webview::WebView; pub use crate::webview_delegate::{ AllowOrDenyRequest, AuthenticationRequest, NavigationRequest, PermissionRequest, - WebViewDelegate, + WebResourceLoad, WebViewDelegate, }; #[cfg(feature = "webdriver")] @@ -836,20 +836,17 @@ impl Servo { web_resource_request, response_sender, ) => { - let webview = webview_id.and_then(|webview_id| self.get_webview_handle(webview_id)); - if let Some(webview) = webview.clone() { - webview.delegate().intercept_web_resource_load( - webview, - &web_resource_request, - response_sender.clone(), - ); - } - - self.delegate().intercept_web_resource_load( - webview, - &web_resource_request, + let web_resource_load = WebResourceLoad { + request: web_resource_request, response_sender, - ); + intercepted: false, + }; + match webview_id.and_then(|webview_id| self.get_webview_handle(webview_id)) { + Some(webview) => webview + .delegate() + .load_web_resource(webview, web_resource_load), + None => self.delegate().load_web_resource(web_resource_load), + } }, EmbedderMsg::Panic(webview_id, reason, backtrace) => { if let Some(webview) = self.get_webview_handle(webview_id) { diff --git a/components/servo/servo_delegate.rs b/components/servo/servo_delegate.rs index 379be8a8513..3c2b9befaaa 100644 --- a/components/servo/servo_delegate.rs +++ b/components/servo/servo_delegate.rs @@ -2,11 +2,8 @@ * 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 embedder_traits::{WebResourceRequest, WebResourceResponseMsg}; -use ipc_channel::ipc::IpcSender; - -use crate::webview_delegate::AllowOrDenyRequest; -use crate::{Servo, WebView}; +use crate::webview_delegate::{AllowOrDenyRequest, WebResourceLoad}; +use crate::Servo; #[derive(Clone, Copy, Debug, Hash, PartialEq)] pub enum ServoError { @@ -27,20 +24,14 @@ pub trait ServoDelegate { /// Request a DevTools connection from a DevTools client. Typically an embedder application /// will show a permissions prompt when this happens to confirm a connection is allowed. fn request_devtools_connection(&self, _servo: &Servo, _request: AllowOrDenyRequest) {} - /// Potentially intercept a resource request. If not handled, the request will not be intercepted. + /// Triggered when Servo will load a web (HTTP/HTTPS) resource. The load may be + /// intercepted and alternate contents can be loaded by the client by calling + /// [`WebResourceLoad::intercept`]. If not handled, the load will continue as normal. /// - /// Note: If this request is associated with a `WebView`, the `WebViewDelegate` will - /// receive this notification first and have a chance to intercept the request. - /// - /// TODO: This API needs to be reworked to match the new model of how responses are sent. - fn intercept_web_resource_load( - &self, - _webview: Option, - _request: &WebResourceRequest, - response_sender: IpcSender, - ) { - let _ = response_sender.send(WebResourceResponseMsg::None); - } + /// Note: This delegate method is called for all resource loads not associated with a + /// [`WebView`]. For loads associated with a [`WebView`], Servo will call + /// [`crate::WebViewDelegate::load_web_resource`]. + fn load_web_resource(&self, _load: WebResourceLoad) {} } pub(crate) struct DefaultServoDelegate; diff --git a/components/servo/webview_delegate.rs b/components/servo/webview_delegate.rs index c84ef699647..ef8cb93cb66 100644 --- a/components/servo/webview_delegate.rs +++ b/components/servo/webview_delegate.rs @@ -9,7 +9,8 @@ use compositing_traits::ConstellationMsg; use embedder_traits::{ AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, FilterPattern, GamepadHapticEffectType, InputMethodType, LoadStatus, MediaSessionEvent, PermissionFeature, - PromptDefinition, PromptOrigin, WebResourceRequest, WebResourceResponseMsg, + PromptDefinition, PromptOrigin, WebResourceRequest, WebResourceResponse, + WebResourceResponseMsg, }; use ipc_channel::ipc::IpcSender; use keyboard_types::KeyboardEvent; @@ -143,6 +144,90 @@ impl Drop for AuthenticationRequest { } } +/// Information related to the loading of a web resource. These are created for all HTTP requests. +/// The client may choose to intercept the load of web resources and send an alternate response +/// by calling [`WebResourceLoad::intercept`]. +pub struct WebResourceLoad { + pub request: WebResourceRequest, + pub(crate) response_sender: IpcSender, + pub(crate) intercepted: bool, +} + +impl WebResourceLoad { + /// The [`WebResourceRequest`] associated with this [`WebResourceLoad`]. + pub fn request(&self) -> &WebResourceRequest { + &self.request + } + /// Intercept this [`WebResourceLoad`] and control the response via the returned + /// [`InterceptedWebResourceLoad`]. + pub fn intercept(mut self, response: WebResourceResponse) -> InterceptedWebResourceLoad { + let _ = self + .response_sender + .send(WebResourceResponseMsg::Start(response)); + self.intercepted = true; + InterceptedWebResourceLoad { + request: self.request.clone(), + response_sender: self.response_sender.clone(), + finished: false, + } + } +} + +impl Drop for WebResourceLoad { + fn drop(&mut self) { + if !self.intercepted { + let _ = self + .response_sender + .send(WebResourceResponseMsg::DoNotIntercept); + } + } +} + +/// An intercepted web resource load. This struct allows the client to send an alternative response +/// after calling [`WebResourceLoad::intercept`]. In order to send chunks of body data, the client +/// must call [`InterceptedWebResourceLoad::send_body_data`]. When the interception is complete, the client +/// should call [`InterceptedWebResourceLoad::finish`]. If neither `finish()` or `cancel()` are called, +/// this interception will automatically be finished when dropped. +pub struct InterceptedWebResourceLoad { + pub request: WebResourceRequest, + pub(crate) response_sender: IpcSender, + pub(crate) finished: bool, +} + +impl InterceptedWebResourceLoad { + /// Send a chunk of response body data. It's possible to make subsequent calls to + /// this method when streaming body data. + pub fn send_body_data(&self, data: Vec) { + let _ = self + .response_sender + .send(WebResourceResponseMsg::SendBodyData(data)); + } + /// Finish this [`InterceptedWebResourceLoad`] and complete the response. + pub fn finish(mut self) { + let _ = self + .response_sender + .send(WebResourceResponseMsg::FinishLoad); + self.finished = true; + } + /// Cancel this [`InterceptedWebResourceLoad`], which will trigger a network error. + pub fn cancel(mut self) { + let _ = self + .response_sender + .send(WebResourceResponseMsg::CancelLoad); + self.finished = true; + } +} + +impl Drop for InterceptedWebResourceLoad { + fn drop(&mut self) { + if !self.finished { + let _ = self + .response_sender + .send(WebResourceResponseMsg::FinishLoad); + } + } +} + pub trait WebViewDelegate { /// The URL of the currently loaded page in this [`WebView`] has changed. The new /// URL can accessed via [`WebView::url`]. @@ -296,19 +381,14 @@ pub trait WebViewDelegate { /// Request to stop a haptic effect on a connected gamepad. fn stop_gamepad_haptic_effect(&self, _webview: WebView, _: usize, _: IpcSender) {} - /// Potentially intercept a resource request. If not handled, the request will not be intercepted. + /// Triggered when this [`WebView`] will load a web (HTTP/HTTPS) resource. The load may be + /// intercepted and alternate contents can be loaded by the client by calling + /// [`WebResourceLoad::intercept`]. If not handled, the load will continue as normal. /// - /// Note: The `ServoDelegate` will also receive this notification and have a chance to intercept - /// the request. - /// - /// TODO: This API needs to be reworked to match the new model of how responses are sent. - fn intercept_web_resource_load( - &self, - _webview: WebView, - _request: &WebResourceRequest, - _response_sender: IpcSender, - ) { - } + /// Note: This delegate method is called for all resource loads associated with a [`WebView`]. + /// For loads not associated with a [`WebView`], such as those for service workers, Servo + /// will call [`crate::ServoDelegate::load_web_resource`]. + fn load_web_resource(&self, _webview: WebView, _load: WebResourceLoad) {} } pub(crate) struct DefaultWebViewDelegate; diff --git a/components/shared/embedder/Cargo.toml b/components/shared/embedder/Cargo.toml index 3076d1c4896..3bb8a5ee83c 100644 --- a/components/shared/embedder/Cargo.toml +++ b/components/shared/embedder/Cargo.toml @@ -30,5 +30,6 @@ malloc_size_of_derive = { workspace = true } num-traits = { workspace = true } serde = { workspace = true } servo_url = { path = "../../url" } +url = { workspace = true } webrender_api = { workspace = true } webxr-api = { workspace = true, features = ["ipc"], optional = true } diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index 533826408c1..071ad16be71 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -18,6 +18,7 @@ use malloc_size_of_derive::MallocSizeOf; use num_derive::FromPrimitive; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; +use url::Url; use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; pub use crate::input_events::*; @@ -452,49 +453,30 @@ pub struct WebResourceRequest { )] #[ignore_malloc_size_of = "Defined in hyper"] pub headers: HeaderMap, - pub url: ServoUrl, + pub url: Url, pub is_for_main_frame: bool, pub is_redirect: bool, } -impl WebResourceRequest { - pub fn new( - method: Method, - headers: HeaderMap, - url: ServoUrl, - is_for_main_frame: bool, - is_redirect: bool, - ) -> Self { - WebResourceRequest { - method, - url, - headers, - is_for_main_frame, - is_redirect, - } - } -} - #[derive(Clone, Deserialize, Serialize)] pub enum WebResourceResponseMsg { - // Response of WebResourceRequest, no body included. + /// Start an interception of this web resource load. It's expected that the client subsequently + /// send either a `CancelLoad` or `FinishLoad` message after optionally sending chunks of body + /// data via `SendBodyData`. Start(WebResourceResponse), - // send a body chunk. It is expected Response sent before body. - Body(HttpBodyData), - // not to override the response. - None, -} - -#[derive(Clone, Deserialize, Serialize)] -pub enum HttpBodyData { - Chunk(Vec), - Done, - Cancelled, + /// Send a chunk of body data. + SendBodyData(Vec), + /// Signal that this load has been finished by the interceptor. + FinishLoad, + /// Signal that this load has been cancelled by the interceptor. + CancelLoad, + /// Signal that this load will not be intercepted. + DoNotIntercept, } #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct WebResourceResponse { - pub url: ServoUrl, + pub url: Url, #[serde( deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize" @@ -511,7 +493,7 @@ pub struct WebResourceResponse { } impl WebResourceResponse { - pub fn new(url: ServoUrl) -> WebResourceResponse { + pub fn new(url: Url) -> WebResourceResponse { WebResourceResponse { url, headers: HeaderMap::new(),