diff options
author | Delan Azabani <dazabani@igalia.com> | 2025-02-05 18:08:40 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-05 10:08:40 +0000 |
commit | 175f28866dc254a98c4a911eb38ed9b200fdc6d1 (patch) | |
tree | e5f89c66cd473e9ed76c66a057e4bc3cc5451fb0 /components/servo/examples | |
parent | 789736590b0dd0806bffeb279f9a9bda9ede0dfc (diff) | |
download | servo-175f28866dc254a98c4a911eb38ed9b200fdc6d1.tar.gz servo-175f28866dc254a98c4a911eb38ed9b200fdc6d1.zip |
libservo: Add WebViewDelegate and ServoDelegate and port `winit_minimal` (#35196)
This change adds the second major part of the new API: delegates which
have methods called by the Servo loop. When a delegate is set on a
`WebView` or on `Servo` itself, the event loop will call into
appropriate delegate methods. Applications can implement the delegate on
their own structs to add special behavior per-`WebView` or for all
`WebView`s.
In addition, each delegate has a default implementation, which
automatically exposes "reasonable" behavior such as by-default allowing
navigation.
There's a lot more work to do here, such as refining the delegate
methods so that they all have nice interfaces, particulary with regard
to delegate methods that need an asynchronous response. This will be
handed gradually as we keep working on the API.
Signed-off-by: Delan Azabani <dazabani@igalia.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Diffstat (limited to 'components/servo/examples')
-rw-r--r-- | components/servo/examples/winit_minimal.rs | 153 |
1 files changed, 79 insertions, 74 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(), + } } } }, |