diff --git a/Cargo.lock b/Cargo.lock index f0674e04..161c0f16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6912,6 +6912,7 @@ name = "verso" version = "0.0.1" dependencies = [ "ipc-channel", + "log", "serde", "url", "versoview_messages", diff --git a/Cargo.toml b/Cargo.toml index ff7f93a6..f63850c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = ["verso", "versoview_messages"] ipc-channel = "0.19" serde = { version = "1.0", features = ["derive"] } url = { version = "2.5.2", features = ["serde"] } +log = "0.4" [package] name = "versoview" @@ -63,7 +64,7 @@ glutin = "0.32.0" glutin-winit = "0.5.0" ipc-channel = { workspace = true } keyboard-types = "0.7" -log = "0.4" +log = { workspace = true } raw-window-handle = { version = "0.6", features = ["std"] } rustls = { version = "0.23", default-features = false, features = ["ring"] } sparkle = "0.1.26" diff --git a/src/errors.rs b/src/errors.rs index 248b404a..80005242 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -14,4 +14,7 @@ pub enum Error { /// Glutin errors. #[error(transparent)] GlutinError(#[from] glutin::error::Error), + /// IPC errors. + #[error(transparent)] + IpcError(#[from] ipc_channel::ipc::IpcError), } diff --git a/src/verso.rs b/src/verso.rs index dc15d780..5a8febe8 100644 --- a/src/verso.rs +++ b/src/verso.rs @@ -580,7 +580,7 @@ impl Verso { } /// Handle message came from webview controller. - pub fn handle_incoming_webview_message(&self, message: ControllerMessage) { + pub fn handle_incoming_webview_message(&mut self, message: ControllerMessage) { match message { ControllerMessage::NavigateTo(to_url) => { if let Some(webview_id) = @@ -594,6 +594,14 @@ impl Verso { ); } } + ControllerMessage::OnNavigationStarting(sender) => { + if let Some((window, _)) = self.windows.values_mut().next() { + window + .event_listeners + .on_navigation_starting + .replace(sender); + } + } _ => {} } } diff --git a/src/webview/webview.rs b/src/webview/webview.rs index 1c983aa1..4c6d4692 100644 --- a/src/webview/webview.rs +++ b/src/webview/webview.rs @@ -121,9 +121,15 @@ impl Window { let _ = rx.recv(); } } - EmbedderMsg::AllowNavigationRequest(id, _url) => { - // TODO should provide a API for users to check url - send_to_constellation(sender, ConstellationMsg::AllowNavigationResponse(id, true)); + EmbedderMsg::AllowNavigationRequest(id, url) => { + let allow = match self.allow_navigation(url) { + Ok(allow) => allow, + Err(e) => { + log::error!("Error when calling navigation handler: {e}"); + true + } + }; + send_to_constellation(sender, ConstellationMsg::AllowNavigationResponse(id, allow)); } EmbedderMsg::GetClipboardContents(sender) => { let contents = clipboard @@ -599,4 +605,18 @@ impl Window { } false } + + fn allow_navigation(&self, url: ServoUrl) -> crate::Result { + if let Some(ref sender) = self.event_listeners.on_navigation_starting { + let (result_sender, receiver) = + ipc::channel::().map_err(|e| ipc::IpcError::Io(e))?; + sender + .send((url.into_url(), result_sender)) + .map_err(|e| ipc::IpcError::Bincode(e))?; + let result = receiver.recv()?; + Ok(result) + } else { + Ok(true) + } + } } diff --git a/src/window.rs b/src/window.rs index 20dd67cd..54781e34 100644 --- a/src/window.rs +++ b/src/window.rs @@ -24,6 +24,7 @@ use script_traits::{ }; use script_traits::{TouchEventType, WheelDelta, WheelMode}; use servo_url::ServoUrl; +use versoview_messages::OnNavigationStartingPayload; use webrender_api::{ units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, LayoutVector2D}, ScrollLocation, @@ -57,6 +58,11 @@ const PANEL_HEIGHT: f64 = 50.0; const TAB_HEIGHT: f64 = 30.0; const PANEL_PADDING: f64 = 4.0; +#[derive(Default)] +pub(crate) struct EventListeners { + pub(crate) on_navigation_starting: Option, +} + /// A Verso window is a Winit window containing several web views. pub struct Window { /// Access to Winit window @@ -67,6 +73,8 @@ pub struct Window { pub(crate) panel: Option, /// The WebView of this window. // pub(crate) webview: Option, + /// Event listeners registered from the webview controller + pub(crate) event_listeners: EventListeners, /// The mouse physical position in the web view. mouse_position: Cell>>, /// Modifiers state of the keyboard. @@ -126,6 +134,7 @@ impl Window { window, surface, panel: None, + event_listeners: Default::default(), mouse_position: Default::default(), modifiers_state: Cell::new(ModifiersState::default()), resizing: false, @@ -168,6 +177,7 @@ impl Window { surface, panel: None, // webview: None, + event_listeners: Default::default(), mouse_position: Default::default(), modifiers_state: Cell::new(ModifiersState::default()), resizing: false, diff --git a/verso/Cargo.toml b/verso/Cargo.toml index 807eaadc..7461b781 100644 --- a/verso/Cargo.toml +++ b/verso/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" ipc-channel = { workspace = true } serde = { workspace = true } url = { workspace = true } +log = { workspace = true } versoview_messages = { path = "../versoview_messages" } diff --git a/verso/src/lib.rs b/verso/src/lib.rs index 6ab21f86..b2fe950f 100644 --- a/verso/src/lib.rs +++ b/verso/src/lib.rs @@ -1,9 +1,25 @@ -use std::{path::Path, process::Command}; +use log::error; +use std::{ + path::Path, + process::Command, + sync::{Arc, Mutex}, +}; use versoview_messages::ControllerMessage; -use ipc_channel::ipc::{IpcOneShotServer, IpcSender}; +use ipc_channel::{ + ipc::{self, IpcOneShotServer, IpcSender}, + router::ROUTER, +}; -pub struct VersoviewController(IpcSender); +#[derive(Default)] +struct EventListeners { + on_navigation_starting: Arc bool + Send + 'static>>>>, +} + +pub struct VersoviewController { + sender: IpcSender, + event_listeners: EventListeners, +} impl VersoviewController { /// Create a new verso instance and get the controller to it @@ -17,11 +33,48 @@ impl VersoviewController { .spawn() .unwrap(); let (_, sender) = server.accept().unwrap(); - Self(sender) + Self { + sender, + event_listeners: EventListeners::default(), + } } /// Navigate to url pub fn navigate(&self, url: url::Url) -> Result<(), Box> { - self.0.send(ControllerMessage::NavigateTo(url)) + self.sender.send(ControllerMessage::NavigateTo(url)) + } + + /// Listen on navigation starting triggered by user click on a link, + /// return a boolean in the callback to decide whether or not allowing this navigation + pub fn on_navigation_starting( + &self, + callback: impl Fn(url::Url) -> bool + Send + 'static, + ) -> Result<(), Box> { + if self + .event_listeners + .on_navigation_starting + .lock() + .unwrap() + .replace(Box::new(callback)) + .is_some() + { + return Ok(()); + } + let cb = self.event_listeners.on_navigation_starting.clone(); + let (sender, receiver) = ipc::channel::<(url::Url, ipc::IpcSender)>()?; + self.sender + .send(ControllerMessage::OnNavigationStarting(sender))?; + ROUTER.add_typed_route( + receiver, + Box::new(move |message| match message { + Ok((url, result_sender)) => { + if let Err(e) = result_sender.send(cb.lock().unwrap().as_ref().unwrap()(url)) { + error!("Error while sending back OnNavigationStarting result: {e}"); + } + } + Err(e) => error!("Error while receiving OnNavigationStarting message: {e}"), + }), + ); + Ok(()) } } diff --git a/verso/src/main.rs b/verso/src/main.rs index 188191d5..ab8a56a3 100644 --- a/verso/src/main.rs +++ b/verso/src/main.rs @@ -6,6 +6,12 @@ fn main() { versoview_path, url::Url::parse("https://example.com").unwrap(), ); + controller + .on_navigation_starting(|url| { + dbg!(url); + true + }) + .unwrap(); sleep(Duration::from_secs(10)); dbg!(controller .navigate(url::Url::parse("https://docs.rs").unwrap()) diff --git a/versoview_messages/src/lib.rs b/versoview_messages/src/lib.rs index f099afb0..b828a22c 100644 --- a/versoview_messages/src/lib.rs +++ b/versoview_messages/src/lib.rs @@ -1,7 +1,11 @@ +use ipc_channel::ipc; use serde::{Deserialize, Serialize}; +pub type OnNavigationStartingPayload = ipc::IpcSender<(url::Url, ipc::IpcSender)>; + #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum ControllerMessage { NavigateTo(url::Url), + OnNavigationStarting(OnNavigationStartingPayload), }