Move Servo IPC off UI thread

Root cause of the post-tab lag: the GPUI 16 ms timer was calling
`WebSurfaceRuntime::ensure_tab` and `tick` on the UI thread, and each
call did a synchronous `serde_json` write plus `read_line` against the
Servo sidecar over stdin/stdout. With even one visible tab, every
frame stalled on cross-process IPC.

Introduce `web_surface_worker.rs` — a per-profile worker thread that
owns the `ServoLiveClient`, drains a coalescing request queue
(latest Ensure/Poll per tab wins, no unbounded growth), and ships
results back through a `std::sync::mpsc` channel. `WebSurfaceRuntime`
now submits work non-blockingly and drains responses in `tick`; the
UI thread never blocks on the sidecar.

Adjacent in-flight cleanup riding along: hardware IOSurface
rendering-context completion (sidecar `live_protocol`,
`hardware_rendering_context`, GPUI BGRA surface shader), CSS viewport
size + device pixel ratio plumbing into `ServoLiveFrame`, and the
Send opt-ins for `CVPixelBuffer`-bearing types so frames can cross
the thread boundary.
This commit is contained in:
2026-05-15 16:41:40 -04:00
parent f4c650c4d8
commit 90c029eddb
29 changed files with 2113 additions and 496 deletions
@@ -2,8 +2,7 @@ use std::{
collections::{HashMap, HashSet},
fs,
io::{self, BufRead},
thread,
time::{Duration, Instant},
time::Instant,
};
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId, UrlText};
@@ -11,7 +10,7 @@ use ely_servo_host::{
IOSurfaceIdentity, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
RenderingContextKind, ResizeRequest, ScrollRequest, ServoHost, ServoSurfaceSize,
SoftwareServoHost,
SoftwareServoHost, WebViewState,
};
use super::args::LiveArgs;
@@ -24,12 +23,6 @@ use super::live_protocol::{
};
use super::perf::{FramePerfAggregator, FramePerfSummary, elapsed_ns};
/// Per-`Ensure` budget for Servo to paint after input dispatch.
/// 250 ms catches the common click + paint round trip within the
/// same `Ensure` instead of waiting for the next 16 ms `Poll`.
const LIVE_FRAME_WAIT_TIMEOUT: Duration = Duration::from_millis(250);
const LIVE_FRAME_WAIT_INTERVAL: Duration = Duration::from_millis(2);
pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
let LiveArgs { profile_data_dir, iosurface_mach_service, rendering_context_kind } = args;
fs::create_dir_all(&profile_data_dir)?;
@@ -64,7 +57,7 @@ pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
// matching stop is the `stdout.flush()` inside
// `write_outcome`.
let frame_started_at = Instant::now();
let mut outcome = match serde_json::from_str::<LiveRequest>(&line) {
let outcome = match serde_json::from_str::<LiveRequest>(&line) {
Ok(request) => handle_request(
&mut host,
&mut sessions,
@@ -75,7 +68,11 @@ pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
Err(error) => Err(LiveSidecarError::Json(error)),
};
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
send_surface_port_if_needed(iosurface_mach_sender.as_mut(), &mut outcome);
let outcome = {
let mut outcome = outcome;
send_surface_port_if_needed(iosurface_mach_sender.as_mut(), &mut outcome);
outcome
};
write_outcome(&mut stdout, &mut perf, &mut pending_summary, outcome, frame_started_at)?;
}
@@ -153,12 +150,11 @@ fn handle_request(
typed_text,
};
if apply_input(host, session, input)? {
// Tell poll_frame to actually wait for Servo to paint
// a response to this input. The visible-content gate
// is bypassed on the hardware path inside poll_frame,
// so we return on the first `has_pending_frame=true`
// (~3 ms in practice) rather than burning the full
// LIVE_FRAME_WAIT_TIMEOUT.
// The app tick calls this sidecar synchronously from
// GPUI's update path. Mark that a fresh frame is
// desired, then let poll_frame take one event-loop
// step; a later 16 ms app tick will poll again if
// Servo has not painted yet.
session.awaiting_visible_frame = true;
}
let webview_id = session.webview_id.clone();
@@ -363,33 +359,24 @@ fn poll_frame(
session: &mut LiveSession,
rendering_context_kind: RenderingContextKind,
) -> Result<LiveOutcome, LiveSidecarError> {
let started_at = Instant::now();
loop {
host.tick();
let snapshot = host.snapshot(&session.webview_id)?;
if snapshot.has_pending_frame() {
let (outcome, has_visible_content) =
paint_pending_frame(host, session, rendering_context_kind)?;
if has_visible_content {
session.awaiting_visible_frame = false;
session.ever_visible_frame = true;
return Ok(outcome);
}
if !session.awaiting_visible_frame {
return Ok(outcome);
}
}
if !session.awaiting_visible_frame {
return Ok(LiveOutcome::empty());
}
if started_at.elapsed() >= LIVE_FRAME_WAIT_TIMEOUT {
return Ok(LiveOutcome::empty());
}
thread::sleep(LIVE_FRAME_WAIT_INTERVAL);
host.tick();
let snapshot = host.snapshot(&session.webview_id)?;
if !snapshot.has_pending_frame() {
return Ok(LiveOutcome::empty());
}
let (outcome, has_visible_content) =
paint_pending_frame(host, session, rendering_context_kind)?;
if has_visible_content {
session.awaiting_visible_frame = false;
session.ever_visible_frame = true;
return Ok(outcome);
}
if !session.awaiting_visible_frame {
return Ok(outcome);
}
Ok(LiveOutcome::empty())
}
fn paint_pending_frame(
@@ -418,7 +405,7 @@ fn paint_readback_frame(
let encode_started_at = Instant::now();
let has_visible_content = session.ever_visible_frame
|| (frame.non_white_pixel_count() > 0 && frame.content_pixel_count() > 0);
let report = LiveFrameReport::new(&snapshot, &frame);
let report = LiveFrameReport::new(&snapshot, &frame, session.device_pixel_ratio());
let encode_ns = elapsed_ns(encode_started_at);
let timings = PartialFrameTimings { paint_ns, encode_ns };
Ok((LiveOutcome::from_frame(report, frame, timings), has_visible_content))
@@ -429,17 +416,51 @@ fn paint_hardware_surface_frame(
host: &mut SoftwareServoHost,
session: &LiveSession,
) -> Result<(LiveOutcome, bool), LiveSidecarError> {
if !session.ever_visible_frame {
return paint_initial_hardware_surface_frame(host, session);
}
let paint_started_at = Instant::now();
host.paint_without_readback(&session.webview_id)?;
let snapshot = host.snapshot(&session.webview_id)?;
let paint_ns = elapsed_ns(paint_started_at);
let encode_started_at = Instant::now();
let report = LiveFrameReport::new_hardware_surface(&snapshot, session.width, session.height);
let report = LiveFrameReport::new_hardware_surface(
&snapshot,
session.width,
session.height,
session.device_pixel_ratio(),
);
let encode_ns = elapsed_ns(encode_started_at);
let timings = PartialFrameTimings { paint_ns, encode_ns };
Ok((LiveOutcome::from_report(report, timings), true))
}
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
fn paint_initial_hardware_surface_frame(
host: &mut SoftwareServoHost,
session: &LiveSession,
) -> Result<(LiveOutcome, bool), LiveSidecarError> {
let paint_started_at = Instant::now();
host.paint(&session.webview_id)?;
let snapshot = host.snapshot(&session.webview_id)?;
let frame = host.last_rendered_frame()?;
let paint_ns = elapsed_ns(paint_started_at);
let encode_started_at = Instant::now();
let report = LiveFrameReport::new(&snapshot, &frame, session.device_pixel_ratio());
let has_visible_content = frame.non_white_pixel_count() > 0
&& frame.content_pixel_count() > 0
&& hardware_snapshot_has_visible_document(&snapshot);
let encode_ns = elapsed_ns(encode_started_at);
let timings = PartialFrameTimings { paint_ns, encode_ns };
Ok((LiveOutcome::from_frame(report, frame, timings), has_visible_content))
}
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
fn hardware_snapshot_has_visible_document(snapshot: &ely_servo_host::WebViewSnapshot) -> bool {
snapshot.title().is_some() || matches!(snapshot.state(), WebViewState::Complete)
}
#[derive(Clone)]
struct LiveSession {
webview_id: ely_domain::WebViewId,
@@ -468,12 +489,12 @@ struct LiveSession {
}
impl LiveSession {
fn new(webview_id: ely_domain::WebViewId, width: u32, height: u32) -> Self {
fn new(webview_id: ely_domain::WebViewId, _width: u32, _height: u32) -> Self {
Self {
webview_id,
requested_url: String::new(),
width: width.max(1),
height: height.max(1),
width: 0,
height: 0,
page_zoom_percent: DEFAULT_ZOOM_PERCENT,
hidpi_scale_milli: 0,
scroll_x: 0,
@@ -482,9 +503,32 @@ impl LiveSession {
ever_visible_frame: false,
}
}
fn device_pixel_ratio(&self) -> f32 {
hidpi_scale_milli_to_f32(self.hidpi_scale_milli)
}
}
fn positive_scroll_component(current: i32, delta: i32) -> i32 {
let value = i64::from(current) + i64::from(delta);
value.clamp(0, i64::from(i32::MAX)) as i32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_live_session_forces_first_resize_after_hidpi() {
let session = LiveSession::new(ely_domain::WebViewId::new(), 1280, 720);
assert_ne!(
session.width, 1280,
"first apply_layout must resize after hidpi has been pushed",
);
assert_ne!(
session.height, 720,
"first apply_layout must resize after hidpi has been pushed",
);
}
}
@@ -4,9 +4,9 @@ use std::{
time::{Duration, Instant},
};
use ely_servo_host::SoftwareServoHost;
#[cfg(any(test, all(feature = "hardware-render", target_os = "macos")))]
use ely_servo_host::{IOSurfaceHandle, IOSurfaceIdentity};
use ely_servo_host::IOSurfaceHandle;
use ely_servo_host::{IOSurfaceIdentity, SoftwareServoHost};
use super::live_protocol::{LiveOutcome, LiveSidecarError, PartialFrameTimings};
use super::perf::{FramePerfAggregator, FramePerfSummary, FrameStageTimings, elapsed_ns};
@@ -35,12 +35,18 @@ pub(super) fn populate_surface_fields(
if outcome.response.frame.is_none() {
return;
}
if outcome.frame.is_some() {
return;
}
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
{
let Ok(Some(identity)) = host.peek_iosurface_identity(webview_id) else {
return;
};
align_report_to_surface_identity(outcome, identity);
if let Err(message) = require_report_matches_surface_identity(outcome, identity) {
*outcome = LiveOutcome::error(message);
return;
}
let handle = if surface_has_been_published(published_surface_ids, tab_id, identity) {
None
} else {
@@ -105,11 +111,21 @@ fn handle_matches_identity(handle: IOSurfaceHandle, identity: IOSurfaceIdentity)
}
#[cfg(any(test, all(feature = "hardware-render", target_os = "macos")))]
fn align_report_to_surface_identity(outcome: &mut LiveOutcome, identity: IOSurfaceIdentity) {
if let Some(frame) = outcome.response.frame.as_mut() {
frame.width = identity.width;
frame.height = identity.height;
fn require_report_matches_surface_identity(
outcome: &LiveOutcome,
identity: IOSurfaceIdentity,
) -> Result<(), String> {
let Some(frame) = outcome.response.frame.as_ref() else {
return Ok(());
};
if frame.width == identity.width && frame.height == identity.height {
return Ok(());
}
Err(format!(
"servo hardware surface size {}x{} did not match frame report {}x{}",
identity.width, identity.height, frame.width, frame.height,
))
}
/// Serialise the response then stream the optional raw RGBA frame on
@@ -181,7 +197,7 @@ mod tests {
live_protocol::{LiveFrameReport, LiveOutcome, PartialFrameTimings},
perf::FramePerfAggregator,
};
use super::{align_report_to_surface_identity, surface_publication_for, write_outcome};
use super::{require_report_matches_surface_identity, surface_publication_for, write_outcome};
#[test]
fn unpublished_surface_without_handle_leaves_selector_empty() {
@@ -235,17 +251,22 @@ mod tests {
}
#[test]
fn hardware_report_uses_surface_identity_dimensions() -> Result<(), Box<dyn Error>> {
let mut outcome = LiveOutcome::from_report(
fn hardware_report_mismatch_is_reported() -> Result<(), Box<dyn Error>> {
let outcome = LiveOutcome::from_report(
report_with_size(2180, 1586),
PartialFrameTimings { paint_ns: 1_000, encode_ns: 2_000 },
);
align_report_to_surface_identity(&mut outcome, identity(7, 2168, 1566));
let error = match require_report_matches_surface_identity(&outcome, identity(7, 2168, 1566))
{
Ok(()) => return Err("mismatched IOSurface dimensions must be reported".into()),
Err(error) => error,
};
let report = outcome.response.frame.ok_or("report must remain present")?;
assert_eq!(report.width, 2168);
assert_eq!(report.height, 1566);
assert_eq!(
error,
"servo hardware surface size 2168x1566 did not match frame report 2180x1586",
);
Ok(())
}
@@ -314,6 +335,9 @@ mod tests {
state: "complete",
width,
height,
device_pixel_ratio: 1.0,
css_viewport_width: width,
css_viewport_height: height,
rgba_byte_count: 0,
non_white_pixel_count: 0,
content_pixel_count: 0,
@@ -176,6 +176,9 @@ pub(super) struct LiveFrameReport {
pub state: &'static str,
pub width: u32,
pub height: u32,
pub device_pixel_ratio: f32,
pub css_viewport_width: u32,
pub css_viewport_height: u32,
pub rgba_byte_count: usize,
pub non_white_pixel_count: u64,
pub content_pixel_count: u64,
@@ -183,13 +186,18 @@ pub(super) struct LiveFrameReport {
}
impl LiveFrameReport {
pub fn new(snapshot: &WebViewSnapshot, frame: &RenderedFrame) -> Self {
pub fn new(snapshot: &WebViewSnapshot, frame: &RenderedFrame, device_pixel_ratio: f32) -> Self {
let (css_viewport_width, css_viewport_height) =
css_viewport_size(frame.width(), frame.height(), device_pixel_ratio);
Self {
loaded_url: snapshot.url().map(str::to_string),
title: snapshot.title().map(str::to_string),
state: state_label(snapshot.state()),
width: frame.width(),
height: frame.height(),
device_pixel_ratio,
css_viewport_width,
css_viewport_height,
rgba_byte_count: frame.rgba_bytes().len(),
non_white_pixel_count: frame.non_white_pixel_count(),
content_pixel_count: frame.content_pixel_count(),
@@ -200,13 +208,23 @@ impl LiveFrameReport {
/// Build a report for the hardware IOSurface path. Pixel metrics
/// are unavailable because the path skips framebuffer readback.
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
pub fn new_hardware_surface(snapshot: &WebViewSnapshot, width: u32, height: u32) -> Self {
pub fn new_hardware_surface(
snapshot: &WebViewSnapshot,
width: u32,
height: u32,
device_pixel_ratio: f32,
) -> Self {
let (css_viewport_width, css_viewport_height) =
css_viewport_size(width, height, device_pixel_ratio);
Self {
loaded_url: snapshot.url().map(str::to_string),
title: snapshot.title().map(str::to_string),
state: state_label(snapshot.state()),
width,
height,
device_pixel_ratio,
css_viewport_width,
css_viewport_height,
rgba_byte_count: 0,
non_white_pixel_count: 0,
content_pixel_count: 0,
@@ -215,6 +233,18 @@ impl LiveFrameReport {
}
}
fn css_viewport_size(width: u32, height: u32, device_pixel_ratio: f32) -> (u32, u32) {
let dpr = if device_pixel_ratio.is_finite() && device_pixel_ratio > 0.0 {
device_pixel_ratio
} else {
1.0
};
(
((width as f32) / dpr).round().max(1.0) as u32,
((height as f32) / dpr).round().max(1.0) as u32,
)
}
fn state_label(state: &WebViewState) -> &'static str {
match state {
WebViewState::Created => "created",
@@ -50,7 +50,9 @@ use euclid::Size2D;
use gleam::gl::{self, Gl};
use image::RgbaImage;
use servo::{DeviceIntRect, RenderingContext};
use surfman::chains::{PreserveBuffer, SwapChain};
use surfman::chains::{PreserveBuffer, SwapChain, SwapChainAPI};
#[cfg(target_os = "macos")]
use surfman::platform::macos::cgl::surface::NativeSurface;
use surfman::{
Connection, Context, ContextAttributeFlags, ContextAttributes, Device, Error as SurfmanError,
GLApi, NativeWidget, Surface, SurfaceAccess, SurfaceType,
@@ -64,6 +66,10 @@ pub struct HardwareOffscreenContext {
size: Cell<PhysicalSize<u32>>,
inner: SurfmanInner,
swap_chain: SwapChain<Device>,
#[cfg(target_os = "macos")]
held_presented_surface: RefCell<Option<Surface>>,
#[cfg(target_os = "macos")]
last_presented_iosurface: RefCell<Option<PresentedIOSurface>>,
}
impl HardwareOffscreenContext {
@@ -86,7 +92,15 @@ impl HardwareOffscreenContext {
inner.bind_surface(surface)?;
inner.make_current()?;
let swap_chain = inner.create_attached_swap_chain()?;
Ok(Self { size: Cell::new(size), inner, swap_chain })
Ok(Self {
size: Cell::new(size),
inner,
swap_chain,
#[cfg(target_os = "macos")]
held_presented_surface: RefCell::new(None),
#[cfg(target_os = "macos")]
last_presented_iosurface: RefCell::new(None),
})
}
}
@@ -94,6 +108,8 @@ impl Drop for HardwareOffscreenContext {
fn drop(&mut self) {
let device = &mut self.inner.device.borrow_mut();
let context = &mut self.inner.context.borrow_mut();
#[cfg(target_os = "macos")]
self.destroy_held_presented_surface(device, context);
let _ = self.swap_chain.destroy(device, context);
}
}
@@ -120,6 +136,8 @@ impl RenderingContext for HardwareOffscreenContext {
let device = &mut self.inner.device.borrow_mut();
let context = &mut self.inner.context.borrow_mut();
#[cfg(target_os = "macos")]
self.destroy_held_presented_surface(device, context);
let size = Size2D::new(size.width as i32, size.height as i32);
let _ = self.swap_chain.resize(device, context, size);
}
@@ -127,7 +145,11 @@ impl RenderingContext for HardwareOffscreenContext {
fn present(&self) {
let device = &mut self.inner.device.borrow_mut();
let context = &mut self.inner.context.borrow_mut();
#[cfg(target_os = "macos")]
self.recycle_held_presented_surface();
let _ = self.swap_chain.swap_buffers(device, context, PreserveBuffer::No);
#[cfg(target_os = "macos")]
self.capture_presented_iosurface(device);
}
fn make_current(&self) -> Result<(), SurfmanError> {
@@ -152,54 +174,64 @@ use crate::iosurface_handle::{IOSurfaceHandle, IOSurfaceIdentity};
#[cfg(target_os = "macos")]
impl HardwareOffscreenContext {
/// Cheap, non-mutating identity probe of the currently bound
/// surface. Reads `Device::context_surface_info` (no unbind, no
/// mach port creation) so callers can dedup before paying the
/// price of `current_iosurface_mach_port`.
pub fn peek_iosurface_identity(&self) -> Result<IOSurfaceIdentity, SurfmanError> {
let device = self.inner.device.borrow();
let context = self.inner.context.borrow();
let info = device.context_surface_info(&context)?.ok_or(SurfmanError::Failed)?;
Ok(IOSurfaceIdentity {
surface_id: info.id.0 as u64,
width: u32::try_from(info.size.width).unwrap_or(0),
height: u32::try_from(info.size.height).unwrap_or(0),
})
/// Cheap, non-mutating identity probe of the IOSurface that was
/// just presented. Used by the sidecar to dedup mach port creation.
pub fn peek_iosurface_identity(&self) -> Result<Option<IOSurfaceIdentity>, SurfmanError> {
Ok(self.last_presented_iosurface.borrow().as_ref().map(|surface| surface.identity))
}
/// Snapshot the IOSurface currently bound to the context and
/// return its mach port name plus dimensions and stable surface
/// id. Increments the IOSurface's mach-port use count; the
/// Snapshot the just-presented IOSurface and return its mach port
/// name plus dimensions and stable surface id. Increments the
/// IOSurface's mach-port use count; the
/// receiving process holds it via `IOSurfaceLookupFromMachPort` and
/// is responsible for `mach_port_deallocate` once the import is
/// finished.
///
/// Implementation note: surfman's CGL backend keeps the bound
/// surface inside the GL context. To inspect it we temporarily
/// `unbind_surface_from_context`, call `device.native_surface()`
/// (which retains the `IOSurfaceRef`), then `bind_surface_to_context`
/// again. The unbind path calls `glFlush` so the IOSurface contents
/// are consistent for any reader importing it after this returns.
pub fn current_iosurface_mach_port(&self) -> Result<IOSurfaceHandle, SurfmanError> {
let device = &mut self.inner.device.borrow_mut();
let context = &mut self.inner.context.borrow_mut();
// `new` always binds a surface and `current_iosurface_mach_port`
// is the only method that unbinds; the `None` branch only fires
// if the invariant has been broken from outside.
let surface =
device.unbind_surface_from_context(context)?.ok_or(SurfmanError::Failed)?;
let native = device.native_surface(&surface);
let mach_port = native.0.create_mach_port();
let info = device.surface_info(&surface);
let handle = IOSurfaceHandle {
let presented = self.last_presented_iosurface.borrow();
let presented = presented.as_ref().ok_or(SurfmanError::Failed)?;
let mach_port = presented.native.0.create_mach_port();
Ok(IOSurfaceHandle {
mach_port_name: mach_port,
surface_id: presented.identity.surface_id,
width: presented.identity.width,
height: presented.identity.height,
})
}
fn capture_presented_iosurface(&self, device: &mut Device) {
let Some(surface) = self.swap_chain.take_pending_surface() else {
self.last_presented_iosurface.borrow_mut().take();
return;
};
let info = device.surface_info(&surface);
let native = device.native_surface(&surface);
let identity = IOSurfaceIdentity {
surface_id: info.id.0 as u64,
width: u32::try_from(info.size.width).unwrap_or(0),
height: u32::try_from(info.size.height).unwrap_or(0),
};
device.bind_surface_to_context(context, surface).map_err(|(error, _)| error)?;
Ok(handle)
self.held_presented_surface.replace(Some(surface));
self.last_presented_iosurface.replace(Some(PresentedIOSurface { identity, native }));
}
fn recycle_held_presented_surface(&self) {
if let Some(surface) = self.held_presented_surface.borrow_mut().take() {
self.swap_chain.recycle_surface(surface);
}
}
fn destroy_held_presented_surface(&self, device: &mut Device, context: &mut Context) {
self.last_presented_iosurface.borrow_mut().take();
if let Some(mut surface) = self.held_presented_surface.borrow_mut().take() {
let _ = device.destroy_surface(context, &mut surface);
}
}
}
#[cfg(target_os = "macos")]
struct PresentedIOSurface {
identity: IOSurfaceIdentity,
native: NativeSurface,
}
/// Trimmed mirror of `paint_api::rendering_context::SurfmanRenderingContext`.
@@ -281,12 +313,10 @@ impl SurfmanInner {
fn bind_surface(&self, surface: Surface) -> Result<(), SurfmanError> {
let device = &self.device.borrow();
let context = &mut self.context.borrow_mut();
device
.bind_surface_to_context(context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})?;
device.bind_surface_to_context(context, surface).map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})?;
Ok(())
}
+5 -5
View File
@@ -19,12 +19,12 @@ mod runtime_webview;
pub use error::ServoHostError;
#[cfg(feature = "hardware-render")]
pub use hardware_rendering_context::HardwareOffscreenContext;
pub use iosurface_handle::{IOSurfaceHandle, IOSurfaceIdentity};
pub use host::{
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest,
MouseHoverRequest, NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
RenderedFrame, RenderedFrameSummary, ResizeRequest, ScreenshotRequest, ScrollRequest,
ServoHost, TouchTapRequest, WebViewSnapshot, WebViewState,
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest, MouseHoverRequest,
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, RenderedFrame,
RenderedFrameSummary, ResizeRequest, ScreenshotRequest, ScrollRequest, ServoHost,
TouchTapRequest, WebViewSnapshot, WebViewState,
};
pub use iosurface_handle::{IOSurfaceHandle, IOSurfaceIdentity};
#[cfg(feature = "servo-engine")]
pub use runtime::{RenderingContextKind, ServoSurfaceSize, SoftwareServoHost};
+7 -6
View File
@@ -108,8 +108,8 @@ impl SoftwareServoHost {
/// Paint and present the webview's current surface while leaving
/// framebuffer readback to callers that explicitly need RGBA
/// bytes. The live hardware path uses this before publishing the
/// IOSurface handle to the renderer process.
/// bytes. The live hardware path exports the just-presented
/// IOSurface from the rendering context.
pub fn paint_without_readback(&mut self, webview_id: &WebViewId) -> Result<(), ServoHostError> {
self.paint_webview(webview_id, false).map(|_| ())
}
@@ -204,9 +204,13 @@ impl ServoHost for SoftwareServoHost {
webview.delegate.set_state(WebViewState::Loading);
if should_create_initial_document {
let hidpi_scale_factor = webview.webview.hidpi_scale_factor();
webview.webview = WebViewBuilder::new(&servo, webview.rendering_context.clone())
.delegate(webview.delegate.clone())
.url(url)
// The live path pushes DPR before first navigation. Preserve that scale when
// replacing the about:blank WebView so CSS viewport = physical surface / DPR.
.hidpi_scale_factor(hidpi_scale_factor)
.build();
// Cosmetic: makes the freshly built WebView paint its
// first frame. The input-accepting invariant lives in
@@ -444,10 +448,7 @@ impl SoftwareServoHost {
let Some(hardware) = webview.hardware_context.as_ref() else {
return Ok(None);
};
hardware
.peek_iosurface_identity()
.map(Some)
.map_err(|_| ServoHostError::RenderingContextUnavailable)
hardware.peek_iosurface_identity().map_err(|_| ServoHostError::RenderingContextUnavailable)
}
/// Mint a fresh mach port for the IOSurface bound to this