diff options
Diffstat (limited to 'components/servo')
-rw-r--r-- | components/servo/examples/winit_minimal.rs | 153 | ||||
-rw-r--r-- | components/servo/lib.rs | 421 | ||||
-rw-r--r-- | components/servo/proxies.rs | 6 | ||||
-rw-r--r-- | components/servo/servo_delegate.rs | 47 | ||||
-rw-r--r-- | components/servo/webview.rs | 277 | ||||
-rw-r--r-- | components/servo/webview_delegate.rs | 262 |
6 files changed, 997 insertions, 169 deletions
diff --git a/components/servo/examples/winit_minimal.rs b/components/servo/examples/winit_minimal.rs index 8a7ea1a78d5..d7a2a43584f 100644 --- a/components/servo/examples/winit_minimal.rs +++ b/components/servo/examples/winit_minimal.rs @@ -2,19 +2,19 @@ * 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::cell::Cell; +use std::cell::{Cell, RefCell}; use std::error::Error; use std::rc::Rc; -use compositing::windowing::{AnimationState, EmbedderEvent, EmbedderMethods, WindowMethods}; -use embedder_traits::EmbedderMsg; +use compositing::windowing::{AnimationState, EmbedderMethods, WindowMethods}; use euclid::{Point2D, Scale, Size2D}; -use servo::{Servo, WebView}; +use servo::{Servo, TouchEventType, WebView}; use servo_geometry::DeviceIndependentPixel; use surfman::{Connection, SurfaceType}; use tracing::warn; use url::Url; -use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel}; +use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel, LayoutVector2D}; +use webrender_api::ScrollLocation; use webrender_traits::SurfmanRenderingContext; use winit::application::ApplicationHandler; use winit::dpi::{PhysicalPosition, PhysicalSize}; @@ -34,20 +34,48 @@ fn main() -> Result<(), Box<dyn Error>> { let mut app = App::new(&event_loop); event_loop.run_app(&mut app)?; - if let App::Running { servo, .. } = app { - servo.deinit(); + if let App::Running(state) = app { + if let Some(state) = Rc::into_inner(state) { + state.servo.deinit(); + } } Ok(()) } +struct AppState { + window_delegate: Rc<WindowDelegate>, + servo: Servo, + webviews: RefCell<Vec<WebView>>, +} + +impl ::servo::WebViewDelegate for AppState { + fn notify_ready_to_show(&self, webview: WebView) { + let rect = self + .window_delegate + .get_coordinates() + .get_viewport() + .to_f32(); + webview.focus(); + webview.move_resize(rect); + webview.raise_to_top(true); + } + + fn notify_new_frame_ready(&self, _: WebView) { + self.servo.present(); + } + + fn request_open_auxiliary_webview(&self, parent_webview: WebView) -> Option<WebView> { + let webview = self.servo.new_auxiliary_webview(); + webview.set_delegate(parent_webview.delegate()); + self.webviews.borrow_mut().push(webview.clone()); + Some(webview) + } +} + enum App { Initial(Waker), - Running { - window_delegate: Rc<WindowDelegate>, - servo: Servo, - webviews: Vec<WebView>, - }, + Running(Rc<AppState>), } impl App { @@ -103,15 +131,20 @@ impl ApplicationHandler<WakerEvent> for App { compositing::CompositeTarget::ContextFbo, ); servo.setup_logging(); - let webviews = vec![servo.new_webview( - Url::parse("https://demo.servo.org/experiments/twgl-tunnel/") - .expect("Guaranteed by argument"), - )]; - *self = Self::Running { + + let app_state = Rc::new(AppState { window_delegate, servo, - webviews, - }; + webviews: Default::default(), + }); + + // Make a new WebView and assign the `AppState` as the delegate. + let url = Url::parse("https://servo.org").expect("Guaranteed by argument"); + let webview = app_state.servo.new_webview(url); + webview.set_delegate(app_state.clone()); + app_state.webviews.borrow_mut().push(webview); + + *self = Self::Running(app_state); } } @@ -121,69 +154,41 @@ impl ApplicationHandler<WakerEvent> for App { _window_id: winit::window::WindowId, event: WindowEvent, ) { - if let Self::Running { - window_delegate, - servo, - webviews, - } = self - { - for message in servo.get_events().collect::<Vec<_>>() { - match message { - // FIXME: rust-analyzer autocompletes this as top_level_browsing_context_id - EmbedderMsg::WebViewOpened(webview_id) => { - // TODO: We currently assume `webview` refers to the same webview as `_webview_id` - let rect = window_delegate.get_coordinates().get_viewport().to_f32(); - if let Some(webview) = - webviews.iter().find(|webview| webview.id() == webview_id) - { - webview.focus(); - webview.move_resize(rect); - webview.raise_to_top(true); - } - }, - EmbedderMsg::AllowOpeningWebView(_, webview_id_sender) => { - let webview = servo.new_auxiliary_webview(); - let _ = webview_id_sender.send(Some(webview.id())); - webviews.push(webview); - }, - EmbedderMsg::AllowNavigationRequest(_, pipeline_id, _) => { - servo.handle_events([EmbedderEvent::AllowNavigationResponse( - pipeline_id, - true, - )]); - }, - _ => {}, - } - } - // FIXME: still needed for the compositor to actually run - servo.handle_events([]); + if let Self::Running(state) = self { + state.servo.spin_event_loop(); } + match event { WindowEvent::CloseRequested => { event_loop.exit(); }, WindowEvent::RedrawRequested => { - if let Self::Running { - window_delegate, - servo, - .. - } = self - { - servo.present(); - window_delegate.window.request_redraw(); + if let Self::Running(state) = self { + state.webviews.borrow().last().unwrap().composite(); + state.servo.present(); } }, - WindowEvent::MouseInput { .. } => { - // When the window is clicked, close the last webview by dropping its handle, - // then show the next most recently opened webview. - // - // TODO: Test closing webviews a better way, so that we can use mouse input to test - // input handling. - if let Self::Running { webviews, .. } = self { - let _ = webviews.pop(); - match webviews.last() { - Some(last) => last.show(true), - None => event_loop.exit(), + WindowEvent::MouseWheel { .. } => { + if let Self::Running(state) = self { + if let Some(webview) = state.webviews.borrow().last() { + webview.notify_scroll_event( + ScrollLocation::Delta(LayoutVector2D::new(0., -20.)), + DeviceIntPoint::new(10, 10), + TouchEventType::Down, + ); + } + } + }, + WindowEvent::KeyboardInput { event, .. } => { + // When pressing 'q' close the latest WebView, then show the next most recently + // opened view or quit when none are left. + if event.logical_key.to_text() == Some("q") { + if let Self::Running(state) = self { + let _ = state.webviews.borrow_mut().pop(); + match state.webviews.borrow().last() { + Some(last) => last.show(true), + None => event_loop.exit(), + } } } }, diff --git a/components/servo/lib.rs b/components/servo/lib.rs index b21445fda84..7e961f35f20 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -18,19 +18,21 @@ //! `WindowMethods` trait. mod proxies; +mod servo_delegate; mod webview; +mod webview_delegate; use std::borrow::Cow; use std::cell::RefCell; use std::cmp::max; +use std::collections::HashMap; use std::path::PathBuf; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use std::sync::{Arc, Mutex}; use std::thread; -use std::vec::Drain; pub use base::id::TopLevelBrowsingContextId; -use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId}; +use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId, WebViewId}; use bluetooth::BluetoothThreadFactory; use bluetooth_traits::BluetoothRequest; use canvas::canvas_paint_thread::CanvasPaintThread; @@ -86,8 +88,10 @@ use script_traits::{ScriptToConstellationChan, WindowSizeData}; use servo_config::opts::Opts; use servo_config::prefs::Preferences; use servo_config::{opts, pref, prefs}; +use servo_delegate::DefaultServoDelegate; use servo_media::player::context::GlContext; use servo_media::ServoMedia; +use servo_url::ServoUrl; #[cfg(all(target_os = "linux", not(target_env = "ohos")))] use surfman::platform::generic::multi::connection::NativeConnection as LinuxNativeConnection; #[cfg(all(target_os = "linux", not(target_env = "ohos")))] @@ -106,6 +110,7 @@ use webrender_traits::{ CrossProcessCompositorApi, WebrenderExternalImageHandlers, WebrenderExternalImageRegistry, WebrenderImageHandlerType, }; +use webview::WebViewInner; #[cfg(feature = "webxr")] pub use webxr; pub use { @@ -117,7 +122,9 @@ pub use { }; use crate::proxies::ConstellationProxy; +pub use crate::servo_delegate::{ServoDelegate, ServoError}; pub use crate::webview::WebView; +pub use crate::webview_delegate::{AllowOrDenyRequest, NavigationRequest, WebViewDelegate}; #[cfg(feature = "webdriver")] fn webdriver(port: u16, constellation: Sender<ConstellationMsg>) { @@ -187,10 +194,16 @@ mod media_platform { /// loop to pump messages between the embedding application and /// various browser components. pub struct Servo { + delegate: Rc<dyn ServoDelegate>, compositor: Rc<RefCell<IOCompositor>>, constellation_proxy: ConstellationProxy, embedder_receiver: Receiver<EmbedderMsg>, - messages_for_embedder: Vec<EmbedderMsg>, + messages_for_embedder: RefCell<Vec<EmbedderMsg>>, + /// A map [`WebView`]s that are managed by this [`Servo`] instance. These are stored + /// as `Weak` references so that the embedding application can control their lifetime. + /// When accessed, `Servo` will be reponsible for cleaning up the invalid `Weak` + /// references. + webviews: RefCell<HashMap<WebViewId, Weak<RefCell<WebViewInner>>>>, /// For single-process Servo instances, this field controls the initialization /// and deinitialization of the JS Engine. Multiprocess Servo instances have their /// own instance that exists in the content process instead. @@ -527,14 +540,24 @@ impl Servo { ); Servo { + delegate: Rc::new(DefaultServoDelegate), compositor: Rc::new(RefCell::new(compositor)), constellation_proxy: ConstellationProxy::new(constellation_chan), embedder_receiver, - messages_for_embedder: Vec::new(), + messages_for_embedder: Default::default(), + webviews: Default::default(), _js_engine_setup: js_engine_setup, } } + pub fn delegate(&self) -> Rc<dyn ServoDelegate> { + self.delegate.clone() + } + + pub fn set_delegate(&mut self, delegate: Rc<dyn ServoDelegate>) { + self.delegate = delegate; + } + #[cfg(all(target_os = "linux", not(target_env = "ohos")))] fn get_native_media_display_and_gl_context( rendering_context: &Rc<dyn RenderingContext>, @@ -678,8 +701,8 @@ impl Servo { } }, - EmbedderEvent::LoadUrl(top_level_browsing_context_id, url) => { - let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url); + EmbedderEvent::LoadUrl(webview_id, url) => { + let msg = ConstellationMsg::LoadUrl(webview_id, url); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending load url to constellation failed ({:?}).", e); } @@ -742,6 +765,7 @@ impl Servo { warn!("Sending navigation to constellation failed ({:?}).", e); } self.messages_for_embedder + .borrow_mut() .push(EmbedderMsg::Status(webview_id, None)); }, @@ -776,15 +800,15 @@ impl Servo { self.compositor.borrow_mut().maybe_start_shutting_down(); }, - EmbedderEvent::ExitFullScreen(top_level_browsing_context_id) => { - let msg = ConstellationMsg::ExitFullScreen(top_level_browsing_context_id); + EmbedderEvent::ExitFullScreen(webview_id) => { + let msg = ConstellationMsg::ExitFullScreen(webview_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending exit fullscreen to constellation failed ({:?}).", e); } }, - EmbedderEvent::Reload(top_level_browsing_context_id) => { - let msg = ConstellationMsg::Reload(top_level_browsing_context_id); + EmbedderEvent::Reload(webview_id) => { + let msg = ConstellationMsg::Reload(webview_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending reload to constellation failed ({:?}).", e); } @@ -807,8 +831,8 @@ impl Servo { self.compositor.borrow_mut().capture_webrender(); }, - EmbedderEvent::NewWebView(url, top_level_browsing_context_id) => { - let msg = ConstellationMsg::NewWebView(url, top_level_browsing_context_id); + EmbedderEvent::NewWebView(url, webview_id) => { + let msg = ConstellationMsg::NewWebView(url, webview_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending NewBrowser message to constellation failed ({:?}).", @@ -817,8 +841,8 @@ impl Servo { } }, - EmbedderEvent::FocusWebView(top_level_browsing_context_id) => { - let msg = ConstellationMsg::FocusWebView(top_level_browsing_context_id); + EmbedderEvent::FocusWebView(webview_id) => { + let msg = ConstellationMsg::FocusWebView(webview_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending FocusBrowser message to constellation failed ({:?}).", @@ -827,8 +851,8 @@ impl Servo { } }, - EmbedderEvent::CloseWebView(top_level_browsing_context_id) => { - let msg = ConstellationMsg::CloseWebView(top_level_browsing_context_id); + EmbedderEvent::CloseWebView(webview_id) => { + let msg = ConstellationMsg::CloseWebView(webview_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending CloseBrowser message to constellation failed ({:?}).", @@ -871,8 +895,8 @@ impl Servo { self.send_to_constellation(ConstellationMsg::BlurWebView); }, - EmbedderEvent::SendError(top_level_browsing_context_id, e) => { - let msg = ConstellationMsg::SendError(top_level_browsing_context_id, e); + EmbedderEvent::SendError(webview_id, e) => { + let msg = ConstellationMsg::SendError(webview_id, e); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending SendError message to constellation failed ({:?}).", @@ -923,7 +947,7 @@ impl Servo { } } - fn receive_messages(&mut self) { + fn receive_messages(&self) { while let Ok(message) = self.embedder_receiver.try_recv() { match (message, self.compositor.borrow().shutdown_state) { (_, ShutdownState::FinishedShuttingDown) => { @@ -936,18 +960,19 @@ impl Servo { (EmbedderMsg::Keyboard(webview_id, key_event), ShutdownState::NotShuttingDown) => { self.messages_for_embedder + .borrow_mut() .push(EmbedderMsg::Keyboard(webview_id, key_event)); }, (message, ShutdownState::NotShuttingDown) => { - self.messages_for_embedder.push(message); + self.messages_for_embedder.borrow_mut().push(message); }, } } } - pub fn get_events(&mut self) -> Drain<'_, EmbedderMsg> { - self.messages_for_embedder.drain(..) + pub fn get_events(&self) -> Vec<EmbedderMsg> { + std::mem::take(&mut *self.messages_for_embedder.borrow_mut()) } pub fn handle_events(&mut self, events: impl IntoIterator<Item = EmbedderEvent>) { @@ -961,8 +986,38 @@ impl Servo { if self.compositor.borrow().shutdown_state != ShutdownState::FinishedShuttingDown { self.compositor.borrow_mut().perform_updates(); } else { - self.messages_for_embedder.push(EmbedderMsg::Shutdown); + self.messages_for_embedder + .borrow_mut() + .push(EmbedderMsg::Shutdown); + } + } + + /// Spin the Servo event loop, which: + /// + /// - Performs updates in the compositor, such as queued pinch zoom events + /// - Runs delebgate methods on all `WebView`s and `Servo` itself + /// - Maybe update the rendered compositor output, but *without* swapping buffers. + /// + /// The return value of this method indicates whether or not Servo, false indicates that Servo + /// has finished shutting down and you should not spin the event loop any longer. + pub fn spin_event_loop(&self) -> bool { + if self.compositor.borrow_mut().receive_messages() { + self.receive_messages(); + } + + self.call_delegate_methods(); + if self.constellation_proxy.disconnected() { + self.delegate() + .notify_error(self, ServoError::LostConnectionWithBackend); } + + self.compositor.borrow_mut().perform_updates(); + + if self.compositor.borrow().shutdown_state == ShutdownState::FinishedShuttingDown { + return false; + } + + true } pub fn pinch_zoom_level(&self) -> f32 { @@ -996,11 +1051,11 @@ impl Servo { } } - pub fn deinit(self) { + pub fn deinit(&self) { self.compositor.borrow_mut().deinit(); } - pub fn present(&mut self) { + pub fn present(&self) { self.compositor.borrow_mut().present(); } @@ -1011,12 +1066,320 @@ impl Servo { } pub fn new_webview(&self, url: url::Url) -> WebView { - WebView::new(&self.constellation_proxy, self.compositor.clone(), url) + let webview = WebView::new(&self.constellation_proxy, self.compositor.clone()); + self.webviews + .borrow_mut() + .insert(webview.id(), webview.weak_handle()); + self.constellation_proxy + .send(ConstellationMsg::NewWebView(url.into(), webview.id())); + webview } - /// FIXME: Remove this once we have a webview delegate. pub fn new_auxiliary_webview(&self) -> WebView { - WebView::new_auxiliary(&self.constellation_proxy, self.compositor.clone()) + let webview = WebView::new(&self.constellation_proxy, self.compositor.clone()); + self.webviews + .borrow_mut() + .insert(webview.id(), webview.weak_handle()); + webview + } + + fn get_webview_handle(&self, id: WebViewId) -> Option<WebView> { + self.webviews + .borrow() + .get(&id) + .and_then(WebView::from_weak_handle) + } + + fn call_delegate_methods(&self) { + let events = self.get_events(); + for event in events { + match event { + EmbedderMsg::Status(webview_id, status_text) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.set_status_text(status_text); + } + }, + EmbedderMsg::ChangePageTitle(webview_id, title) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.set_page_title(title); + } + }, + EmbedderMsg::MoveTo(webview_id, position) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().request_move_to(webview, position); + } + }, + EmbedderMsg::ResizeTo(webview_id, size) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().request_resize_to(webview, size); + } + }, + EmbedderMsg::Prompt(webview_id, prompt_definition, prompt_origin) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .show_prompt(webview, prompt_definition, prompt_origin); + } + }, + EmbedderMsg::ShowContextMenu(webview_id, ipc_sender, title, items) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .show_context_menu(webview, ipc_sender, title, items); + } + }, + EmbedderMsg::AllowNavigationRequest(webview_id, pipeline_id, servo_url) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + let request = NavigationRequest { + url: servo_url.into_url(), + pipeline_id, + constellation_proxy: self.constellation_proxy.clone(), + response_sent: false, + }; + webview.delegate().request_navigation(webview, request); + } + }, + EmbedderMsg::AllowOpeningWebView(webview_id, response_sender) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + let new_webview = + webview.delegate().request_open_auxiliary_webview(webview); + let _ = response_sender.send(new_webview.map(|webview| webview.id())); + } + }, + EmbedderMsg::WebViewOpened(webview_id) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().notify_ready_to_show(webview); + } + }, + EmbedderMsg::WebViewClosed(_) => {}, + EmbedderMsg::WebViewFocused(webview_id) => { + for id in self.webviews.borrow().keys() { + if let Some(webview) = self.get_webview_handle(*id) { + let focused = webview.id() == webview_id; + webview.set_focused(focused); + } + } + }, + EmbedderMsg::WebViewBlurred => { + for id in self.webviews.borrow().keys() { + if let Some(webview) = self.get_webview_handle(*id) { + webview.set_focused(false); + } + } + }, + EmbedderMsg::AllowUnload(webview_id, response_sender) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + let request = AllowOrDenyRequest { + response_sender, + response_sent: false, + default_response: true, + }; + webview.delegate().request_unload(webview, request); + } + }, + EmbedderMsg::Keyboard(webview_id, keyboard_event) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .notify_keyboard_event(webview, keyboard_event); + } + }, + EmbedderMsg::ClearClipboardContents(webview_id) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().clear_clipboard_contents(webview); + } + }, + EmbedderMsg::GetClipboardContents(webview_id, ipc_sender) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .get_clipboard_contents(webview, ipc_sender); + } + }, + EmbedderMsg::SetClipboardContents(webview_id, string) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().set_clipboard_contents(webview, string); + } + }, + EmbedderMsg::SetCursor(webview_id, cursor) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.set_cursor(cursor); + } + }, + EmbedderMsg::NewFavicon(webview_id, url) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.set_favicon_url(url.into_url()); + } + }, + EmbedderMsg::NotifyLoadStatusChanged(webview_id, load_status) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.set_load_status(load_status); + } + }, + EmbedderMsg::HistoryChanged(webview_id, urls, current_index) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + let urls: Vec<_> = urls.into_iter().map(ServoUrl::into_url).collect(); + let current_url = urls[current_index].clone(); + + webview.delegate().notify_history_changed( + webview.clone(), + urls, + current_index, + ); + webview.set_url(current_url); + } + }, + EmbedderMsg::SetFullscreenState(webview_id, fullscreen) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .request_fullscreen_state_change(webview, fullscreen); + } + }, + EmbedderMsg::WebResourceRequested( + webview_id, + 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, + response_sender, + ); + }, + EmbedderMsg::Panic(webview_id, reason, backtrace) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .notify_crashed(webview, reason, backtrace); + } + }, + EmbedderMsg::GetSelectedBluetoothDevice(webview_id, items, response_sender) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().show_bluetooth_device_dialog( + webview, + items, + response_sender, + ); + } + }, + EmbedderMsg::SelectFiles( + webview_id, + filter_patterns, + allow_select_multiple, + response_sender, + ) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().show_file_selection_dialog( + webview, + filter_patterns, + allow_select_multiple, + response_sender, + ); + } + }, + EmbedderMsg::PromptPermission(webview_id, permission_prompt, result_sender) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().request_permission( + webview, + permission_prompt, + result_sender, + ); + } + }, + EmbedderMsg::ShowIME(webview_id, input_method_type, text, multiline, position) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().show_ime( + webview, + input_method_type, + text, + multiline, + position, + ); + } + }, + EmbedderMsg::HideIME(webview_id) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().hide_ime(webview); + } + }, + EmbedderMsg::ReportProfile(_items) => {}, + EmbedderMsg::MediaSessionEvent(webview_id, media_session_event) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview + .delegate() + .notify_media_session_event(webview, media_session_event); + } + }, + EmbedderMsg::OnDevtoolsStarted(port, token) => match port { + Ok(port) => self + .delegate() + .notify_devtools_server_started(self, port, token), + Err(()) => self + .delegate() + .notify_error(self, ServoError::DevtoolsFailedToStart), + }, + EmbedderMsg::RequestDevtoolsConnection(response_sender) => { + self.delegate().request_devtools_connection( + self, + AllowOrDenyRequest { + response_sender, + response_sent: false, + default_response: false, + }, + ); + }, + EmbedderMsg::ReadyToPresent(webview_ids) => { + for webview_id in webview_ids { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().notify_new_frame_ready(webview); + } + } + }, + EmbedderMsg::EventDelivered(webview_id, event) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().notify_event_delivered(webview, event); + } + }, + EmbedderMsg::PlayGamepadHapticEffect( + webview_id, + gamepad_index, + gamepad_haptic_effect_type, + ipc_sender, + ) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().play_gamepad_haptic_effect( + webview, + gamepad_index, + gamepad_haptic_effect_type, + ipc_sender, + ); + } + }, + EmbedderMsg::StopGamepadHapticEffect(webview_id, gamepad_index, ipc_sender) => { + if let Some(webview) = self.get_webview_handle(webview_id) { + webview.delegate().stop_gamepad_haptic_effect( + webview, + gamepad_index, + ipc_sender, + ); + } + }, + EmbedderMsg::Shutdown => { + // This message isn't necessary in the new API -- it's the return value of `spin_event_loop`. + }, + } + } } } diff --git a/components/servo/proxies.rs b/components/servo/proxies.rs index 526eeea21f3..248e1e66f66 100644 --- a/components/servo/proxies.rs +++ b/components/servo/proxies.rs @@ -23,6 +23,10 @@ impl ConstellationProxy { } } + pub fn disconnected(&self) -> bool { + self.disconnected.load(Ordering::SeqCst) + } + pub fn send(&self, msg: ConstellationMsg) { if self.try_send(msg).is_err() { warn!("Lost connection to Constellation. Will report to embedder.") @@ -30,7 +34,7 @@ impl ConstellationProxy { } pub fn try_send(&self, msg: ConstellationMsg) -> Result<(), SendError<ConstellationMsg>> { - if self.disconnected.load(Ordering::SeqCst) { + if self.disconnected() { return Err(SendError(msg)); } if let Err(error) = self.sender.send(msg) { diff --git a/components/servo/servo_delegate.rs b/components/servo/servo_delegate.rs new file mode 100644 index 00000000000..379be8a8513 --- /dev/null +++ b/components/servo/servo_delegate.rs @@ -0,0 +1,47 @@ +/* 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 embedder_traits::{WebResourceRequest, WebResourceResponseMsg}; +use ipc_channel::ipc::IpcSender; + +use crate::webview_delegate::AllowOrDenyRequest; +use crate::{Servo, WebView}; + +#[derive(Clone, Copy, Debug, Hash, PartialEq)] +pub enum ServoError { + /// The channel to the off-the-main-thread web engine has been lost. No further + /// attempts to communicate will happen. This is an unrecoverable error in Servo. + LostConnectionWithBackend, + /// The devtools server, used to expose pages to remote web inspectors has failed + /// to start. + DevtoolsFailedToStart, +} + +pub trait ServoDelegate { + /// Notification that Servo has received a major error. + fn notify_error(&self, _servo: &Servo, _error: ServoError) {} + /// Report that the DevTools server has started on the given `port`. The `token` that + /// be used to bypass the permission prompt from the DevTools client. + fn notify_devtools_server_started(&self, _servo: &Servo, _port: u16, _token: String) {} + /// 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. + /// + /// 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<WebView>, + _request: &WebResourceRequest, + response_sender: IpcSender<WebResourceResponseMsg>, + ) { + let _ = response_sender.send(WebResourceResponseMsg::None); + } +} + +pub(crate) struct DefaultServoDelegate; +impl ServoDelegate for DefaultServoDelegate {} diff --git a/components/servo/webview.rs b/components/servo/webview.rs index a47ccaf04ff..b2a286fad7a 100644 --- a/components/servo/webview.rs +++ b/components/servo/webview.rs @@ -2,9 +2,10 @@ * 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::cell::RefCell; +use std::cell::{Ref, RefCell, RefMut}; use std::ffi::c_void; -use std::rc::Rc; +use std::hash::Hash; +use std::rc::{Rc, Weak}; use std::time::Duration; use base::id::WebViewId; @@ -12,24 +13,47 @@ use compositing::windowing::{MouseWindowEvent, WebRenderDebugOption}; use compositing::IOCompositor; use compositing_traits::ConstellationMsg; use embedder_traits::{ - ClipboardEventType, GamepadEvent, MediaSessionActionType, Theme, TouchEventType, TouchId, - TraversalDirection, WheelDelta, + ClipboardEventType, Cursor, GamepadEvent, LoadStatus, MediaSessionActionType, Theme, + TouchEventType, TouchId, TraversalDirection, WheelDelta, }; use keyboard_types::{CompositionEvent, KeyboardEvent}; use url::Url; use webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePoint, DeviceRect}; use webrender_api::ScrollLocation; +use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate}; use crate::ConstellationProxy; #[derive(Clone)] -pub struct WebView(Rc<WebViewInner>); +pub struct WebView(Rc<RefCell<WebViewInner>>); -struct WebViewInner { +impl PartialEq for WebView { + fn eq(&self, other: &Self) -> bool { + self.inner().id == other.inner().id + } +} + +impl Hash for WebView { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.inner().id.hash(state); + } +} + +pub(crate) struct WebViewInner { // TODO: ensure that WebView instances interact with the correct Servo instance pub(crate) id: WebViewId, pub(crate) constellation_proxy: ConstellationProxy, pub(crate) compositor: Rc<RefCell<IOCompositor>>, + pub(crate) delegate: Rc<dyn WebViewDelegate>, + + rect: DeviceRect, + load_status: LoadStatus, + url: Option<Url>, + status_text: Option<String>, + page_title: Option<String>, + favicon_url: Option<Url>, + focused: bool, + cursor: Cursor, } impl Drop for WebViewInner { @@ -49,61 +73,175 @@ impl WebView { pub(crate) fn new( constellation_proxy: &ConstellationProxy, compositor: Rc<RefCell<IOCompositor>>, - url: Url, ) -> Self { - let webview_id = WebViewId::new(); - constellation_proxy.send(ConstellationMsg::NewWebView(url.into(), webview_id)); - - Self(Rc::new(WebViewInner { - id: webview_id, + Self(Rc::new(RefCell::new(WebViewInner { + id: WebViewId::new(), constellation_proxy: constellation_proxy.clone(), compositor, - })) + delegate: Rc::new(DefaultWebViewDelegate), + rect: DeviceRect::zero(), + load_status: LoadStatus::Complete, + url: None, + status_text: None, + page_title: None, + favicon_url: None, + focused: false, + cursor: Cursor::Pointer, + }))) } - /// FIXME: Remove this once we have a webview delegate. - pub(crate) fn new_auxiliary( - constellation_proxy: &ConstellationProxy, - compositor: Rc<RefCell<IOCompositor>>, - ) -> Self { - let webview_id = WebViewId::new(); + fn inner(&self) -> Ref<'_, WebViewInner> { + self.0.borrow() + } + + fn inner_mut(&self) -> RefMut<'_, WebViewInner> { + self.0.borrow_mut() + } + + pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> { + inner.upgrade().map(WebView) + } + + pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> { + Rc::downgrade(&self.0) + } - Self( - WebViewInner { - id: webview_id, - constellation_proxy: constellation_proxy.clone(), - compositor, - } - .into(), - ) + pub fn delegate(&self) -> Rc<dyn WebViewDelegate> { + self.inner().delegate.clone() + } + + pub fn set_delegate(&self, delegate: Rc<dyn WebViewDelegate>) { + self.inner_mut().delegate = delegate; } - /// FIXME: Remove this once we have a webview delegate. pub fn id(&self) -> WebViewId { - self.0.id + self.inner().id + } + + pub fn load_status(&self) -> LoadStatus { + self.inner().load_status + } + + pub(crate) fn set_load_status(self, new_value: LoadStatus) { + if self.inner().load_status == new_value { + return; + } + self.inner_mut().load_status = new_value; + self.delegate().notify_load_status_changed(self, new_value); + } + + pub fn url(&self) -> Option<Url> { + self.inner().url.clone() + } + + pub(crate) fn set_url(self, new_value: Url) { + if self + .inner() + .url + .as_ref() + .is_some_and(|url| url == &new_value) + { + return; + } + self.inner_mut().url = Some(new_value.clone()); + self.delegate().notify_url_changed(self, new_value); + } + + pub fn status_text(&self) -> Option<String> { + self.inner().status_text.clone() + } + + pub(crate) fn set_status_text(self, new_value: Option<String>) { + if self.inner().status_text == new_value { + return; + } + self.inner_mut().status_text = new_value.clone(); + self.delegate().notify_status_text_changed(self, new_value); + } + + pub fn page_title(&self) -> Option<String> { + self.inner().page_title.clone() + } + + pub(crate) fn set_page_title(self, new_value: Option<String>) { + if self.inner().page_title == new_value { + return; + } + self.inner_mut().page_title = new_value.clone(); + self.delegate().notify_page_title_changed(self, new_value); + } + + pub fn favicon_url(&self) -> Option<Url> { + self.inner().favicon_url.clone() + } + + pub(crate) fn set_favicon_url(self, new_value: Url) { + if self + .inner() + .favicon_url + .as_ref() + .is_some_and(|url| url == &new_value) + { + return; + } + self.inner_mut().favicon_url = Some(new_value.clone()); + self.delegate().notify_favicon_url_changed(self, new_value); + } + + pub fn focused(&self) -> bool { + self.inner().focused + } + + pub(crate) fn set_focused(self, new_value: bool) { + if self.inner().focused == new_value { + return; + } + self.inner_mut().focused = new_value; + self.delegate().notify_focus_changed(self, new_value); + } + + pub fn cursor(&self) -> Cursor { + self.inner().cursor + } + + pub(crate) fn set_cursor(self, new_value: Cursor) { + if self.inner().cursor == new_value { + return; + } + self.inner_mut().cursor = new_value; + self.delegate().notify_cursor_changed(self, new_value); } pub fn focus(&self) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::FocusWebView(self.id())); } pub fn blur(&self) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::BlurWebView); } + pub fn rect(&self) -> DeviceRect { + self.inner().rect + } + pub fn move_resize(&self, rect: DeviceRect) { - self.0 + if self.inner().rect == rect { + return; + } + + self.inner_mut().rect = rect; + self.inner() .compositor .borrow_mut() .move_resize_webview(self.id(), rect); } pub fn show(&self, hide_others: bool) { - self.0 + self.inner() .compositor .borrow_mut() .show_webview(self.id(), hide_others) @@ -111,7 +249,7 @@ impl WebView { } pub fn hide(&self) { - self.0 + self.inner() .compositor .borrow_mut() .hide_webview(self.id()) @@ -119,7 +257,7 @@ impl WebView { } pub fn raise_to_top(&self, hide_others: bool) { - self.0 + self.inner() .compositor .borrow_mut() .raise_webview_to_top(self.id(), hide_others) @@ -127,25 +265,25 @@ impl WebView { } pub fn notify_theme_change(&self, theme: Theme) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::ThemeChange(theme)) } pub fn load(&self, url: Url) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::LoadUrl(self.id(), url.into())) } pub fn reload(&self) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::Reload(self.id())) } pub fn go_back(&self, amount: usize) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::TraverseHistory( self.id(), @@ -154,7 +292,7 @@ impl WebView { } pub fn go_forward(&self, amount: usize) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::TraverseHistory( self.id(), @@ -163,28 +301,31 @@ impl WebView { } pub fn notify_pointer_button_event(&self, event: MouseWindowEvent) { - self.0 + self.inner() .compositor .borrow_mut() .on_mouse_window_event_class(event); } pub fn notify_pointer_move_event(&self, event: DevicePoint) { - self.0 + self.inner() .compositor .borrow_mut() .on_mouse_window_move_event_class(event); } pub fn notify_touch_event(&self, event_type: TouchEventType, id: TouchId, point: DevicePoint) { - self.0 + self.inner() .compositor .borrow_mut() .on_touch_event(event_type, id, point); } pub fn notify_wheel_event(&self, delta: WheelDelta, point: DevicePoint) { - self.0.compositor.borrow_mut().on_wheel_event(delta, point); + self.inner() + .compositor + .borrow_mut() + .on_wheel_event(delta, point); } pub fn notify_scroll_event( @@ -193,123 +334,129 @@ impl WebView { point: DeviceIntPoint, touch_event_type: TouchEventType, ) { - self.0 + self.inner() .compositor .borrow_mut() .on_scroll_event(location, point, touch_event_type); } pub fn notify_keyboard_event(&self, event: KeyboardEvent) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::Keyboard(self.id(), event)) } pub fn notify_ime_event(&self, event: CompositionEvent) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::IMECompositionEvent(event)) } pub fn notify_ime_dismissed_event(&self) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::IMEDismissed); } pub fn notify_gamepad_event(&self, event: GamepadEvent) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::Gamepad(event)); } pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::MediaSessionAction(event)); } pub fn notify_clipboard_event(&self, event: ClipboardEventType) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::Clipboard(event)); } pub fn notify_vsync(&self) { - self.0.compositor.borrow_mut().on_vsync(); + self.inner().compositor.borrow_mut().on_vsync(); } pub fn notify_rendering_context_resized(&self) { - self.0 + self.inner() .compositor .borrow_mut() .on_rendering_context_resized(); } pub fn set_zoom(&self, new_zoom: f32) { - self.0 + self.inner() .compositor .borrow_mut() .on_zoom_window_event(new_zoom); } pub fn reset_zoom(&self) { - self.0.compositor.borrow_mut().on_zoom_reset_window_event(); + self.inner() + .compositor + .borrow_mut() + .on_zoom_reset_window_event(); } pub fn set_pinch_zoom(&self, new_pinch_zoom: f32) { - self.0 + self.inner() .compositor .borrow_mut() .on_pinch_zoom_window_event(new_pinch_zoom); } pub fn exit_fullscreen(&self) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::ExitFullScreen(self.id())); } pub fn set_throttled(&self, throttled: bool) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::SetWebViewThrottled(self.id(), throttled)); } pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) { - self.0 + self.inner() .compositor .borrow_mut() .toggle_webrender_debug(debugging); } pub fn capture_webrender(&self) { - self.0.compositor.borrow_mut().capture_webrender(); + self.inner().compositor.borrow_mut().capture_webrender(); } pub fn invalidate_native_surface(&self) { - self.0.compositor.borrow_mut().invalidate_native_surface(); + self.inner() + .compositor + .borrow_mut() + .invalidate_native_surface(); } pub fn composite(&self) { - self.0.compositor.borrow_mut().composite(); + self.inner().compositor.borrow_mut().composite(); } pub fn replace_native_surface(&self, native_widget: *mut c_void, size: DeviceIntSize) { - self.0 + self.inner() .compositor .borrow_mut() .replace_native_surface(native_widget, size); } pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::ToggleProfiler(rate, max_duration)); } pub fn send_error(&self, message: String) { - self.0 + self.inner() .constellation_proxy .send(ConstellationMsg::SendError(Some(self.id()), message)); } diff --git a/components/servo/webview_delegate.rs b/components/servo/webview_delegate.rs new file mode 100644 index 00000000000..5648294b2c6 --- /dev/null +++ b/components/servo/webview_delegate.rs @@ -0,0 +1,262 @@ +/* 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 std::path::PathBuf; + +use base::id::PipelineId; +use compositing_traits::ConstellationMsg; +use embedder_traits::{ + CompositorEventVariant, ContextMenuResult, Cursor, FilterPattern, GamepadHapticEffectType, + InputMethodType, LoadStatus, MediaSessionEvent, PermissionPrompt, PermissionRequest, + PromptDefinition, PromptOrigin, WebResourceRequest, WebResourceResponseMsg, +}; +use ipc_channel::ipc::IpcSender; +use keyboard_types::KeyboardEvent; +use url::Url; +use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; + +use crate::{ConstellationProxy, WebView}; + +/// A request to navigate a [`WebView`] or one of its inner frames. This can be handled +/// asynchronously. If not handled, the request will automatically be allowed. +pub struct NavigationRequest { + pub url: Url, + pub(crate) pipeline_id: PipelineId, + pub(crate) constellation_proxy: ConstellationProxy, + pub(crate) response_sent: bool, +} + +impl NavigationRequest { + pub fn allow(mut self) { + self.constellation_proxy + .send(ConstellationMsg::AllowNavigationResponse( + self.pipeline_id, + true, + )); + self.response_sent = true; + } + + pub fn deny(mut self) { + self.constellation_proxy + .send(ConstellationMsg::AllowNavigationResponse( + self.pipeline_id, + false, + )); + self.response_sent = true; + } +} + +impl Drop for NavigationRequest { + fn drop(&mut self) { + if !self.response_sent { + self.constellation_proxy + .send(ConstellationMsg::AllowNavigationResponse( + self.pipeline_id, + true, + )); + } + } +} + +pub struct AllowOrDenyRequest { + pub(crate) response_sender: IpcSender<bool>, + pub(crate) response_sent: bool, + pub(crate) default_response: bool, +} + +impl AllowOrDenyRequest { + pub fn allow(mut self) { + let _ = self.response_sender.send(true); + self.response_sent = true; + } + + pub fn deny(mut self) { + let _ = self.response_sender.send(false); + self.response_sent = true; + } +} + +impl Drop for AllowOrDenyRequest { + fn drop(&mut self) { + if !self.response_sent { + let _ = self.response_sender.send(self.default_response); + } + } +} + +pub trait WebViewDelegate { + /// The URL of the currently loaded page in this [`WebView`] has changed. The new + /// URL can accessed via [`WebView::url`]. + fn notify_url_changed(&self, _webview: WebView, _url: Url) {} + /// The page title of the currently loaded page in this [`WebView`] has changed. The new + /// title can accessed via [`WebView::page_title`]. + fn notify_page_title_changed(&self, _webview: WebView, _title: Option<String>) {} + /// The status text of the currently loaded page in this [`WebView`] has changed. The new + /// status text can accessed via [`WebView::status_text`]. + fn notify_status_text_changed(&self, _webview: WebView, _status: Option<String>) {} + /// This [`WebView`] has either become focused or lost focus. Whether or not the + /// [`WebView`] is focused can be accessed via [`WebView::focused`]. + fn notify_focus_changed(&self, _webview: WebView, _focused: bool) {} + /// The `LoadStatus` of the currently loading or loaded page in this [`WebView`] has changed. The new + /// status can accessed via [`WebView::load_status`]. + fn notify_load_status_changed(&self, _webview: WebView, _status: LoadStatus) {} + /// The [`Cursor`] of the currently loaded page in this [`WebView`] has changed. The new + /// cursor can accessed via [`WebView::cursor`]. + fn notify_cursor_changed(&self, _webview: WebView, _: Cursor) {} + /// The favicon [`Url`] of the currently loaded page in this [`WebView`] has changed. The new + /// favicon [`Url`] can accessed via [`WebView::favicon_url`]. + fn notify_favicon_url_changed(&self, _webview: WebView, _: Url) {} + + /// A [`WebView`] was created and is now ready to show in the user interface. + fn notify_ready_to_show(&self, _webview: WebView) {} + /// Notify the embedder that it needs to present a new frame. + fn notify_new_frame_ready(&self, _webview: WebView) {} + /// The given event was delivered to a pipeline in the given webview. + fn notify_event_delivered(&self, _webview: WebView, _event: CompositorEventVariant) {} + /// The history state has changed. + // changed pattern; maybe wasteful if embedder doesn’t care? + fn notify_history_changed(&self, _webview: WebView, _: Vec<Url>, _: usize) {} + + /// A keyboard event has been sent to Servo, but remains unprocessed. This allows the + /// embedding application to handle key events while first letting the [`WebView`] + /// have an opportunity to handle it first. Apart from builtin keybindings, page + /// content may expose custom keybindings as well. + fn notify_keyboard_event(&self, _webview: WebView, _: KeyboardEvent) {} + /// A pipeline in the webview panicked. First string is the reason, second one is the backtrace. + fn notify_crashed(&self, _webview: WebView, _reason: String, _backtrace: Option<String>) {} + /// Notifies the embedder about media session events + /// (i.e. when there is metadata for the active media session, playback state changes...). + fn notify_media_session_event(&self, _webview: WebView, _event: MediaSessionEvent) {} + + /// Whether or not to allow a [`WebView`] to load a URL in its main frame or one of its + /// nested `<iframe>`s. [`NavigationRequest`]s are accepted by default. + fn request_navigation(&self, _webview: WebView, _navigation_request: NavigationRequest) {} + /// Whether or not to allow a [`WebView`] to unload a `Document` in its main frame or one + /// of its nested `<iframe>`s. By default, unloads are allowed. + fn request_unload(&self, _webview: WebView, _unload_request: AllowOrDenyRequest) {} + /// Move the window to a point + fn request_move_to(&self, _webview: WebView, _: DeviceIntPoint) {} + /// Resize the window to size + fn request_resize_to(&self, _webview: WebView, _: DeviceIntSize) {} + /// Whether or not to allow script to open a new `WebView`. If not handled by the + /// embedder, these requests are automatically denied. + fn request_open_auxiliary_webview(&self, _parent_webview: WebView) -> Option<WebView> { + None + } + /// Page content has requested that this [`WebView`] be closed. It's the embedder's + /// responsibility to either ignore this request or to remove the [`WebView`] from the + /// interface. + fn request_close(&self, _webview: WebView) {} + /// Open interface to request permission specified by prompt. + fn request_permission( + &self, + _webview: WebView, + _: PermissionPrompt, + result_sender: IpcSender<PermissionRequest>, + ) { + let _ = result_sender.send(PermissionRequest::Denied); + } + + /// Show dialog to user + /// TODO: This API needs to be reworked to match the new model of how responses are sent. + fn show_prompt(&self, _webview: WebView, prompt: PromptDefinition, _: PromptOrigin) { + let _ = match prompt { + PromptDefinition::Alert(_, response_sender) => response_sender.send(()), + PromptDefinition::OkCancel(_, response_sender) => { + response_sender.send(embedder_traits::PromptResult::Dismissed) + }, + PromptDefinition::Input(_, _, response_sender) => response_sender.send(None), + PromptDefinition::Credentials(response_sender) => { + response_sender.send(Default::default()) + }, + }; + } + /// Show a context menu to the user + fn show_context_menu( + &self, + _webview: WebView, + result_sender: IpcSender<ContextMenuResult>, + _: Option<String>, + _: Vec<String>, + ) { + let _ = result_sender.send(ContextMenuResult::Ignored); + } + + /// Inform embedder to clear the clipboard + fn clear_clipboard_contents(&self, _webview: WebView) {} + /// Gets system clipboard contents + fn get_clipboard_contents(&self, _webview: WebView, _: IpcSender<String>) {} + /// Sets system clipboard contents + fn set_clipboard_contents(&self, _webview: WebView, _: String) {} + + /// Enter or exit fullscreen + fn request_fullscreen_state_change(&self, _webview: WebView, _: bool) {} + /// Open dialog to select bluetooth device. + /// TODO: This API needs to be reworked to match the new model of how responses are sent. + fn show_bluetooth_device_dialog( + &self, + _webview: WebView, + _: Vec<String>, + response_sender: IpcSender<Option<String>>, + ) { + let _ = response_sender.send(None); + } + + /// Open file dialog to select files. Set boolean flag to true allows to select multiple files. + fn show_file_selection_dialog( + &self, + _webview: WebView, + _filter_pattern: Vec<FilterPattern>, + _allow_select_mutiple: bool, + response_sender: IpcSender<Option<Vec<PathBuf>>>, + ) { + let _ = response_sender.send(None); + } + + /// Request to present an IME to the user when an editable element is focused. + /// If `type` is [`InputMethodType::Text`], then the `text` parameter specifies + /// the pre-existing text content and the zero-based index into the string + /// of the insertion point. + fn show_ime( + &self, + _webview: WebView, + _type: InputMethodType, + _text: Option<(String, i32)>, + _multiline: bool, + _position: DeviceIntRect, + ) { + } + + /// Request to hide the IME when the editable element is blurred. + fn hide_ime(&self, _webview: WebView) {} + + /// Request to play a haptic effect on a connected gamepad. + fn play_gamepad_haptic_effect( + &self, + _webview: WebView, + _: usize, + _: GamepadHapticEffectType, + _: IpcSender<bool>, + ) { + } + /// Request to stop a haptic effect on a connected gamepad. + fn stop_gamepad_haptic_effect(&self, _webview: WebView, _: usize, _: IpcSender<bool>) {} + + /// Potentially intercept a resource request. If not handled, the request will not be intercepted. + /// + /// 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<WebResourceResponseMsg>, + ) { + } +} + +pub(crate) struct DefaultWebViewDelegate; +impl WebViewDelegate for DefaultWebViewDelegate {} |