mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-26 17:03:09 +00:00
feat(servo-shell): add servo-embed feature gate and embedder contract
Adds src/embedder.rs with the full Servo embedding implementation behind #[cfg(feature = " servo-embed)]:
This commit is contained in:
parent
6b428e5a47
commit
2a9f034815
4 changed files with 330 additions and 6 deletions
|
|
@ -8,6 +8,12 @@ rust-version.workspace = true
|
|||
name = "weft-servo-shell"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
# Enable actual Servo rendering. Requires manually adding the deps listed in
|
||||
# SERVO_PIN.md to this file before building; they are not included here to
|
||||
# avoid pulling the Servo monorepo (~1 GB) into every `cargo check` cycle.
|
||||
servo-embed = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
tracing = "0.1"
|
||||
|
|
|
|||
81
crates/weft-servo-shell/SERVO_PIN.md
Normal file
81
crates/weft-servo-shell/SERVO_PIN.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Servo Pin
|
||||
|
||||
## Current pin
|
||||
|
||||
| Field | Value |
|
||||
|---------|--------------------------------------------------------------------------|
|
||||
| Source | <https://github.com/servo/servo> |
|
||||
| Rev | `c242860f0ef4e7c6e60dfea29310167898e6eb38` (main, 2026-03-11) |
|
||||
| Crate | `servo` (package name as of 2026-03-11; previously `libservo`) |
|
||||
| Feature | `servo-embed` (optional; off by default) |
|
||||
|
||||
## Adding the Cargo dependencies
|
||||
|
||||
The Servo deps are **not** in `Cargo.toml` by default to avoid pulling the
|
||||
Servo monorepo (~1 GB) into every `cargo check` cycle. To activate, add the
|
||||
following to `crates/weft-servo-shell/Cargo.toml` and change the `servo-embed`
|
||||
feature line to declare `dep:servo`, `dep:winit`, and `dep:softbuffer`:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
servo-embed = ["dep:servo", "dep:winit", "dep:softbuffer"]
|
||||
|
||||
[dependencies.servo]
|
||||
git = "https://github.com/servo/servo"
|
||||
rev = "c242860f0ef4e7c6e60dfea29310167898e6eb38"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.winit]
|
||||
version = "0.30"
|
||||
optional = true
|
||||
features = ["wayland"]
|
||||
|
||||
[dependencies.softbuffer]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
```
|
||||
|
||||
Then build:
|
||||
|
||||
```sh
|
||||
cargo build -p weft-servo-shell --features servo-embed
|
||||
```
|
||||
|
||||
The first build downloads and compiles Servo and its dependencies, which takes
|
||||
30–60 minutes cold. Subsequent incremental builds are faster.
|
||||
|
||||
## System dependencies
|
||||
|
||||
The following system packages are required when `servo-embed` is enabled:
|
||||
|
||||
- `libgles2-mesa-dev` or equivalent OpenGL ES headers
|
||||
- `libssl-dev`
|
||||
- `libdbus-1-dev`
|
||||
- `libudev-dev`
|
||||
- `libxkbcommon-dev`
|
||||
- `libwayland-dev`
|
||||
|
||||
On Fedora/RHEL: `mesa-libGL-devel openssl-devel dbus-devel systemd-devel libxkbcommon-devel wayland-devel`
|
||||
|
||||
## Rendering approach
|
||||
|
||||
Initial bringup uses `SoftwareRenderingContext` (CPU rasterisation) blitted to a
|
||||
`softbuffer`-backed winit window. Production rendering will move to an EGL/GL
|
||||
context once the Wayland surface pipeline is stable.
|
||||
|
||||
## Known gaps at this pin
|
||||
|
||||
- **GAP-1**: Wayland input events not forwarded to Servo (keyboard/pointer stubs)
|
||||
- **GAP-2**: DMA-BUF surface export not implemented (software blit only)
|
||||
- **GAP-3**: WebGPU adapter on Mesa may fail CTS
|
||||
- **GAP-4**: CSS `backdrop-filter` and CSS Grid have partial coverage
|
||||
|
||||
## Update policy
|
||||
|
||||
Pin is reviewed monthly. To update:
|
||||
|
||||
1. Check the [Servo release page](https://github.com/servo/servo/releases) for new tags.
|
||||
2. Update `tag` in `Cargo.toml` and run `cargo update -p servo`.
|
||||
3. Confirm the compositor and shell tests still pass.
|
||||
4. Update this file with the new tag and any new or resolved gaps.
|
||||
227
crates/weft-servo-shell/src/embedder.rs
Normal file
227
crates/weft-servo-shell/src/embedder.rs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#![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<Mutex<EventLoopProxy<ServoWake>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ServoWake;
|
||||
|
||||
impl EventLoopWaker for WeftEventLoopWaker {
|
||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||||
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<std::sync::atomic::AtomicBool>,
|
||||
}
|
||||
|
||||
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<Arc<Window>>,
|
||||
servo: Option<servo::Servo>,
|
||||
webview: Option<servo::WebView>,
|
||||
redraw_requested: Arc<std::sync::atomic::AtomicBool>,
|
||||
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<Window>, 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<ServoWake> 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::<ServoWake>::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}"))
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::Context;
|
||||
|
||||
#[cfg(feature = "servo-embed")]
|
||||
mod embedder;
|
||||
mod protocols;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
|
|
@ -66,11 +68,19 @@ fn appd_ws_port() -> u16 {
|
|||
|
||||
fn embed_servo(
|
||||
_wayland_display: &str,
|
||||
_html_path: &std::path::Path,
|
||||
_ws_port: u16,
|
||||
html_path: &std::path::Path,
|
||||
ws_port: u16,
|
||||
) -> anyhow::Result<()> {
|
||||
anyhow::bail!(
|
||||
"Servo embedding not yet implemented; \
|
||||
see docs/architecture/winit-wayland-audit.md for gap assessment"
|
||||
)
|
||||
#[cfg(feature = "servo-embed")]
|
||||
return embedder::run(html_path, ws_port);
|
||||
|
||||
#[cfg(not(feature = "servo-embed"))]
|
||||
{
|
||||
tracing::warn!(
|
||||
path = %html_path.display(),
|
||||
ws_port,
|
||||
"servo-embed feature not enabled; build with --features servo-embed to activate"
|
||||
);
|
||||
anyhow::bail!("servo-embed feature required")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue