Transfer Servo IOSurface ports over Mach

This commit is contained in:
2026-05-13 04:05:07 -04:00
parent 72f7b9761e
commit a743435ea1
12 changed files with 442 additions and 8 deletions
@@ -14,6 +14,9 @@ use thiserror::Error;
#[path = "ely_servo_sidecar/args.rs"]
mod args;
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
#[path = "ely_servo_sidecar/iosurface_mach.rs"]
mod iosurface_mach;
#[path = "ely_servo_sidecar/live.rs"]
mod live;
#[path = "ely_servo_sidecar/live_output.rs"]
@@ -15,6 +15,7 @@ pub(super) enum SidecarCommand {
pub(super) struct LiveArgs {
pub(super) profile_data_dir: PathBuf,
pub(super) iosurface_mach_service: Option<String>,
/// Rendering context the host's webviews are built against.
/// Defaults to [`RenderingContextKind::Software`], which keeps
/// the binary's behaviour bit-identical to pre-flag builds.
@@ -137,6 +138,7 @@ fn parse_command(
fn parse_live_args(args: impl IntoIterator<Item = String>) -> Result<LiveArgs, SidecarArgsError> {
let mut args = args.into_iter();
let mut profile_data_dir = None;
let mut iosurface_mach_service = None;
let mut rendering_context_kind = RenderingContextKind::default();
while let Some(name) = args.next() {
@@ -155,6 +157,10 @@ fn parse_live_args(args: impl IntoIterator<Item = String>) -> Result<LiveArgs, S
_ => return Err(SidecarArgsError::InvalidRenderingContext { value }),
};
}
"--iosurface-mach-service" => {
iosurface_mach_service =
Some(next_argument(&mut args, "--iosurface-mach-service")?);
}
_ => return Err(SidecarArgsError::UnknownArgument { value: name }),
}
}
@@ -162,6 +168,7 @@ fn parse_live_args(args: impl IntoIterator<Item = String>) -> Result<LiveArgs, S
Ok(LiveArgs {
profile_data_dir: profile_data_dir
.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--profile-data-dir" })?,
iosurface_mach_service,
rendering_context_kind,
})
}
@@ -159,6 +159,13 @@ fn live_accepts_explicit_hardware_rendering_context() -> Result<(), SidecarArgsE
Ok(())
}
#[test]
fn live_accepts_iosurface_mach_service_name() -> Result<(), SidecarArgsError> {
let args = parse_live(&["--iosurface-mach-service", "com.ely.test.iosurface"])?;
assert_eq!(args.iosurface_mach_service.as_deref(), Some("com.ely.test.iosurface"));
Ok(())
}
#[test]
fn live_rejects_unknown_rendering_context_value() {
assert!(matches!(
@@ -0,0 +1,133 @@
use std::{ffi::CString, mem, time::Duration};
use mach2::{
bootstrap::{bootstrap_look_up, bootstrap_port},
kern_return::KERN_SUCCESS,
mach_port::mach_port_deallocate,
message::{
MACH_MSG_SUCCESS, MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND, MACH_MSGH_BITS,
MACH_MSGH_BITS_COMPLEX, MACH_SEND_MSG, MACH_SEND_TIMEOUT, mach_msg, mach_msg_body_t,
mach_msg_header_t, mach_msg_port_descriptor_t,
},
port::{MACH_PORT_NULL, mach_port_t},
traps::mach_task_self,
};
use thiserror::Error;
use super::live_protocol::{LiveOutcome, LiveSidecarError};
const IOSURFACE_PORT_MESSAGE_ID: i32 = 0x454c_5901;
const SEND_TIMEOUT: Duration = Duration::from_secs(1);
pub(super) struct IOSurfaceMachSender {
send_port: mach_port_t,
}
#[derive(Debug, Error)]
pub(super) enum IOSurfaceMachError {
#[error("Mach service name contains an interior nul byte")]
InvalidServiceName,
#[error("bootstrap_look_up returned {code}")]
LookupService { code: i32 },
#[error("mach_msg send returned {code}")]
Send { code: i32 },
}
impl IOSurfaceMachSender {
pub(super) fn connect(service_name: &str) -> Result<Self, IOSurfaceMachError> {
let service_name =
CString::new(service_name).map_err(|_| IOSurfaceMachError::InvalidServiceName)?;
let mut send_port = MACH_PORT_NULL;
#[expect(unsafe_code)]
let result =
unsafe { bootstrap_look_up(bootstrap_port, service_name.as_ptr(), &mut send_port) };
if result != KERN_SUCCESS {
return Err(IOSurfaceMachError::LookupService { code: result });
}
Ok(Self { send_port })
}
pub(super) fn send_surface_port(
&mut self,
surface_id: u64,
mach_port: mach_port_t,
) -> Result<(), IOSurfaceMachError> {
let mut message = IOSurfacePortMessage {
header: mach_msg_header_t {
msgh_bits: MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX,
msgh_size: mem::size_of::<IOSurfacePortMessage>() as u32,
msgh_remote_port: self.send_port,
msgh_local_port: MACH_PORT_NULL,
msgh_voucher_port: MACH_PORT_NULL,
msgh_id: IOSURFACE_PORT_MESSAGE_ID,
},
body: mach_msg_body_t { msgh_descriptor_count: 1 },
surface_port: mach_msg_port_descriptor_t::new(mach_port, MACH_MSG_TYPE_MOVE_SEND),
surface_id,
};
#[expect(unsafe_code)]
let result = unsafe {
mach_msg(
&mut message.header,
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
message.header.msgh_size,
0,
MACH_PORT_NULL,
timeout_millis(SEND_TIMEOUT),
MACH_PORT_NULL,
)
};
if result != MACH_MSG_SUCCESS {
destroy_message(&mut message);
return Err(IOSurfaceMachError::Send { code: result });
}
Ok(())
}
}
impl Drop for IOSurfaceMachSender {
fn drop(&mut self) {
#[expect(unsafe_code)]
let task = unsafe { mach_task_self() };
#[expect(unsafe_code)]
unsafe {
let _ = mach_port_deallocate(task, self.send_port);
}
}
}
pub(super) fn send_surface_port_if_needed(
sender: Option<&mut IOSurfaceMachSender>,
outcome: &mut Result<LiveOutcome, LiveSidecarError>,
) {
let (Some(sender), Ok(live_outcome)) = (sender, outcome.as_ref()) else {
return;
};
let Some(handle) = live_outcome.response.surface_handle else {
return;
};
if let Err(error) = sender.send_surface_port(handle.surface_id, handle.mach_port_name) {
*outcome = Err(LiveSidecarError::IOSurfaceMach(error));
}
}
#[repr(C)]
struct IOSurfacePortMessage {
header: mach_msg_header_t,
body: mach_msg_body_t,
surface_port: mach_msg_port_descriptor_t,
surface_id: u64,
}
fn timeout_millis(timeout: Duration) -> u32 {
u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX).max(1)
}
fn destroy_message(message: &mut IOSurfacePortMessage) {
#[expect(unsafe_code)]
unsafe {
mach2::message::mach_msg_destroy(&mut message.header);
}
}
@@ -14,6 +14,8 @@ use ely_servo_host::{
};
use super::args::LiveArgs;
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
use super::iosurface_mach::{IOSurfaceMachSender, send_surface_port_if_needed};
use super::live_output::{populate_surface_fields, write_outcome};
pub(super) use super::live_protocol::LiveSidecarError;
use super::live_protocol::{
@@ -28,14 +30,19 @@ 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> {
fs::create_dir_all(&args.profile_data_dir)?;
let rendering_context_kind = args.rendering_context_kind;
let LiveArgs { profile_data_dir, iosurface_mach_service, rendering_context_kind } = args;
fs::create_dir_all(&profile_data_dir)?;
let context_label = rendering_context_label(rendering_context_kind);
let mut host = SoftwareServoHost::new_with_config_dir_and_kind(
ServoSurfaceSize::new(1, 1),
Some(args.profile_data_dir),
Some(profile_data_dir),
rendering_context_kind,
)?;
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
let mut iosurface_mach_sender =
iosurface_mach_service.as_deref().map(IOSurfaceMachSender::connect).transpose()?;
#[cfg(not(all(feature = "hardware-render", target_os = "macos")))]
let _ = iosurface_mach_service;
let mut sessions = HashMap::new();
let mut perf =
FramePerfAggregator::new(context_label, FramePerfAggregator::DEFAULT_WINDOW_SIZE);
@@ -56,7 +63,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 outcome = match serde_json::from_str::<LiveRequest>(&line) {
let mut outcome = match serde_json::from_str::<LiveRequest>(&line) {
Ok(request) => handle_request(
&mut host,
&mut sessions,
@@ -66,6 +73,8 @@ 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);
write_outcome(&mut stdout, &mut perf, &mut pending_summary, outcome, frame_started_at)?;
}
@@ -9,6 +9,8 @@ use ely_servo_host::{
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
use super::iosurface_mach::IOSurfaceMachError;
use super::perf::FramePerfSummary;
#[derive(Deserialize)]
@@ -242,6 +244,10 @@ pub(super) enum LiveSidecarError {
#[error(transparent)]
Json(#[from] serde_json::Error),
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
#[error(transparent)]
IOSurfaceMach(#[from] IOSurfaceMachError),
}
#[cfg(test)]