aboutsummaryrefslogtreecommitdiffstats
path: root/components/servo/examples
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2025-01-23 20:15:53 +0800
committerGitHub <noreply@github.com>2025-01-23 12:15:53 +0000
commit2db828f0c7e6704d3d7374ba8688c491ef7fe3e9 (patch)
tree9ba23bffcba079f993695a04047a6c8cf9cea29d /components/servo/examples
parent8740c03682c2108146fca7a3ba5a7a145ca0d5c3 (diff)
downloadservo-2db828f0c7e6704d3d7374ba8688c491ef7fe3e9.tar.gz
servo-2db828f0c7e6704d3d7374ba8688c491ef7fe3e9.zip
Add minimal libservo example using winit (#35118)
* Add minimal libservo example using winit Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com> * CI: include examples in libservo compile test Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com> * CI: build libservo with `continue-on-error` Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com> --------- Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components/servo/examples')
-rw-r--r--components/servo/examples/winit_minimal.rs265
1 files changed, 265 insertions, 0 deletions
diff --git a/components/servo/examples/winit_minimal.rs b/components/servo/examples/winit_minimal.rs
new file mode 100644
index 00000000000..56a42ac4cc3
--- /dev/null
+++ b/components/servo/examples/winit_minimal.rs
@@ -0,0 +1,265 @@
+/* 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::cell::Cell;
+use std::error::Error;
+use std::mem::replace;
+use std::rc::Rc;
+use std::sync::{Arc, Mutex};
+
+use base::id::WebViewId;
+use compositing::windowing::{AnimationState, EmbedderEvent, EmbedderMethods, WindowMethods};
+use embedder_traits::EmbedderMsg;
+use euclid::{Point2D, Scale, Size2D};
+use servo::Servo;
+use servo_geometry::DeviceIndependentPixel;
+use servo_url::ServoUrl;
+use surfman::{Connection, SurfaceType};
+use tracing::warn;
+use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DevicePixel};
+use webrender_traits::RenderingContext;
+use winit::application::ApplicationHandler;
+use winit::dpi::{PhysicalPosition, PhysicalSize};
+use winit::event::WindowEvent;
+use winit::event_loop::EventLoop;
+use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
+use winit::window::Window;
+
+fn main() -> Result<(), Box<dyn Error>> {
+ rustls::crypto::ring::default_provider()
+ .install_default()
+ .expect("Failed to install crypto provider");
+
+ let event_loop = EventLoop::with_user_event()
+ .build()
+ .expect("Failed to create EventLoop");
+ let mut app = App::new(&event_loop);
+ event_loop.run_app(&mut app)?;
+
+ Ok(())
+}
+
+enum App {
+ Initial(Waker),
+ Running {
+ window_delegate: Rc<WindowDelegate>,
+ servo: Servo<WindowDelegate>,
+ },
+ Exiting,
+}
+
+impl App {
+ fn new(event_loop: &EventLoop<WakerEvent>) -> Self {
+ Self::Initial(Waker::new(event_loop))
+ }
+}
+
+impl ApplicationHandler<WakerEvent> for App {
+ fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
+ if let Self::Initial(waker) = self {
+ let window = event_loop
+ .create_window(Window::default_attributes())
+ .expect("Failed to create winit Window");
+ let display_handle = event_loop
+ .display_handle()
+ .expect("Failed to get display handle");
+ let connection = Connection::from_display_handle(display_handle)
+ .expect("Failed to create connection");
+ let adapter = connection
+ .create_adapter()
+ .expect("Failed to create adapter");
+ let rendering_context = RenderingContext::create(&connection, &adapter, None)
+ .expect("Failed to create rendering context");
+ let native_widget = rendering_context
+ .connection()
+ .create_native_widget_from_window_handle(
+ window.window_handle().expect("Failed to get window handle"),
+ winit_size_to_euclid_size(window.inner_size())
+ .to_i32()
+ .to_untyped(),
+ )
+ .expect("Failed to create native widget");
+ let surface = rendering_context
+ .create_surface(SurfaceType::Widget { native_widget })
+ .expect("Failed to create surface");
+ rendering_context
+ .bind_surface(surface)
+ .expect("Failed to bind surface");
+ rendering_context
+ .make_gl_context_current()
+ .expect("Failed to make context current");
+ let window_delegate = Rc::new(WindowDelegate::new(window));
+ let mut servo = Servo::new(
+ Default::default(),
+ Default::default(),
+ rendering_context,
+ Box::new(EmbedderDelegate {
+ waker: waker.clone(),
+ }),
+ window_delegate.clone(),
+ Default::default(),
+ compositing::CompositeTarget::Window,
+ );
+ servo.setup_logging();
+ servo.handle_events([EmbedderEvent::NewWebView(
+ ServoUrl::parse("https://demo.servo.org/experiments/twgl-tunnel/")
+ .expect("Guaranteed by argument"),
+ WebViewId::new(),
+ )]);
+ *self = Self::Running {
+ window_delegate,
+ servo,
+ };
+ }
+ }
+
+ fn window_event(
+ &mut self,
+ event_loop: &winit::event_loop::ActiveEventLoop,
+ _window_id: winit::window::WindowId,
+ event: WindowEvent,
+ ) {
+ if let Self::Running {
+ window_delegate,
+ servo,
+ } = self
+ {
+ let mut events_for_servo = vec![];
+ for (_webview_id, message) in servo.get_events() {
+ match message {
+ // FIXME: rust-analyzer autocompletes this as top_level_browsing_context_id
+ EmbedderMsg::WebViewOpened(webview_id) => {
+ let rect = window_delegate.get_coordinates().get_viewport().to_f32();
+ events_for_servo.extend([
+ EmbedderEvent::FocusWebView(webview_id),
+ EmbedderEvent::MoveResizeWebView(webview_id, rect),
+ EmbedderEvent::RaiseWebViewToTop(webview_id, true),
+ ]);
+ },
+ _ => {},
+ }
+ }
+ servo.handle_events(events_for_servo);
+ }
+ match event {
+ WindowEvent::CloseRequested => {
+ if matches!(self, Self::Running { .. }) {
+ let Self::Running { servo, .. } = replace(self, Self::Exiting) else {
+ unreachable!()
+ };
+ // TODO: ask Servo to shut down and wait for EmbedderMsg::Shutdown?
+ servo.deinit();
+ }
+ event_loop.exit();
+ },
+ WindowEvent::RedrawRequested => {
+ if let Self::Running {
+ window_delegate,
+ servo,
+ } = self
+ {
+ servo.present();
+ window_delegate.window.request_redraw();
+ }
+ },
+ _ => (),
+ }
+ }
+}
+
+struct EmbedderDelegate {
+ waker: Waker,
+}
+
+impl EmbedderMethods for EmbedderDelegate {
+ // FIXME: rust-analyzer “Implement missing members” autocompletes this as
+ // webxr_api::MainThreadWaker, which is not available when building without
+ // libservo/webxr, and even if it was, it would fail to compile with E0053.
+ fn create_event_loop_waker(&mut self) -> Box<dyn embedder_traits::EventLoopWaker> {
+ Box::new(self.waker.clone())
+ }
+}
+
+#[derive(Clone)]
+struct Waker(Arc<Mutex<winit::event_loop::EventLoopProxy<WakerEvent>>>);
+#[derive(Debug)]
+struct WakerEvent;
+
+impl Waker {
+ fn new(event_loop: &EventLoop<WakerEvent>) -> Self {
+ Self(Arc::new(Mutex::new(event_loop.create_proxy())))
+ }
+}
+
+impl embedder_traits::EventLoopWaker for Waker {
+ fn clone_box(&self) -> Box<dyn embedder_traits::EventLoopWaker> {
+ Box::new(Self(self.0.clone()))
+ }
+
+ fn wake(&self) {
+ if let Err(error) = self
+ .0
+ .lock()
+ .expect("Failed to lock EventLoopProxy")
+ .send_event(WakerEvent)
+ {
+ warn!(?error, "Failed to wake event loop");
+ }
+ }
+}
+
+struct WindowDelegate {
+ window: Window,
+ animation_state: Cell<AnimationState>,
+}
+
+impl WindowDelegate {
+ fn new(window: Window) -> Self {
+ Self {
+ window,
+ animation_state: Cell::new(AnimationState::Idle),
+ }
+ }
+}
+
+impl WindowMethods for WindowDelegate {
+ fn get_coordinates(&self) -> compositing::windowing::EmbedderCoordinates {
+ let monitor = self
+ .window
+ .current_monitor()
+ .or_else(|| self.window.available_monitors().nth(0))
+ .expect("Failed to get winit monitor");
+ let scale =
+ Scale::<f64, DeviceIndependentPixel, DevicePixel>::new(self.window.scale_factor());
+ let window_size = winit_size_to_euclid_size(self.window.outer_size()).to_i32();
+ let window_origin = self.window.outer_position().unwrap_or_default();
+ let window_origin = winit_position_to_euclid_point(window_origin).to_i32();
+ let window_rect = DeviceIntRect::from_origin_and_size(window_origin, window_size);
+ let viewport_origin = DeviceIntPoint::zero(); // bottom left
+ let viewport_size = winit_size_to_euclid_size(self.window.inner_size()).to_f32();
+ let viewport = DeviceIntRect::from_origin_and_size(viewport_origin, viewport_size.to_i32());
+
+ compositing::windowing::EmbedderCoordinates {
+ hidpi_factor: Scale::new(self.window.scale_factor() as f32),
+ screen_size: (winit_size_to_euclid_size(monitor.size()).to_f64() / scale).to_i32(),
+ available_screen_size: (winit_size_to_euclid_size(monitor.size()).to_f64() / scale)
+ .to_i32(),
+ window_rect: (window_rect.to_f64() / scale).to_i32(),
+ framebuffer: viewport.size(),
+ viewport,
+ }
+ }
+
+ fn set_animation_state(&self, state: compositing::windowing::AnimationState) {
+ self.animation_state.set(state);
+ }
+}
+
+pub fn winit_size_to_euclid_size<T>(size: PhysicalSize<T>) -> Size2D<T, DevicePixel> {
+ Size2D::new(size.width, size.height)
+}
+
+pub fn winit_position_to_euclid_point<T>(position: PhysicalPosition<T>) -> Point2D<T, DevicePixel> {
+ Point2D::new(position.x, position.y)
+}