#![cfg(feature = "servo-embed")] use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; use servo::{ EventLoopWaker, ServoBuilder, ServoDelegate, ServoUrl, WebViewBuilder, WebViewDelegate, }; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, window::{Window, WindowAttributes, WindowId}, }; // ── Event loop waker ────────────────────────────────────────────────────────── #[derive(Clone)] struct WeftEventLoopWaker { proxy: Arc>>, } #[derive(Debug, Clone)] struct ServoWake; impl EventLoopWaker for WeftEventLoopWaker { fn clone_box(&self) -> Box { Box::new(self.clone()) } fn wake(&self) { let _ = self .proxy .lock() .unwrap_or_else(|p| p.into_inner()) .send_event(ServoWake); } } // ── Servo delegate ──────────────────────────────────────────────────────────── struct WeftServoDelegate; impl ServoDelegate for WeftServoDelegate { fn notify_error(&self, error: servo::ServoError) { tracing::error!(?error, "Servo error"); } } // ── WebView delegate ────────────────────────────────────────────────────────── struct WeftWebViewDelegate { redraw_requested: Arc, } impl WebViewDelegate for WeftWebViewDelegate { fn notify_new_frame_ready(&self, _webview: servo::WebView) { self.redraw_requested .store(true, std::sync::atomic::Ordering::Relaxed); } } // ── Application state ───────────────────────────────────────────────────────── struct App { url: ServoUrl, window: Option>, servo: Option, webview: Option, redraw_requested: Arc, waker: WeftEventLoopWaker, shutting_down: bool, } impl App { fn new(url: ServoUrl, waker: WeftEventLoopWaker) -> Self { Self { url, window: None, servo: None, webview: None, redraw_requested: Arc::new(std::sync::atomic::AtomicBool::new(false)), waker, shutting_down: false, } } fn blit_to_window(window: &Arc, rendering_context: &servo::SoftwareRenderingContext) { let size = window.inner_size(); let Some(pixels) = rendering_context.read_pixels() else { return; }; let ctx = softbuffer::Context::new(Arc::clone(window)).expect("softbuffer context"); let mut surface = softbuffer::Surface::new(&ctx, Arc::clone(window)).expect("softbuffer surface"); let _ = surface.resize( std::num::NonZeroU32::new(size.width).unwrap_or(std::num::NonZeroU32::new(1).unwrap()), std::num::NonZeroU32::new(size.height).unwrap_or(std::num::NonZeroU32::new(1).unwrap()), ); let Ok(mut buf) = surface.buffer_mut() else { return; }; for (dst, src) in buf.iter_mut().zip(pixels.chunks(4)) { *dst = u32::from_be_bytes([0, src[0], src[1], src[2]]); } let _ = buf.present(); } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.window.is_some() { return; } let attrs = WindowAttributes::default().with_title("WEFT Shell"); let window = Arc::new( event_loop .create_window(attrs) .expect("failed to create shell window"), ); let size = window.inner_size(); self.window = Some(Arc::clone(&window)); let servo = ServoBuilder::default() .event_loop_waker(Box::new(self.waker.clone())) .build(); servo.set_delegate(Rc::new(WeftServoDelegate)); let rendering_context = Rc::new( servo::SoftwareRenderingContext::new(servo::euclid::Size2D::new( size.width, size.height, )) .expect("SoftwareRenderingContext"), ); let webview = WebViewBuilder::new(&servo, Rc::clone(&rendering_context)) .delegate(Rc::new(WeftWebViewDelegate { redraw_requested: Arc::clone(&self.redraw_requested), })) .url(self.url.clone()) .build(); self.servo = Some(servo); self.webview = Some(webview); } fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: ServoWake) { if let Some(servo) = &self.servo { servo.spin_event_loop(); } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if self.shutting_down { event_loop.exit(); return; } if let Some(servo) = &self.servo { servo.spin_event_loop(); } if self .redraw_requested .swap(false, std::sync::atomic::Ordering::Relaxed) { if let Some(w) = &self.window { w.request_redraw(); } } } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent, ) { match event { WindowEvent::RedrawRequested => { if let (Some(window), Some(servo)) = (&self.window, &self.servo) { if let Some(wv) = &self.webview { let rc = wv.rendering_context(); Self::blit_to_window(window, rc); } servo.spin_event_loop(); } } WindowEvent::Resized(new_size) => { if let Some(wv) = &self.webview { wv.resize(servo::euclid::Size2D::new(new_size.width, new_size.height)); } } WindowEvent::CloseRequested => { self.shutting_down = true; if let Some(servo) = &self.servo { servo.start_shutting_down(); } event_loop.exit(); } _ => {} } } } // ── Public entry point ──────────────────────────────────────────────────────── pub fn run(html_path: &Path, _ws_port: u16) -> anyhow::Result<()> { let url_str = format!("file://{}", html_path.display()); let url = ServoUrl::parse(&url_str).map_err(|e| anyhow::anyhow!("invalid URL {url_str}: {e}"))?; let event_loop = EventLoop::::with_user_event() .build() .map_err(|e| anyhow::anyhow!("event loop: {e}"))?; let waker = WeftEventLoopWaker { proxy: Arc::new(Mutex::new(event_loop.create_proxy())), }; let mut app = App::new(url, waker); event_loop .run_app(&mut app) .map_err(|e| anyhow::anyhow!("event loop run: {e}")) }