Fix Servo IOSurface orientation and resize identity

This commit is contained in:
2026-05-13 10:21:50 -04:00
parent 9212b0be24
commit f4c650c4d8
6 changed files with 277 additions and 55 deletions
@@ -8,9 +8,10 @@ use std::{
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId, UrlText};
use ely_servo_host::{
KeyboardTextRequest, MouseClickRequest, MouseHoverRequest, NavigationRequest, PageZoomRequest,
PermissionDecision, PermissionRequest, RenderingContextKind, ResizeRequest, ScrollRequest,
ServoHost, ServoSurfaceSize, SoftwareServoHost,
IOSurfaceIdentity, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
RenderingContextKind, ResizeRequest, ScrollRequest, ServoHost, ServoSurfaceSize,
SoftwareServoHost,
};
use super::args::LiveArgs;
@@ -47,7 +48,7 @@ pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
let mut perf =
FramePerfAggregator::new(context_label, FramePerfAggregator::DEFAULT_WINDOW_SIZE);
let mut pending_summary: Option<FramePerfSummary> = None;
let mut published_surface_ids: HashMap<String, HashSet<u64>> = HashMap::new();
let mut published_surface_ids: HashMap<String, HashSet<IOSurfaceIdentity>> = HashMap::new();
let stdin = io::stdin();
let mut stdout = io::stdout().lock();
@@ -91,7 +92,7 @@ const fn rendering_context_label(kind: RenderingContextKind) -> &'static str {
fn handle_request(
host: &mut SoftwareServoHost,
sessions: &mut HashMap<String, LiveSession>,
published_surface_ids: &mut HashMap<String, HashSet<u64>>,
published_surface_ids: &mut HashMap<String, HashSet<IOSurfaceIdentity>>,
rendering_context_kind: RenderingContextKind,
request: LiveRequest,
) -> Result<LiveOutcome, LiveSidecarError> {
@@ -29,7 +29,7 @@ pub(super) fn populate_surface_fields(
host: &SoftwareServoHost,
webview_id: &ely_domain::WebViewId,
tab_id: &str,
published_surface_ids: &mut HashMap<String, HashSet<u64>>,
published_surface_ids: &mut HashMap<String, HashSet<IOSurfaceIdentity>>,
outcome: &mut LiveOutcome,
) {
if outcome.response.frame.is_none() {
@@ -40,6 +40,7 @@ pub(super) fn populate_surface_fields(
let Ok(Some(identity)) = host.peek_iosurface_identity(webview_id) else {
return;
};
align_report_to_surface_identity(outcome, identity);
let handle = if surface_has_been_published(published_surface_ids, tab_id, identity) {
None
} else {
@@ -57,13 +58,11 @@ pub(super) fn populate_surface_fields(
#[cfg(any(test, all(feature = "hardware-render", target_os = "macos")))]
fn surface_has_been_published(
published_surface_ids: &HashMap<String, HashSet<u64>>,
published_surface_ids: &HashMap<String, HashSet<IOSurfaceIdentity>>,
tab_id: &str,
identity: IOSurfaceIdentity,
) -> bool {
published_surface_ids
.get(tab_id)
.is_some_and(|published| published.contains(&identity.surface_id))
published_surface_ids.get(tab_id).is_some_and(|published| published.contains(&identity))
}
#[cfg(any(test, all(feature = "hardware-render", target_os = "macos")))]
@@ -75,7 +74,7 @@ struct SurfacePublication {
#[cfg(any(test, all(feature = "hardware-render", target_os = "macos")))]
fn surface_publication_for(
published_surface_ids: &mut HashMap<String, HashSet<u64>>,
published_surface_ids: &mut HashMap<String, HashSet<IOSurfaceIdentity>>,
tab_id: &str,
identity: IOSurfaceIdentity,
handle: Option<IOSurfaceHandle>,
@@ -91,7 +90,10 @@ fn surface_publication_for(
return SurfacePublication { current_surface_id: None, surface_handle: None };
};
published_surface_ids.entry(tab_id.to_string()).or_default().insert(handle.surface_id);
published_surface_ids
.entry(tab_id.to_string())
.or_default()
.insert(IOSurfaceIdentity::from_handle(handle));
SurfacePublication { current_surface_id: Some(handle.surface_id), surface_handle: Some(handle) }
}
@@ -102,6 +104,14 @@ fn handle_matches_identity(handle: IOSurfaceHandle, identity: IOSurfaceIdentity)
&& handle.height == identity.height
}
#[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;
}
}
/// Serialise the response then stream the optional raw RGBA frame on
/// the same stdout pipe. The client reads the JSON line, takes
/// `rgba_byte_count` from the report, then reads that many bytes
@@ -163,11 +173,7 @@ pub(super) fn write_outcome(
#[cfg(test)]
mod tests {
use std::{
collections::{HashMap, HashSet},
error::Error,
time::Instant,
};
use std::{collections::HashMap, error::Error, time::Instant};
use ely_servo_host::{IOSurfaceHandle, IOSurfaceIdentity};
@@ -175,7 +181,7 @@ mod tests {
live_protocol::{LiveFrameReport, LiveOutcome, PartialFrameTimings},
perf::FramePerfAggregator,
};
use super::{surface_publication_for, write_outcome};
use super::{align_report_to_surface_identity, surface_publication_for, write_outcome};
#[test]
fn unpublished_surface_without_handle_leaves_selector_empty() {
@@ -197,12 +203,15 @@ mod tests {
assert_eq!(publication.current_surface_id, Some(7));
assert_eq!(publication.surface_handle, Some(handle));
assert!(published.get("tab-1").is_some_and(|ids| ids.contains(&7)));
assert!(published.get("tab-1").is_some_and(|ids| ids.contains(&identity(7, 800, 600))));
}
#[test]
fn published_surface_reuses_selector_without_republishing_handle() {
let mut published = HashMap::from([("tab-1".to_string(), HashSet::from([7]))]);
let mut published = HashMap::new();
let handle = handle(7, 800, 600);
let _ =
surface_publication_for(&mut published, "tab-1", identity(7, 800, 600), Some(handle));
let publication =
surface_publication_for(&mut published, "tab-1", identity(7, 800, 600), None);
@@ -210,6 +219,36 @@ mod tests {
assert!(publication.surface_handle.is_none());
}
#[test]
fn same_surface_id_with_changed_dimensions_republishes_handle() {
let mut published = HashMap::new();
let initial = handle(7, 800, 600);
let resized = handle(7, 1024, 768);
let _ =
surface_publication_for(&mut published, "tab-1", identity(7, 800, 600), Some(initial));
let publication =
surface_publication_for(&mut published, "tab-1", identity(7, 1024, 768), Some(resized));
assert_eq!(publication.current_surface_id, Some(7));
assert_eq!(publication.surface_handle, Some(resized));
}
#[test]
fn hardware_report_uses_surface_identity_dimensions() -> Result<(), Box<dyn Error>> {
let mut 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 report = outcome.response.frame.ok_or("report must remain present")?;
assert_eq!(report.width, 2168);
assert_eq!(report.height, 1566);
Ok(())
}
#[test]
fn mismatched_handle_leaves_surface_unpublished() {
let mut published = HashMap::new();
@@ -263,13 +302,19 @@ mod tests {
}
fn report_with_byte_count(rgba_byte_count: usize) -> LiveFrameReport {
let mut report = report_with_size(2, 2);
report.rgba_byte_count = rgba_byte_count;
report
}
fn report_with_size(width: u32, height: u32) -> LiveFrameReport {
LiveFrameReport {
loaded_url: Some("https://example.com/".to_string()),
title: Some("Example".to_string()),
state: "complete",
width: 2,
height: 2,
rgba_byte_count,
width,
height,
rgba_byte_count: 0,
non_white_pixel_count: 0,
content_pixel_count: 0,
sample_hash: 0,