mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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>
This commit is contained in:
parent
8740c03682
commit
2db828f0c7
7 changed files with 287 additions and 6 deletions
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
|
@ -173,9 +173,10 @@ jobs:
|
|||
timeout_minutes: 20
|
||||
max_attempts: 2 # https://github.com/servo/servo/issues/30683
|
||||
command: ./mach test-unit --${{ inputs.profile }}
|
||||
- name: Build libservo
|
||||
- name: Build libservo with examples
|
||||
if: ${{ inputs.build-libservo }}
|
||||
run: cargo build -p libservo
|
||||
continue-on-error: true
|
||||
run: cargo build -p libservo --all-targets
|
||||
- name: Archive build timing
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
5
.github/workflows/mac.yml
vendored
5
.github/workflows/mac.yml
vendored
|
@ -160,9 +160,10 @@ jobs:
|
|||
timeout_minutes: 40 # https://github.com/servo/servo/issues/30275
|
||||
max_attempts: 3 # https://github.com/servo/servo/issues/30683
|
||||
command: ./mach test-unit --${{ inputs.profile }}
|
||||
- name: Build libservo
|
||||
- name: Build libservo with examples
|
||||
if: ${{ inputs.build-libservo }}
|
||||
run: cargo build -p libservo
|
||||
continue-on-error: true
|
||||
run: cargo build -p libservo --all-targets
|
||||
- name: Build mach package
|
||||
run: ./mach package --${{ inputs.profile }}
|
||||
- name: Run DMG smoketest
|
||||
|
|
5
.github/workflows/windows.yml
vendored
5
.github/workflows/windows.yml
vendored
|
@ -176,9 +176,10 @@ jobs:
|
|||
timeout_minutes: 30
|
||||
max_attempts: 3 # https://github.com/servo/servo/issues/30683
|
||||
command: .\mach test-unit --${{ inputs.profile }} -- -- --test-threads=1
|
||||
- name: Build libservo
|
||||
- name: Build libservo with examples
|
||||
if: ${{ inputs.build-libservo }}
|
||||
run: cargo build -p libservo
|
||||
continue-on-error: true
|
||||
run: cargo build -p libservo --all-targets
|
||||
- name: Archive build timing
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4210,6 +4210,7 @@ dependencies = [
|
|||
"keyboard-types",
|
||||
"layout_thread_2013",
|
||||
"layout_thread_2020",
|
||||
"libservo",
|
||||
"log",
|
||||
"media",
|
||||
"mozangle",
|
||||
|
@ -4218,6 +4219,7 @@ dependencies = [
|
|||
"profile",
|
||||
"profile_traits",
|
||||
"rayon",
|
||||
"rustls",
|
||||
"script",
|
||||
"script_layout_interface",
|
||||
"script_traits",
|
||||
|
@ -4238,6 +4240,7 @@ dependencies = [
|
|||
"webrender_traits",
|
||||
"webxr",
|
||||
"webxr-api",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -105,3 +105,9 @@ surfman = { workspace = true, features = ["sm-x11", "sm-raw-window-handle-06"] }
|
|||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "ios"), not(target_os = "android"), not(target_env = "ohos"), not(target_arch = "arm"), not(target_arch = "aarch64")))'.dependencies]
|
||||
gaol = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
libservo = { path = ".", features = ["tracing"] }
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring"] }
|
||||
tracing = { workspace = true }
|
||||
winit = "0.30.8"
|
||||
|
|
265
components/servo/examples/winit_minimal.rs
Normal file
265
components/servo/examples/winit_minimal.rs
Normal file
|
@ -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)
|
||||
}
|
|
@ -134,6 +134,10 @@ stdenv.mkDerivation (androidEnvironment // {
|
|||
# [WARN script::dom::gpu] Could not get GPUAdapter ("NotFound")
|
||||
# TLA Err: Error: Couldn't request WebGPU adapter.
|
||||
vulkan-loader
|
||||
|
||||
# $ cargo run -p libservo --example winit_minimal
|
||||
# Unable to load the libEGL shared object
|
||||
libGL
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue