From 9e60aaecb23fe4e35f47d541bbe6af0b154e9679 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Thu, 20 Nov 2025 07:26:00 +0100 Subject: [PATCH 1/8] refactor: Consolidate Servo initialization and event handling into a new `webview` module. --- examples/servo/src/delegate.rs | 11 +- examples/servo/src/lib.rs | 81 ++++---- examples/servo/src/on_events.rs | 194 ------------------ .../servo_rendering_adapter.rs | 11 +- examples/servo/src/servo_util.rs | 146 ------------- examples/servo/src/webview/mod.rs | 4 + examples/servo/src/webview/webview.rs | 150 ++++++++++++++ examples/servo/src/webview/webview_events.rs | 181 ++++++++++++++++ examples/servo/ui/app.slint | 10 +- examples/servo/ui/webview.slint | 16 ++ 10 files changed, 404 insertions(+), 400 deletions(-) delete mode 100644 examples/servo/src/on_events.rs delete mode 100644 examples/servo/src/servo_util.rs create mode 100644 examples/servo/src/webview/mod.rs create mode 100644 examples/servo/src/webview/webview.rs create mode 100644 examples/servo/src/webview/webview_events.rs diff --git a/examples/servo/src/delegate.rs b/examples/servo/src/delegate.rs index 465a252230d..e458da2a953 100644 --- a/examples/servo/src/delegate.rs +++ b/examples/servo/src/delegate.rs @@ -3,18 +3,17 @@ use std::rc::Rc; -use servo::{WebView, WebViewDelegate}; - use crate::{MyApp, adapter::SlintServoAdapter}; +use servo::{WebView, WebViewDelegate}; pub struct AppDelegate { - pub state: Rc, pub app: slint::Weak, + pub adapter: Rc, } impl AppDelegate { - pub fn new(state: Rc, app: slint::Weak) -> Self { - Self { state, app } + pub fn new(app: slint::Weak, adapter: Rc) -> Self { + Self { app, adapter } } } @@ -24,7 +23,7 @@ impl WebViewDelegate for AppDelegate { fn notify_new_frame_ready(&self, webview: WebView) { webview.paint(); if let Some(app) = self.app.upgrade() { - self.state.update_web_content_with_latest_frame(&app); + self.adapter.update_web_content_with_latest_frame(&app); } } } diff --git a/examples/servo/src/lib.rs b/examples/servo/src/lib.rs index 42655867190..3cc63836c34 100644 --- a/examples/servo/src/lib.rs +++ b/examples/servo/src/lib.rs @@ -3,10 +3,9 @@ mod adapter; mod delegate; -mod on_events; mod rendering_context; -mod servo_util; mod waker; +mod webview; #[cfg(target_os = "linux")] mod gl_bindings { @@ -17,54 +16,21 @@ mod gl_bindings { use slint::ComponentHandle; -use crate::servo_util::init_servo; +#[cfg(not(target_os = "android"))] +use crate::webview::WebView; slint::include_modules!(); pub fn main() { #[cfg(not(target_os = "android"))] - let (device, queue) = { - let backends = wgpu::Backends::from_env().unwrap_or_default(); - - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends, - flags: Default::default(), - backend_options: Default::default(), - memory_budget_thresholds: Default::default(), - }); - - let adapter = spin_on::spin_on(async { - instance - .request_adapter(&Default::default()) - .await - .expect("Failed to find an appropriate WGPU adapter") - }); - - let (device, queue) = spin_on::spin_on(async { - adapter.request_device(&Default::default()).await.expect("Failed to create WGPU device") - }); - - slint::BackendSelector::new() - .require_wgpu_27(slint::wgpu_27::WGPUConfiguration::Manual { - instance, - adapter, - device: device.clone(), - queue: queue.clone() - }) - .select() - .expect("Failed to create Slint backend with WGPU based renderer - ensure your system supports WGPU"); - - (device, queue) - }; + let (device, queue) = setup_wgpu(); let app = MyApp::new().expect("Failed to create Slint application - check UI resources"); - let url = "https://slint.dev"; - #[cfg(not(target_os = "android"))] - let _adapter = init_servo( + WebView::new( app.clone_strong(), - url.into(), + "https://slint.dev".into(), #[cfg(not(target_os = "android"))] device, #[cfg(not(target_os = "android"))] @@ -80,3 +46,38 @@ pub fn android_main(android_app: slint::android::AndroidApp) { slint::android::init(android_app).unwrap(); main(); } + +#[cfg(not(target_os = "android"))] +fn setup_wgpu() -> (wgpu::Device, wgpu::Queue) { + let backends = wgpu::Backends::from_env().unwrap_or_default(); + + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends, + flags: Default::default(), + backend_options: Default::default(), + memory_budget_thresholds: Default::default(), + }); + + let adapter = spin_on::spin_on(async { + instance + .request_adapter(&Default::default()) + .await + .expect("Failed to find an appropriate WGPU adapter") + }); + + let (device, queue) = spin_on::spin_on(async { + adapter.request_device(&Default::default()).await.expect("Failed to create WGPU device") + }); + + slint::BackendSelector::new() + .require_wgpu_27(slint::wgpu_27::WGPUConfiguration::Manual { + instance, + adapter, + device: device.clone(), + queue: queue.clone() + }) + .select() + .expect("Failed to create Slint backend with WGPU based renderer - ensure your system supports WGPU"); + + (device, queue) +} diff --git a/examples/servo/src/on_events.rs b/examples/servo/src/on_events.rs deleted file mode 100644 index b12f92f492a..00000000000 --- a/examples/servo/src/on_events.rs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -use std::rc::Rc; - -use url::Url; -use winit::dpi::PhysicalSize; - -use euclid::{Box2D, Point2D, Scale, Size2D}; - -use i_slint_core::items::{ColorScheme, PointerEvent, PointerEventKind}; -use slint::{ComponentHandle, platform::PointerEventButton}; - -use servo::{ - InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Scroll, Theme, - TouchEvent, TouchEventType, TouchId, WebViewPoint, - webrender_api::units::{DevicePixel, DevicePoint, DeviceVector2D}, -}; - -use crate::{ - MyApp, WebviewLogic, - adapter::{SlintServoAdapter, upgrade_adapter}, -}; - -pub fn on_app_callbacks(app: &MyApp, adapter: Rc) { - on_url(app, adapter.clone()); - on_theme(app, adapter.clone()); - on_resize(app, adapter.clone()); - on_scroll(app, adapter.clone()); - on_buttons(app, adapter.clone()); - on_pointer(app, adapter.clone()); -} - -fn on_url(app: &MyApp, adapter: Rc) { - let adapter_weak = Rc::downgrade(&adapter); - app.global::().on_loadUrl(move |url| { - let adapter = upgrade_adapter(&adapter_weak); - let webview = adapter.webview(); - let url = Url::parse(url.as_str()).expect("Failed to parse url"); - webview.load(url); - }); -} - -fn on_theme(app: &MyApp, adapter: Rc) { - let adapter_weak = Rc::downgrade(&adapter); - app.global::().on_theme(move |color_scheme| { - let theme = if color_scheme == ColorScheme::Dark { Theme::Dark } else { Theme::Light }; - - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - // Theme not updating until mouse move over it - // https://github.com/servo/servo/issues/40268 - webview.notify_theme_change(theme); - }); -} - -// This will always called when slint window show first time and when resize so set scale factor here -fn on_resize(app: &MyApp, adapter: Rc) { - let adapter_weak = Rc::downgrade(&adapter); - let app_weak = app.as_weak(); - app.global::().on_resize(move |width, height| { - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - let scale_factor = - app_weak.upgrade().expect("Failed to upgrade app").window().scale_factor(); - - let scale = Scale::new(scale_factor); - - webview.set_hidpi_scale_factor(scale); - - let size = Size2D::new(width, height); - - let physical_size = PhysicalSize::new(size.width as u32, size.height as u32); - - let rect: Box2D = Box2D::from_origin_and_size(Point2D::origin(), size); - - webview.move_resize(rect); - webview.resize(physical_size); - }); -} - -fn on_scroll(app: &MyApp, adapter: Rc) { - let adapter_weak = Rc::downgrade(&adapter); - app.global::().on_scroll(move |initial_x, initial_y, delta_x, delta_y| { - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - let point = DevicePoint::new(initial_x, initial_y); - - let moved_by = DeviceVector2D::new(delta_x, delta_y); - - // Invert delta to match Servo's coordinate system - let servo_delta = -moved_by; - - webview.notify_scroll_event(Scroll::Delta(servo_delta.into()), point.into()); - }); -} - -fn on_buttons(app: &MyApp, adapter: Rc) { - let adapter_weak = Rc::downgrade(&adapter); - app.on_back(move || { - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - webview.go_back(1); - }); - - let adapter_weak = Rc::downgrade(&adapter); - app.on_forward(move || { - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - webview.go_forward(1); - }); - - let adapter_weak = Rc::downgrade(&adapter); - app.on_reload(move || { - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - webview.reload(); - }); -} - -fn on_pointer(app: &MyApp, adapter: Rc) { - let adapter_weak = Rc::downgrade(&adapter); - app.global::().on_pointer(move |pointer_event, x, y| { - let adapter = upgrade_adapter(&adapter_weak); - - let webview = adapter.webview(); - - let point = DevicePoint::new(x, y); - - let input_event = - convert_slint_pointer_event_to_servo_input_event(&pointer_event, point.into()); - - webview.notify_input_event(input_event); - }); -} - -/// Converts Slint pointer events to Servo input events. -/// Distinguishes between touch and mouse events for proper handling. -fn convert_slint_pointer_event_to_servo_input_event( - pointer_event: &PointerEvent, - point: WebViewPoint, -) -> InputEvent { - if pointer_event.is_touch { - handle_touch_events(pointer_event, point) - } else { - handle_mouse_events(pointer_event, point) - } -} - -fn handle_touch_events(pointer_event: &PointerEvent, point: WebViewPoint) -> InputEvent { - let touch_id = TouchId(1); - let touch_event = match pointer_event.kind { - PointerEventKind::Down => TouchEvent::new(TouchEventType::Down, touch_id, point), - PointerEventKind::Up => TouchEvent::new(TouchEventType::Up, touch_id, point), - _ => TouchEvent::new(TouchEventType::Move, touch_id, point), - }; - InputEvent::Touch(touch_event) -} - -fn handle_mouse_events(pointer_event: &PointerEvent, point: WebViewPoint) -> InputEvent { - let button = get_mouse_button(pointer_event); - match pointer_event.kind { - PointerEventKind::Down => { - let mouse_event = MouseButtonEvent::new(MouseButtonAction::Down, button, point); - InputEvent::MouseButton(mouse_event) - } - PointerEventKind::Up => { - let mouse_event = MouseButtonEvent::new(MouseButtonAction::Up, button, point); - InputEvent::MouseButton(mouse_event) - } - _ => InputEvent::MouseMove(MouseMoveEvent::new(point)), - } -} - -fn get_mouse_button(point_event: &PointerEvent) -> MouseButton { - match point_event.button { - PointerEventButton::Left => MouseButton::Left, - PointerEventButton::Right => MouseButton::Right, - PointerEventButton::Middle => MouseButton::Middle, - _ => MouseButton::Left, - } -} diff --git a/examples/servo/src/rendering_context/servo_rendering_adapter.rs b/examples/servo/src/rendering_context/servo_rendering_adapter.rs index 6a0df5316fc..becde8be051 100644 --- a/examples/servo/src/rendering_context/servo_rendering_adapter.rs +++ b/examples/servo/src/rendering_context/servo_rendering_adapter.rs @@ -4,14 +4,13 @@ use std::rc::Rc; use euclid::Point2D; -use winit::dpi::PhysicalSize; - use slint::{Image, SharedPixelBuffer}; +use winit::dpi::PhysicalSize; use servo::{RenderingContext, SoftwareRenderingContext, webrender_api::units::DeviceIntRect}; #[cfg(not(target_os = "android"))] -use crate::rendering_context::GPURenderingContext; +use {crate::rendering_context::GPURenderingContext, slint::wgpu_27::wgpu}; pub fn create_software_context(size: PhysicalSize) -> Box { let rendering_context = Rc::new( @@ -74,10 +73,8 @@ impl ServoRenderingAdapter for ServoGPURenderingContext { ); #[cfg(target_vendor = "apple")] - let texture = self - .rendering_context - .get_wgpu_texture_from_metal(&self.device, &self.queue) - .expect( + let texture = + self.rendering_context.get_wgpu_texture_from_metal(&self.device, &self.queue).expect( "Failed to get WGPU texture from Metal texture - ensure rendering context is valid", ); diff --git a/examples/servo/src/servo_util.rs b/examples/servo/src/servo_util.rs deleted file mode 100644 index 8ae14ec63d2..00000000000 --- a/examples/servo/src/servo_util.rs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -use std::rc::Rc; - -use smol::channel; -use url::Url; -use winit::dpi::PhysicalSize; - -use euclid::Size2D; - -use i_slint_core::items::ColorScheme; -use slint::{ComponentHandle, SharedString}; - -use servo::{Servo, ServoBuilder, Theme, WebViewBuilder, webrender_api::units::DevicePixel}; - -use crate::{ - MyApp, Palette, WebviewLogic, - adapter::{SlintServoAdapter, upgrade_adapter}, - delegate::AppDelegate, - on_events::on_app_callbacks, - rendering_context::ServoRenderingAdapter, - waker::Waker, -}; - -pub fn init_servo( - app: MyApp, - initial_url: SharedString, - #[cfg(not(target_os = "android"))] device: slint::wgpu_27::wgpu::Device, - #[cfg(not(target_os = "android"))] queue: slint::wgpu_27::wgpu::Queue, -) -> Rc { - let (waker_sender, waker_receiver) = channel::unbounded::<()>(); - - let adapter = Rc::new(SlintServoAdapter::new( - waker_sender.clone(), - waker_receiver.clone(), - #[cfg(not(target_os = "android"))] - device, - #[cfg(not(target_os = "android"))] - queue, - )); - - let state_weak = Rc::downgrade(&adapter); - - let state = upgrade_adapter(&state_weak); - - let (rendering_adapter, physical_size) = init_rendering_adpater(&app, state.clone()); - - let servo = init_servo_builder(state.clone(), rendering_adapter.clone()); - - init_webview(&app, physical_size, initial_url, state, servo, rendering_adapter); - - spin_servo_event_loop(adapter.clone()); - - on_app_callbacks(&app, adapter.clone()); - - adapter -} - -fn init_rendering_adpater( - app: &MyApp, - adapter: Rc, -) -> (Rc>, PhysicalSize) { - let width = app.global::().get_viewport_width(); - let height = app.global::().get_viewport_height(); - - let size: Size2D = Size2D::new(width, height); - let physical_size = PhysicalSize::new(size.width as u32, size.height as u32); - - #[cfg(not(target_os = "android"))] - let rendering_adapter = crate::rendering_context::try_create_gpu_context( - adapter.wgpu_device(), - adapter.wgpu_queue(), - physical_size, - ) - .unwrap(); - - #[cfg(target_os = "android")] - let rendering_adapter = crate::rendering_context::create_software_context(physical_size); - - let rendering_adapter_rc = Rc::new(rendering_adapter); - - (rendering_adapter_rc, physical_size) -} - -fn init_servo_builder( - adapter: Rc, - rendering_adapter: Rc>, -) -> Servo { - let waker = Waker::new(adapter.waker_sender()); - - let event_loop_waker = Box::new(waker); - - let rendering_context = rendering_adapter.get_rendering_context(); - - ServoBuilder::new(rendering_context).event_loop_waker(event_loop_waker).build() -} - -fn init_webview( - app: &MyApp, - physical_size: PhysicalSize, - initial_url: SharedString, - adapter: Rc, - servo: Servo, - rendering_adapter: Rc>, -) { - app.global::().set_current_url(initial_url.clone()); - - let url = Url::parse(&initial_url).expect("Failed to parse url"); - - let delegate = Rc::new(AppDelegate::new(adapter.clone(), app.as_weak())); - - let webview = - WebViewBuilder::new(&servo).url(url).size(physical_size).delegate(delegate).build(); - - webview.show(true); - - let color_scheme = app.global::().get_color_scheme(); - - let theme = if color_scheme == ColorScheme::Dark { Theme::Dark } else { Theme::Light }; - - webview.notify_theme_change(theme); - - adapter.set_inner(servo, webview, rendering_adapter); -} - -fn spin_servo_event_loop(state: Rc) { - let state_weak = Rc::downgrade(&state); - - slint::spawn_local({ - async move { - loop { - let state = match state_weak.upgrade() { - Some(s) => s, - None => break, - }; - - let _ = state.waker_reciver().recv().await; - if let Some(ref servo) = *state.servo.borrow() { - servo.spin_event_loop(); - } - } - } - }) - .expect("Failed to spawn servo event loop task"); -} diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs new file mode 100644 index 00000000000..a2019710a8e --- /dev/null +++ b/examples/servo/src/webview/mod.rs @@ -0,0 +1,4 @@ +mod webview; +mod webview_events; + +pub use webview::WebView; diff --git a/examples/servo/src/webview/webview.rs b/examples/servo/src/webview/webview.rs new file mode 100644 index 00000000000..42c216d088b --- /dev/null +++ b/examples/servo/src/webview/webview.rs @@ -0,0 +1,150 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +use std::rc::Rc; + +use smol::channel; +use url::Url; +use winit::dpi::PhysicalSize; + +use euclid::Size2D; + +use i_slint_core::items::ColorScheme; +use slint::{ComponentHandle, SharedString}; + +use servo::{Servo, ServoBuilder, Theme, WebViewBuilder, webrender_api::units::DevicePixel}; + +use crate::{ + MyApp, Palette, WebviewLogic, + adapter::{SlintServoAdapter, upgrade_adapter}, + delegate::AppDelegate, + rendering_context::ServoRenderingAdapter, + waker::Waker, +}; + +pub struct WebView {} + +impl WebView { + pub fn new( + app: MyApp, + initial_url: SharedString, + #[cfg(not(target_os = "android"))] device: slint::wgpu_27::wgpu::Device, + #[cfg(not(target_os = "android"))] queue: slint::wgpu_27::wgpu::Queue, + ) { + let (waker_sender, waker_receiver) = channel::unbounded::<()>(); + + let adapter = Rc::new(SlintServoAdapter::new( + waker_sender.clone(), + waker_receiver.clone(), + #[cfg(not(target_os = "android"))] + device, + #[cfg(not(target_os = "android"))] + queue, + )); + + let state_weak = Rc::downgrade(&adapter); + let state = upgrade_adapter(&state_weak); + + let (rendering_adapter, physical_size) = Self::init_rendering_adapter(&app, state.clone()); + + let servo = Self::init_servo_builder(state.clone(), rendering_adapter.clone()); + + Self::init_webview( + &app, + physical_size, + initial_url, + state.clone(), + servo, + rendering_adapter, + ); + + Self::spin_servo_event_loop(adapter.clone()); + + super::webview_events::WebViewEvents::new(&app, adapter.clone()); + } + + fn init_rendering_adapter( + app: &MyApp, + adapter: Rc, + ) -> (Rc>, PhysicalSize) { + let width = app.global::().get_viewport_width(); + let height = app.global::().get_viewport_height(); + + let size: Size2D = Size2D::new(width, height); + let physical_size = PhysicalSize::new(size.width as u32, size.height as u32); + + #[cfg(not(target_os = "android"))] + let rendering_adapter = crate::rendering_context::try_create_gpu_context( + adapter.wgpu_device(), + adapter.wgpu_queue(), + physical_size, + ) + .unwrap(); + + #[cfg(target_os = "android")] + let rendering_adapter = crate::rendering_context::create_software_context(physical_size); + + let rendering_adapter_rc = Rc::new(rendering_adapter); + + (rendering_adapter_rc, physical_size) + } + + fn init_servo_builder( + adapter: Rc, + rendering_adapter: Rc>, + ) -> Servo { + let waker = Waker::new(adapter.waker_sender()); + let event_loop_waker = Box::new(waker); + let rendering_context = rendering_adapter.get_rendering_context(); + + ServoBuilder::new(rendering_context).event_loop_waker(event_loop_waker).build() + } + + fn init_webview( + app: &MyApp, + physical_size: PhysicalSize, + initial_url: SharedString, + adapter: Rc, + servo: Servo, + rendering_adapter: Rc>, + ) { + app.global::().set_current_url(initial_url.clone()); + + let url = Url::parse(&initial_url).expect("Failed to parse url"); + + let delegate = Rc::new(AppDelegate::new(app.as_weak(), adapter.clone())); + + let webview = + WebViewBuilder::new(&servo).url(url).size(physical_size).delegate(delegate).build(); + + webview.show(true); + + let color_scheme = app.global::().get_color_scheme(); + let theme = if color_scheme == ColorScheme::Dark { Theme::Dark } else { Theme::Light }; + + webview.notify_theme_change(theme); + + adapter.set_inner(servo, webview, rendering_adapter); + } + + fn spin_servo_event_loop(state: Rc) { + let state_weak = Rc::downgrade(&state); + + slint::spawn_local({ + async move { + loop { + let state = match state_weak.upgrade() { + Some(s) => s, + None => break, + }; + + let _ = state.waker_reciver().recv().await; + if let Some(ref servo) = *state.servo.borrow() { + servo.spin_event_loop(); + } + } + } + }) + .expect("Failed to spawn servo event loop task"); + } +} diff --git a/examples/servo/src/webview/webview_events.rs b/examples/servo/src/webview/webview_events.rs new file mode 100644 index 00000000000..f4684f08020 --- /dev/null +++ b/examples/servo/src/webview/webview_events.rs @@ -0,0 +1,181 @@ +use std::rc::Rc; + +use url::Url; +use winit::dpi::PhysicalSize; + +use euclid::{Box2D, Point2D, Scale, Size2D}; + +use i_slint_core::items::{ColorScheme, PointerEvent, PointerEventKind}; +use slint::{ComponentHandle, platform::PointerEventButton}; + +use servo::{ + InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Scroll, Theme, + TouchEvent, TouchEventType, TouchId, WebViewPoint, + webrender_api::units::{DevicePixel, DevicePoint, DeviceVector2D}, +}; + +use crate::{ + MyApp, WebviewLogic, + adapter::{SlintServoAdapter, upgrade_adapter}, +}; + +pub struct WebViewEvents<'a> { + app: &'a MyApp, + adapter: Rc, +} + +impl<'a> WebViewEvents<'a> { + pub fn new(app: &'a MyApp, adapter: Rc) { + let instance = Self { app, adapter }; + instance.on_url(); + instance.on_theme(); + instance.on_resize(); + instance.on_scroll(); + instance.on_buttons(); + instance.on_pointer(); + } + + fn on_url(&self) { + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_loadUrl(move |url| { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + let url = Url::parse(url.as_str()).expect("Failed to parse url"); + webview.load(url); + }); + } + + fn on_theme(&self) { + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_theme(move |color_scheme| { + let theme = if color_scheme == ColorScheme::Dark { Theme::Dark } else { Theme::Light }; + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + // Theme not updating until mouse move over it + // https://github.com/servo/servo/issues/40268 + webview.notify_theme_change(theme); + }); + } + + fn on_resize(&self) { + let app_weak = self.app.as_weak(); + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_resize(move |width, height| { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + + let scale_factor = + app_weak.upgrade().expect("Failed to upgrade app").window().scale_factor(); + let scale = Scale::new(scale_factor); + + webview.set_hidpi_scale_factor(scale); + + let size = Size2D::new(width, height); + let physical_size = PhysicalSize::new(size.width as u32, size.height as u32); + let rect: Box2D = + Box2D::from_origin_and_size(Point2D::origin(), size); + + webview.move_resize(rect); + webview.resize(physical_size); + }); + } + + fn on_scroll(&self) { + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_scroll( + move |initial_x, initial_y, delta_x, delta_y| { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + + let point = DevicePoint::new(initial_x, initial_y); + let moved_by = DeviceVector2D::new(delta_x, delta_y); + // Invert delta to match Servo's coordinate system + let servo_delta = -moved_by; + + webview.notify_scroll_event(Scroll::Delta(servo_delta.into()), point.into()); + }, + ); + } + + fn on_buttons(&self) { + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_back(move || { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + webview.go_back(1); + }); + + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_forward(move || { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + webview.go_forward(1); + }); + + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_reload(move || { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + webview.reload(); + }); + } + + fn on_pointer(&self) { + let adapter_weak = Rc::downgrade(&self.adapter); + self.app.global::().on_pointer(move |pointer_event, x, y| { + let adapter = upgrade_adapter(&adapter_weak); + let webview = adapter.webview(); + let point = DevicePoint::new(x, y); + let input_event = Self::convert_slint_pointer_event_to_servo_input_event( + &pointer_event, + point.into(), + ); + webview.notify_input_event(input_event); + }); + } + + fn convert_slint_pointer_event_to_servo_input_event( + pointer_event: &PointerEvent, + point: WebViewPoint, + ) -> InputEvent { + if pointer_event.is_touch { + Self::handle_touch_events(pointer_event, point) + } else { + Self::handle_mouse_events(pointer_event, point) + } + } + + fn handle_touch_events(pointer_event: &PointerEvent, point: WebViewPoint) -> InputEvent { + let touch_id = TouchId(1); + let touch_event = match pointer_event.kind { + PointerEventKind::Down => TouchEvent::new(TouchEventType::Down, touch_id, point), + PointerEventKind::Up => TouchEvent::new(TouchEventType::Up, touch_id, point), + _ => TouchEvent::new(TouchEventType::Move, touch_id, point), + }; + InputEvent::Touch(touch_event) + } + + fn handle_mouse_events(pointer_event: &PointerEvent, point: WebViewPoint) -> InputEvent { + let button = Self::get_mouse_button(pointer_event); + match pointer_event.kind { + PointerEventKind::Down => { + let mouse_event = MouseButtonEvent::new(MouseButtonAction::Down, button, point); + InputEvent::MouseButton(mouse_event) + } + PointerEventKind::Up => { + let mouse_event = MouseButtonEvent::new(MouseButtonAction::Up, button, point); + InputEvent::MouseButton(mouse_event) + } + _ => InputEvent::MouseMove(MouseMoveEvent::new(point)), + } + } + + fn get_mouse_button(point_event: &PointerEvent) -> MouseButton { + match point_event.button { + PointerEventButton::Left => MouseButton::Left, + PointerEventButton::Right => MouseButton::Right, + PointerEventButton::Middle => MouseButton::Middle, + _ => MouseButton::Left, + } + } +} diff --git a/examples/servo/ui/app.slint b/examples/servo/ui/app.slint index 345c81bb504..508bd90a460 100644 --- a/examples/servo/ui/app.slint +++ b/examples/servo/ui/app.slint @@ -16,10 +16,6 @@ export component MyApp inherits Window { preferred-width: 1224px; preferred-height: 768px; - callback back(); - callback forward(); - callback reload(); - VerticalBox { padding: 0px; spacing: 0px; @@ -32,21 +28,21 @@ export component MyApp inherits Window { Button { text: "Back"; clicked => { - back(); + webview.back(); } } Button { text: "Forward"; clicked => { - forward(); + webview.forward(); } } Button { text: "Reload"; clicked => { - reload(); + webview.reload(); } } diff --git a/examples/servo/ui/webview.slint b/examples/servo/ui/webview.slint index 8ac7ae6f842..2393cf0edeb 100644 --- a/examples/servo/ui/webview.slint +++ b/examples/servo/ui/webview.slint @@ -11,6 +11,10 @@ export global WebviewLogic { callback pointer(event: PointerEvent, x: physical-length, y: physical-length); callback scroll(initital_x: physical-length, initital_y: physical-length, delta_x: length, delta_y: length); + callback back(); + callback forward(); + callback reload(); + in property web_content; in-out property current_url; @@ -25,6 +29,18 @@ export component Webview { out property current_url: WebviewLogic.current_url; + public function back() { + WebviewLogic.back(); + } + + public function forward() { + WebviewLogic.forward(); + } + + public function reload() { + WebviewLogic.reload(); + } + public function loadUrl(url: string) { WebviewLogic.loadUrl(url); } From 6a9b412dc87be2d2be130d0c08bd9de12e3d013e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:11:21 +0000 Subject: [PATCH 2/8] [autofix.ci] apply automated fixes --- examples/servo/src/webview/mod.rs | 3 +++ examples/servo/src/webview/webview_events.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs index a2019710a8e..5f1537d1182 100644 --- a/examples/servo/src/webview/mod.rs +++ b/examples/servo/src/webview/mod.rs @@ -1,3 +1,6 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + mod webview; mod webview_events; diff --git a/examples/servo/src/webview/webview_events.rs b/examples/servo/src/webview/webview_events.rs index f4684f08020..eb934a71025 100644 --- a/examples/servo/src/webview/webview_events.rs +++ b/examples/servo/src/webview/webview_events.rs @@ -1,3 +1,6 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + use std::rc::Rc; use url::Url; From 048c6226cc01bca4b8b18585fd707a7fc870cda1 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Thu, 20 Nov 2025 09:52:48 +0100 Subject: [PATCH 3/8] refactor: Move webview-related modules into the `webview` directory and update all internal import paths. --- examples/servo/src/lib.rs | 4 -- examples/servo/src/{ => webview}/adapter.rs | 2 +- examples/servo/src/{ => webview}/delegate.rs | 3 +- examples/servo/src/webview/mod.rs | 5 ++ .../gpu_rendering_context.rs | 52 +++++++------------ .../rendering_context/metal/metal.rs | 7 +-- .../rendering_context/metal/mod.rs | 0 .../metal/texture_importer.rs | 0 .../{ => webview}/rendering_context/mod.rs | 0 .../servo_rendering_adapter.rs | 2 +- .../rendering_context/surfman_context.rs | 0 examples/servo/src/{ => webview}/waker.rs | 0 examples/servo/src/webview/webview.rs | 13 +++-- .../servo/{ui => src/webview}/webview.slint | 0 examples/servo/src/webview/webview_events.rs | 7 ++- examples/servo/ui/app.slint | 4 +- 16 files changed, 40 insertions(+), 59 deletions(-) rename examples/servo/src/{ => webview}/adapter.rs (97%) rename examples/servo/src/{ => webview}/delegate.rs (93%) rename examples/servo/src/{ => webview}/rendering_context/gpu_rendering_context.rs (90%) rename examples/servo/src/{ => webview}/rendering_context/metal/metal.rs (98%) rename examples/servo/src/{ => webview}/rendering_context/metal/mod.rs (100%) rename examples/servo/src/{ => webview}/rendering_context/metal/texture_importer.rs (100%) rename examples/servo/src/{ => webview}/rendering_context/mod.rs (100%) rename examples/servo/src/{ => webview}/rendering_context/servo_rendering_adapter.rs (98%) rename examples/servo/src/{ => webview}/rendering_context/surfman_context.rs (100%) rename examples/servo/src/{ => webview}/waker.rs (100%) rename examples/servo/{ui => src/webview}/webview.slint (100%) diff --git a/examples/servo/src/lib.rs b/examples/servo/src/lib.rs index 3cc63836c34..724e46d5c4d 100644 --- a/examples/servo/src/lib.rs +++ b/examples/servo/src/lib.rs @@ -1,10 +1,6 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT -mod adapter; -mod delegate; -mod rendering_context; -mod waker; mod webview; #[cfg(target_os = "linux")] diff --git a/examples/servo/src/adapter.rs b/examples/servo/src/webview/adapter.rs similarity index 97% rename from examples/servo/src/adapter.rs rename to examples/servo/src/webview/adapter.rs index 46d8d5c9d65..6bf8cc07540 100644 --- a/examples/servo/src/adapter.rs +++ b/examples/servo/src/webview/adapter.rs @@ -12,7 +12,7 @@ use slint::ComponentHandle; #[cfg(not(target_os = "android"))] use slint::wgpu_27::wgpu; -use crate::{MyApp, WebviewLogic, rendering_context::ServoRenderingAdapter}; +use crate::{MyApp, WebviewLogic, webview::rendering_context::ServoRenderingAdapter}; /// Upgrades a weak reference to SlintServoAdapter to a strong reference. /// Panics if the adapter has been dropped. diff --git a/examples/servo/src/delegate.rs b/examples/servo/src/webview/delegate.rs similarity index 93% rename from examples/servo/src/delegate.rs rename to examples/servo/src/webview/delegate.rs index e458da2a953..2df099d5b1b 100644 --- a/examples/servo/src/delegate.rs +++ b/examples/servo/src/webview/delegate.rs @@ -3,7 +3,8 @@ use std::rc::Rc; -use crate::{MyApp, adapter::SlintServoAdapter}; +use super::adapter::SlintServoAdapter; +use crate::MyApp; use servo::{WebView, WebViewDelegate}; pub struct AppDelegate { diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs index 5f1537d1182..adb05581933 100644 --- a/examples/servo/src/webview/mod.rs +++ b/examples/servo/src/webview/mod.rs @@ -1,7 +1,12 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT +mod adapter; +mod delegate; +mod rendering_context; +mod waker; mod webview; mod webview_events; +pub use waker::Waker; pub use webview::WebView; diff --git a/examples/servo/src/rendering_context/gpu_rendering_context.rs b/examples/servo/src/webview/rendering_context/gpu_rendering_context.rs similarity index 90% rename from examples/servo/src/rendering_context/gpu_rendering_context.rs rename to examples/servo/src/webview/rendering_context/gpu_rendering_context.rs index 86fa2198592..0fadefca2b1 100644 --- a/examples/servo/src/rendering_context/gpu_rendering_context.rs +++ b/examples/servo/src/webview/rendering_context/gpu_rendering_context.rs @@ -34,7 +34,7 @@ pub enum VulkanTextureError { OpenGL(String), } -use crate::rendering_context::surfman_context::SurfmanRenderingContext; +use super::surfman_context::SurfmanRenderingContext; pub struct GPURenderingContext { pub size: Cell>, @@ -69,11 +69,7 @@ impl GPURenderingContext { let swap_chain = surfman_rendering_info.create_attached_swap_chain()?; - Ok(Self { - swap_chain, - size: Cell::new(size), - surfman_rendering_info, - }) + Ok(Self { swap_chain, size: Cell::new(size), surfman_rendering_info }) } /// Imports Metal surface as a WGPU texture for rendering on macOS/iOS. @@ -84,7 +80,7 @@ impl GPURenderingContext { wgpu_device: &wgpu::Device, wgpu_queue: &wgpu::Queue, ) -> Result { - use crate::rendering_context::metal::WPGPUTextureFromMetal; + use super::metal::WPGPUTextureFromMetal; let device = &self.surfman_rendering_info.device.borrow(); let mut context = self.surfman_rendering_info.context.borrow_mut(); @@ -93,15 +89,15 @@ impl GPURenderingContext { let size = self.size.get(); - let wgpu_texture = WPGPUTextureFromMetal::new( - size, + let wgpu_texture = WPGPUTextureFromMetal::new(size, wgpu_device).get( wgpu_device, - ) - .get(wgpu_device, wgpu_queue, device, &surface); + wgpu_queue, + device, + &surface, + ); - let _ = device - .bind_surface_to_context(&mut context, surface) - .map_err(|(err, mut surface)| { + let _ = + device.bind_surface_to_context(&mut context, surface).map_err(|(err, mut surface)| { let _ = device.destroy_surface(&mut context, &mut surface); err }); @@ -129,9 +125,7 @@ impl GPURenderingContext { .map_err(VulkanTextureError::Surfman)? .ok_or(VulkanTextureError::NoSurface)?; - device - .make_context_current(&mut context) - .map_err(VulkanTextureError::Surfman)?; + device.make_context_current(&mut context).map_err(VulkanTextureError::Surfman)?; let surface_info = device.surface_info(&surface); @@ -153,11 +147,7 @@ impl GPURenderingContext { &vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) .format(vk::Format::R8G8B8A8_UNORM) - .extent(vk::Extent3D { - width: size.width, - height: size.height, - depth: 1, - }) + .extent(vk::Extent3D { width: size.width, height: size.height, depth: 1 }) .mip_levels(1) .array_layers(1) .samples(vk::SampleCountFlags::TYPE_1) @@ -239,12 +229,9 @@ impl GPURenderingContext { // Blit Servo's framebuffer to the imported texture - let draw_framebuffer = gl - .create_framebuffer() - .map_err(VulkanTextureError::OpenGL)?; - let read_framebuffer = surface_info - .framebuffer_object - .ok_or(VulkanTextureError::NoFramebuffer)?; + let draw_framebuffer = gl.create_framebuffer().map_err(VulkanTextureError::OpenGL)?; + let read_framebuffer = + surface_info.framebuffer_object.ok_or(VulkanTextureError::NoFramebuffer)?; // todo: tried using gl.named_framebuffer_texture instead but it errored. gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, Some(draw_framebuffer)); gl.framebuffer_texture_2d( @@ -320,9 +307,8 @@ impl GPURenderingContext { ) }; - let _ = device - .bind_surface_to_context(&mut context, surface) - .map_err(|(err, mut surface)| { + let _ = + device.bind_surface_to_context(&mut context, surface).map_err(|(err, mut surface)| { let _ = device.destroy_surface(&mut context, &mut surface); err }); @@ -360,9 +346,7 @@ impl RenderingContext for GPURenderingContext { fn present(&self) { let mut device = self.surfman_rendering_info.device.borrow_mut(); let mut context = self.surfman_rendering_info.context.borrow_mut(); - let _ = self - .swap_chain - .swap_buffers(&mut *device, &mut *context, PreserveBuffer::No); + let _ = self.swap_chain.swap_buffers(&mut *device, &mut *context, PreserveBuffer::No); } fn make_current(&self) -> std::result::Result<(), surfman::Error> { diff --git a/examples/servo/src/rendering_context/metal/metal.rs b/examples/servo/src/webview/rendering_context/metal/metal.rs similarity index 98% rename from examples/servo/src/rendering_context/metal/metal.rs rename to examples/servo/src/webview/rendering_context/metal/metal.rs index 23bee7124cd..176dec9648d 100644 --- a/examples/servo/src/rendering_context/metal/metal.rs +++ b/examples/servo/src/webview/rendering_context/metal/metal.rs @@ -15,7 +15,7 @@ use objc2_metal::{MTLPixelFormat, MTLTextureDescriptor, MTLTextureType, MTLTextu use foreign_types_shared::ForeignType; use winit::dpi::PhysicalSize; -use crate::rendering_context::metal::ServoTextureImporter; +use super::ServoTextureImporter; /// WGPU texture wrapper for Metal IOSurface textures. /// @@ -28,10 +28,7 @@ pub struct WPGPUTextureFromMetal { impl WPGPUTextureFromMetal { pub fn new(size: PhysicalSize, wgpu_device: &wgpu::Device) -> Self { - Self { - size, - texture_importer: ServoTextureImporter::new(wgpu_device), - } + Self { size, texture_importer: ServoTextureImporter::new(wgpu_device) } } pub fn get( diff --git a/examples/servo/src/rendering_context/metal/mod.rs b/examples/servo/src/webview/rendering_context/metal/mod.rs similarity index 100% rename from examples/servo/src/rendering_context/metal/mod.rs rename to examples/servo/src/webview/rendering_context/metal/mod.rs diff --git a/examples/servo/src/rendering_context/metal/texture_importer.rs b/examples/servo/src/webview/rendering_context/metal/texture_importer.rs similarity index 100% rename from examples/servo/src/rendering_context/metal/texture_importer.rs rename to examples/servo/src/webview/rendering_context/metal/texture_importer.rs diff --git a/examples/servo/src/rendering_context/mod.rs b/examples/servo/src/webview/rendering_context/mod.rs similarity index 100% rename from examples/servo/src/rendering_context/mod.rs rename to examples/servo/src/webview/rendering_context/mod.rs diff --git a/examples/servo/src/rendering_context/servo_rendering_adapter.rs b/examples/servo/src/webview/rendering_context/servo_rendering_adapter.rs similarity index 98% rename from examples/servo/src/rendering_context/servo_rendering_adapter.rs rename to examples/servo/src/webview/rendering_context/servo_rendering_adapter.rs index becde8be051..85ea60dcd29 100644 --- a/examples/servo/src/rendering_context/servo_rendering_adapter.rs +++ b/examples/servo/src/webview/rendering_context/servo_rendering_adapter.rs @@ -10,7 +10,7 @@ use winit::dpi::PhysicalSize; use servo::{RenderingContext, SoftwareRenderingContext, webrender_api::units::DeviceIntRect}; #[cfg(not(target_os = "android"))] -use {crate::rendering_context::GPURenderingContext, slint::wgpu_27::wgpu}; +use {super::GPURenderingContext, slint::wgpu_27::wgpu}; pub fn create_software_context(size: PhysicalSize) -> Box { let rendering_context = Rc::new( diff --git a/examples/servo/src/rendering_context/surfman_context.rs b/examples/servo/src/webview/rendering_context/surfman_context.rs similarity index 100% rename from examples/servo/src/rendering_context/surfman_context.rs rename to examples/servo/src/webview/rendering_context/surfman_context.rs diff --git a/examples/servo/src/waker.rs b/examples/servo/src/webview/waker.rs similarity index 100% rename from examples/servo/src/waker.rs rename to examples/servo/src/webview/waker.rs diff --git a/examples/servo/src/webview/webview.rs b/examples/servo/src/webview/webview.rs index 42c216d088b..ccb61f6b3fe 100644 --- a/examples/servo/src/webview/webview.rs +++ b/examples/servo/src/webview/webview.rs @@ -15,13 +15,12 @@ use slint::{ComponentHandle, SharedString}; use servo::{Servo, ServoBuilder, Theme, WebViewBuilder, webrender_api::units::DevicePixel}; use crate::{ - MyApp, Palette, WebviewLogic, - adapter::{SlintServoAdapter, upgrade_adapter}, - delegate::AppDelegate, - rendering_context::ServoRenderingAdapter, - waker::Waker, + MyApp, Palette, WebviewLogic, webview::Waker, webview::delegate::AppDelegate, + webview::rendering_context::ServoRenderingAdapter, }; +use super::adapter::{SlintServoAdapter, upgrade_adapter}; + pub struct WebView {} impl WebView { @@ -74,7 +73,7 @@ impl WebView { let physical_size = PhysicalSize::new(size.width as u32, size.height as u32); #[cfg(not(target_os = "android"))] - let rendering_adapter = crate::rendering_context::try_create_gpu_context( + let rendering_adapter = super::rendering_context::try_create_gpu_context( adapter.wgpu_device(), adapter.wgpu_queue(), physical_size, @@ -82,7 +81,7 @@ impl WebView { .unwrap(); #[cfg(target_os = "android")] - let rendering_adapter = crate::rendering_context::create_software_context(physical_size); + let rendering_adapter = super::rendering_context::create_software_context(physical_size); let rendering_adapter_rc = Rc::new(rendering_adapter); diff --git a/examples/servo/ui/webview.slint b/examples/servo/src/webview/webview.slint similarity index 100% rename from examples/servo/ui/webview.slint rename to examples/servo/src/webview/webview.slint diff --git a/examples/servo/src/webview/webview_events.rs b/examples/servo/src/webview/webview_events.rs index eb934a71025..d498fdb1e43 100644 --- a/examples/servo/src/webview/webview_events.rs +++ b/examples/servo/src/webview/webview_events.rs @@ -17,10 +17,9 @@ use servo::{ webrender_api::units::{DevicePixel, DevicePoint, DeviceVector2D}, }; -use crate::{ - MyApp, WebviewLogic, - adapter::{SlintServoAdapter, upgrade_adapter}, -}; +use crate::{MyApp, WebviewLogic}; + +use super::adapter::{SlintServoAdapter, upgrade_adapter}; pub struct WebViewEvents<'a> { app: &'a MyApp, diff --git a/examples/servo/ui/app.slint b/examples/servo/ui/app.slint index 508bd90a460..3e306c6847f 100644 --- a/examples/servo/ui/app.slint +++ b/examples/servo/ui/app.slint @@ -7,9 +7,9 @@ import { Button, LineEdit, } from "std-widgets.slint"; -import { Webview } from "webview.slint"; +import { Webview } from "../src/webview/webview.slint"; -export { WebviewLogic } from "webview.slint"; +export { WebviewLogic } from "../src/webview/webview.slint"; export { Palette } from "std-widgets.slint"; export component MyApp inherits Window { From 25537e9846307920d8fd3d96f344c78f5dce3cd8 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Thu, 20 Nov 2025 10:15:49 +0100 Subject: [PATCH 4/8] refactor: move servo field to inner struct and add direct accessor --- examples/servo/src/webview/adapter.rs | 10 +++++++--- examples/servo/src/webview/webview.rs | 4 +--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/servo/src/webview/adapter.rs b/examples/servo/src/webview/adapter.rs index 6bf8cc07540..3fd652770f7 100644 --- a/examples/servo/src/webview/adapter.rs +++ b/examples/servo/src/webview/adapter.rs @@ -27,11 +27,11 @@ pub struct SlintServoAdapter { waker_sender: Sender<()>, /// Channel receiver for event loop wake signals waker_receiver: Receiver<()>, - pub servo: RefCell>, inner: RefCell, } pub struct SlintServoAdapterInner { + servo: Option, webview: Option, rendering_adapter: Option>>, #[cfg(not(target_os = "android"))] @@ -50,8 +50,8 @@ impl SlintServoAdapter { Self { waker_sender, waker_receiver, - servo: RefCell::new(None), inner: RefCell::new(SlintServoAdapterInner { + servo: None, webview: None, rendering_adapter: None, #[cfg(not(target_os = "android"))] @@ -88,6 +88,10 @@ impl SlintServoAdapter { self.inner().queue.clone() } + pub fn servo(&self) -> Ref<'_, Servo> { + Ref::map(self.inner(), |inner| inner.servo.as_ref().expect("Servo not initialized yet")) + } + pub fn webview(&self) -> WebView { self.inner().webview.as_ref().expect("Webview not initialized yet").clone() } @@ -98,8 +102,8 @@ impl SlintServoAdapter { webview: WebView, rendering_adapter: Rc>, ) { - *self.servo.borrow_mut() = Some(servo); let mut inner = self.inner_mut(); + inner.servo = Some(servo); inner.webview = Some(webview); inner.rendering_adapter = Some(rendering_adapter); } diff --git a/examples/servo/src/webview/webview.rs b/examples/servo/src/webview/webview.rs index ccb61f6b3fe..e8d381fc31f 100644 --- a/examples/servo/src/webview/webview.rs +++ b/examples/servo/src/webview/webview.rs @@ -138,9 +138,7 @@ impl WebView { }; let _ = state.waker_reciver().recv().await; - if let Some(ref servo) = *state.servo.borrow() { - servo.spin_event_loop(); - } + state.servo().spin_event_loop(); } } }) From 92b9fa5e1a7f7f527520b237b161c4a712785197 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Thu, 20 Nov 2025 11:40:01 +0100 Subject: [PATCH 5/8] docs: Add docs for webview module --- examples/servo/src/webview/adapter.rs | 33 ++++++++- examples/servo/src/webview/delegate.rs | 24 +++++++ examples/servo/src/webview/mod.rs | 53 +++++++++++++++ examples/servo/src/webview/waker.rs | 15 +++++ examples/servo/src/webview/webview.rs | 92 ++++++++++++++++++++++++++ 5 files changed, 214 insertions(+), 3 deletions(-) diff --git a/examples/servo/src/webview/adapter.rs b/examples/servo/src/webview/adapter.rs index 3fd652770f7..f7a84f5d900 100644 --- a/examples/servo/src/webview/adapter.rs +++ b/examples/servo/src/webview/adapter.rs @@ -14,14 +14,37 @@ use slint::wgpu_27::wgpu; use crate::{MyApp, WebviewLogic, webview::rendering_context::ServoRenderingAdapter}; -/// Upgrades a weak reference to SlintServoAdapter to a strong reference. -/// Panics if the adapter has been dropped. +/// Upgrades a weak reference to `SlintServoAdapter` to a strong reference. +/// +/// # Arguments +/// +/// * `weak_ref` - Weak reference to upgrade +/// +/// # Panics +/// +/// Panics if the adapter has been dropped (weak reference cannot be upgraded). pub fn upgrade_adapter(weak_ref: &Weak) -> Rc { weak_ref.upgrade().expect("Failed to upgrade SlintServoAdapter") } /// Bridge between Slint UI and Servo browser engine. -/// Manages the lifecycle and communication between the UI and browser components. +/// +/// `SlintServoAdapter` manages the lifecycle and communication between the Slint UI +/// framework and the Servo browser engine. It holds references to both systems and +/// facilitates bidirectional data flow. +/// +/// # Responsibilities +/// +/// - **State Management**: Holds Servo and WebView instances +/// - **Event Communication**: Manages async channels for event loop waking +/// - **Rendering Coordination**: Bridges Servo's framebuffer to Slint's display +/// - **Resource Management**: Manages WGPU device and queue (non-Android) +/// +/// # Thread Safety +/// +/// This type uses `RefCell` for interior mutability and is designed to be used +/// within a single-threaded context (Slint's main thread). Access is coordinated +/// via `Rc` reference counting. pub struct SlintServoAdapter { /// Channel sender to wake the event loop waker_sender: Sender<()>, @@ -30,6 +53,10 @@ pub struct SlintServoAdapter { inner: RefCell, } +/// Internal state for `SlintServoAdapter`. +/// +/// Holds the WebView instance, rendering adapter, and platform-specific +/// GPU resources. Wrapped in `RefCell` for interior mutability. pub struct SlintServoAdapterInner { servo: Option, webview: Option, diff --git a/examples/servo/src/webview/delegate.rs b/examples/servo/src/webview/delegate.rs index 2df099d5b1b..5fa13a46a9f 100644 --- a/examples/servo/src/webview/delegate.rs +++ b/examples/servo/src/webview/delegate.rs @@ -7,12 +7,36 @@ use super::adapter::SlintServoAdapter; use crate::MyApp; use servo::{WebView, WebViewDelegate}; +/// Servo delegate for handling browser engine callbacks. +/// +/// `AppDelegate` implements Servo's `WebViewDelegate` trait to receive notifications +/// about rendering events. It acts as a bridge, forwarding Servo's frame updates to +/// the Slint UI for display. +/// +/// # Responsibilities +/// +/// - Receives frame-ready notifications from Servo +/// - Triggers frame painting in Servo +/// - Updates the Slint UI with the latest rendered content +/// +/// # Lifecycle +/// +/// The delegate holds a weak reference to the Slint app to avoid circular references. +/// If the app is dropped, frame updates are silently ignored. pub struct AppDelegate { + /// Weak reference to the Slint application pub app: slint::Weak, + /// Reference to the Slint-Servo adapter for state access pub adapter: Rc, } impl AppDelegate { + /// Creates a new delegate instance. + /// + /// # Arguments + /// + /// * `app` - Weak reference to the Slint application + /// * `adapter` - Reference to the Slint-Servo adapter pub fn new(app: slint::Weak, adapter: Rc) -> Self { Self { app, adapter } } diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs index adb05581933..e897aa9cd66 100644 --- a/examples/servo/src/webview/mod.rs +++ b/examples/servo/src/webview/mod.rs @@ -1,6 +1,59 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT +//! WebView integration module for embedding Servo browser engine in Slint applications. +//! +//! This module provides a reusable `WebView` component that integrates the Servo browser engine +//! with Slint UI framework. It handles the complex bridging between Servo's rendering pipeline +//! and Slint's display system. +//! +//! # Architecture +//! +//! The module is organized into several key components: +//! +//! - **`WebView`**: Main public API for creating and managing a web browser instance +//! - **`adapter`**: Bridge between Slint UI and Servo engine, managing state and communication +//! - **`delegate`**: Servo callback handler for frame updates and rendering notifications +//! - **`rendering_context`**: Platform-specific rendering backends (GPU/software) +//! - **`waker`**: Event loop integration for async Servo operations +//! - **`webview_events`**: UI event handlers for user interactions (clicks, scrolls, etc.) +//! +//! # Example +//! +//! ```rust,no_run +//! use slint::ComponentHandle; +//! use crate::webview::WebView; +//! +//! // Create Slint application +//! let app = MyApp::new().unwrap(); +//! +//! // Initialize WGPU for GPU rendering (non-Android platforms) +//! # #[cfg(not(target_os = "android"))] +//! let (device, queue) = setup_wgpu(); +//! +//! // Create WebView instance +//! # #[cfg(not(target_os = "android"))] +//! WebView::new( +//! app.clone_strong(), +//! "https://example.com".into(), +//! device, +//! queue, +//! ); +//! +//! // Run the application +//! app.run().unwrap(); +//! ``` +//! +//! # Platform Support +//! +//! - **Desktop (Linux, macOS)**: GPU-accelerated rendering via WGPU +//! - **Android**: Software rendering fallback +//! +//! # Threading Model +//! +//! The WebView runs Servo's event loop asynchronously using `slint::spawn_local()`. +//! All UI interactions are marshaled through async channels to maintain thread safety. + mod adapter; mod delegate; mod rendering_context; diff --git a/examples/servo/src/webview/waker.rs b/examples/servo/src/webview/waker.rs index 3ba204af560..14c219f8a81 100644 --- a/examples/servo/src/webview/waker.rs +++ b/examples/servo/src/webview/waker.rs @@ -4,10 +4,25 @@ use servo::EventLoopWaker; use smol::channel::Sender; +/// Event loop waker for integrating Servo's async operations with Slint. +/// +/// The `Waker` implements Servo's `EventLoopWaker` trait to signal when the +/// Servo event loop needs to process pending events. It uses an async channel +/// to communicate between Servo's rendering thread and Slint's event loop. +/// +/// # Thread Safety +/// +/// This type is `Clone` and can be safely shared across threads via the +/// underlying channel sender. #[derive(Clone)] pub struct Waker(Sender<()>); impl Waker { + /// Creates a new waker with the given channel sender. + /// + /// # Arguments + /// + /// * `sender` - Channel sender for signaling the event loop pub fn new(sender: Sender<()>) -> Self { Self(sender) } diff --git a/examples/servo/src/webview/webview.rs b/examples/servo/src/webview/webview.rs index e8d381fc31f..e78ef27ffba 100644 --- a/examples/servo/src/webview/webview.rs +++ b/examples/servo/src/webview/webview.rs @@ -21,9 +21,50 @@ use crate::{ use super::adapter::{SlintServoAdapter, upgrade_adapter}; +/// A web browser component powered by the Servo engine. +/// +/// `WebView` provides a high-level interface for embedding a full-featured web browser +/// into Slint applications. It handles the initialization and lifecycle management of +/// the Servo browser engine, rendering pipeline, and event handling. +/// +/// # Architecture +/// +/// The WebView orchestrates several subsystems: +/// - **Rendering**: Platform-specific GPU or software rendering +/// - **Event Loop**: Async Servo event processing +/// - **UI Integration**: Bidirectional communication with Slint +/// - **Input Handling**: Mouse, touch, and keyboard events +/// +/// # Platform Differences +/// +/// - **Non-Android platforms**: Uses GPU-accelerated rendering via WGPU +/// - **Android**: Falls back to software rendering +/// ``` pub struct WebView {} impl WebView { + /// Creates and initializes a new WebView instance. + /// + /// This method sets up the complete web browser infrastructure including: + /// - Servo browser engine initialization + /// - Rendering context (GPU or software) + /// - Event loop for async operations + /// - UI event handlers for user interactions + /// + /// # Arguments + /// + /// * `app` - The Slint application instance to integrate with + /// * `initial_url` - The URL to load when the browser starts + /// * `device` - WGPU device for GPU rendering (non-Android only) + /// * `queue` - WGPU command queue for GPU operations (non-Android only) + /// + /// # Panics + /// + /// Panics if: + /// - The initial URL cannot be parsed + /// - GPU rendering context creation fails (on non-Android platforms) + /// - Servo event loop task cannot be spawned + /// ``` pub fn new( app: MyApp, initial_url: SharedString, @@ -62,6 +103,16 @@ impl WebView { super::webview_events::WebViewEvents::new(&app, adapter.clone()); } + /// Initializes the rendering adapter based on platform capabilities. + /// + /// Creates either a GPU-accelerated or software rendering context depending on + /// the platform and availability. The viewport size is extracted from the Slint UI. + /// + /// # Returns + /// + /// A tuple containing: + /// - The rendering adapter (GPU or software) + /// - The physical size of the viewport in pixels fn init_rendering_adapter( app: &MyApp, adapter: Rc, @@ -88,6 +139,19 @@ impl WebView { (rendering_adapter_rc, physical_size) } + /// Initializes and builds the Servo browser engine instance. + /// + /// Configures Servo with the rendering context and event loop waker for + /// async operation integration. + /// + /// # Arguments + /// + /// * `adapter` - The Slint-Servo adapter for state management + /// * `rendering_adapter` - The rendering backend to use + /// + /// # Returns + /// + /// A configured Servo instance ready for use fn init_servo_builder( adapter: Rc, rendering_adapter: Rc>, @@ -99,6 +163,22 @@ impl WebView { ServoBuilder::new(rendering_context).event_loop_waker(event_loop_waker).build() } + /// Initializes the Servo WebView with the initial URL and configuration. + /// + /// Sets up the WebView with: + /// - Initial URL to load + /// - Viewport size + /// - Delegate for frame update callbacks + /// - Theme (light/dark mode) based on Slint settings + /// + /// # Arguments + /// + /// * `app` - The Slint application instance + /// * `physical_size` - Initial viewport dimensions + /// * `initial_url` - URL to navigate to on startup + /// * `adapter` - The Slint-Servo adapter + /// * `servo` - The Servo engine instance + /// * `rendering_adapter` - The rendering backend fn init_webview( app: &MyApp, physical_size: PhysicalSize, @@ -126,6 +206,18 @@ impl WebView { adapter.set_inner(servo, webview, rendering_adapter); } + /// Spawns the async event loop for Servo operations. + /// + /// Creates a background task that continuously processes Servo events. + /// The loop runs until the adapter is dropped (weak reference becomes invalid). + /// + /// # Arguments + /// + /// * `state` - The Slint-Servo adapter containing the event channel + /// + /// # Panics + /// + /// Panics if the async task cannot be spawned fn spin_servo_event_loop(state: Rc) { let state_weak = Rc::downgrade(&state); From 53f3c0095afbf59fa831e094c31f7819f3a0b896 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Thu, 20 Nov 2025 12:11:29 +0100 Subject: [PATCH 6/8] docs: Change webview module to public and improve example in docs --- examples/servo/src/lib.rs | 2 +- examples/servo/src/webview/mod.rs | 51 +++++++++++++++++++++++---- examples/servo/src/webview/webview.rs | 2 -- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/examples/servo/src/lib.rs b/examples/servo/src/lib.rs index 724e46d5c4d..cbdf737ff5a 100644 --- a/examples/servo/src/lib.rs +++ b/examples/servo/src/lib.rs @@ -1,7 +1,7 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT -mod webview; +pub mod webview; #[cfg(target_os = "linux")] mod gl_bindings { diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs index e897aa9cd66..59cf53c39df 100644 --- a/examples/servo/src/webview/mod.rs +++ b/examples/servo/src/webview/mod.rs @@ -17,6 +17,16 @@ //! - **`rendering_context`**: Platform-specific rendering backends (GPU/software) //! - **`waker`**: Event loop integration for async Servo operations //! - **`webview_events`**: UI event handlers for user interactions (clicks, scrolls, etc.) +//! +//! # Platform Support +//! +//! - **Desktop (Linux, macOS)**: GPU-accelerated rendering via WGPU +//! - **Android**: Software rendering fallback +//! +//! # Threading Model +//! +//! The WebView runs Servo's event loop asynchronously using `slint::spawn_local()`. +//! All UI interactions are marshaled through async channels to maintain thread safety. //! //! # Example //! @@ -24,6 +34,7 @@ //! use slint::ComponentHandle; //! use crate::webview::WebView; //! +//! pub fn main() { //! // Create Slint application //! let app = MyApp::new().unwrap(); //! @@ -42,17 +53,43 @@ //! //! // Run the application //! app.run().unwrap(); -//! ``` +//! } //! -//! # Platform Support +//! #[cfg(not(target_os = "android"))] +//! fn setup_wgpu() -> (wgpu::Device, wgpu::Queue) { +//! let backends = wgpu::Backends::from_env().unwrap_or_default(); + +//! let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { +//! backends, +//! flags: Default::default(), +//! backend_options: Default::default(), +//! memory_budget_thresholds: Default::default(), +//! }); //! -//! - **Desktop (Linux, macOS)**: GPU-accelerated rendering via WGPU -//! - **Android**: Software rendering fallback +//! let adapter = spin_on::spin_on(async { +//! instance +//! .request_adapter(&Default::default()) +//! .await +//! .unwrap() +//! }); //! -//! # Threading Model +//! let (device, queue) = spin_on::spin_on(async { +//! adapter.request_device(&Default::default()).await.unwrap() +//! }); //! -//! The WebView runs Servo's event loop asynchronously using `slint::spawn_local()`. -//! All UI interactions are marshaled through async channels to maintain thread safety. +//! slint::BackendSelector::new() +//! .require_wgpu_27(slint::wgpu_27::WGPUConfiguration::Manual { +//! instance, +//! adapter, +//! device: device.clone(), +//! queue: queue.clone() +//! }) +//! .select() +//! .unwrap(); +//! +//! (device, queue) +//! } +//! ``` mod adapter; mod delegate; diff --git a/examples/servo/src/webview/webview.rs b/examples/servo/src/webview/webview.rs index e78ef27ffba..81ed158445b 100644 --- a/examples/servo/src/webview/webview.rs +++ b/examples/servo/src/webview/webview.rs @@ -39,7 +39,6 @@ use super::adapter::{SlintServoAdapter, upgrade_adapter}; /// /// - **Non-Android platforms**: Uses GPU-accelerated rendering via WGPU /// - **Android**: Falls back to software rendering -/// ``` pub struct WebView {} impl WebView { @@ -64,7 +63,6 @@ impl WebView { /// - The initial URL cannot be parsed /// - GPU rendering context creation fails (on non-Android platforms) /// - Servo event loop task cannot be spawned - /// ``` pub fn new( app: MyApp, initial_url: SharedString, From 54ffa923223507c1de9ebc345304b4b1536651c0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:22:35 +0000 Subject: [PATCH 7/8] [autofix.ci] apply automated fixes --- examples/servo/src/webview/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs index 59cf53c39df..6fd725ecfdc 100644 --- a/examples/servo/src/webview/mod.rs +++ b/examples/servo/src/webview/mod.rs @@ -17,7 +17,7 @@ //! - **`rendering_context`**: Platform-specific rendering backends (GPU/software) //! - **`waker`**: Event loop integration for async Servo operations //! - **`webview_events`**: UI event handlers for user interactions (clicks, scrolls, etc.) -//! +//! //! # Platform Support //! //! - **Desktop (Linux, macOS)**: GPU-accelerated rendering via WGPU From 11f384482d16b75e38c172af30f6d3cdde0c0998 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Fri, 21 Nov 2025 11:43:43 +0100 Subject: [PATCH 8/8] docs: Improve example in docs --- examples/servo/src/webview/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/servo/src/webview/mod.rs b/examples/servo/src/webview/mod.rs index 6fd725ecfdc..c81e82c10e3 100644 --- a/examples/servo/src/webview/mod.rs +++ b/examples/servo/src/webview/mod.rs @@ -39,15 +39,17 @@ //! let app = MyApp::new().unwrap(); //! //! // Initialize WGPU for GPU rendering (non-Android platforms) -//! # #[cfg(not(target_os = "android"))] +//! #[cfg(not(target_os = "android"))] //! let (device, queue) = setup_wgpu(); //! //! // Create WebView instance -//! # #[cfg(not(target_os = "android"))] +//! #[cfg(not(target_os = "android"))] //! WebView::new( //! app.clone_strong(), //! "https://example.com".into(), +//! #[cfg(not(target_os = "android"))] //! device, +//! #[cfg(not(target_os = "android"))] //! queue, //! ); //!