diff --git a/Cargo.lock b/Cargo.lock index c7257ee..0349cba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2255,6 +2255,7 @@ dependencies = [ "gpui-component-assets", "image", "io-surface", + "mach2", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -2268,6 +2269,7 @@ dependencies = [ "tracing-subscriber", "ureq", "url", + "uuid", ] [[package]] @@ -2309,6 +2311,7 @@ dependencies = [ "glow", "image", "log", + "mach2", "objc2-io-surface", "serde", "serde_json", diff --git a/crates/ely_app/Cargo.toml b/crates/ely_app/Cargo.toml index c046ba1..a7d3b1a 100644 --- a/crates/ely_app/Cargo.toml +++ b/crates/ely_app/Cargo.toml @@ -35,10 +35,12 @@ core-foundation = "0.10" # version it built against. core-video = "0.4" io-surface = "0.16" +mach2 = "0.6" objc2 = "0.6" objc2-core-foundation = { version = "0.3.2", features = ["CFBase", "CFDictionary", "CFNumber", "CFString"] } objc2-foundation = { version = "0.3.1", features = ["NSDictionary", "NSString", "NSValue"] } objc2-io-surface = "0.3.2" +uuid.workspace = true [dev-dependencies] gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/ely_app/src/services/iosurface_mach.rs b/crates/ely_app/src/services/iosurface_mach.rs new file mode 100644 index 0000000..2ea50b6 --- /dev/null +++ b/crates/ely_app/src/services/iosurface_mach.rs @@ -0,0 +1,233 @@ +use std::{ + collections::BTreeMap, + ffi::CString, + mem, + time::{Duration, Instant}, +}; + +use mach2::{ + bootstrap::{bootstrap_port, bootstrap_register}, + kern_return::KERN_SUCCESS, + mach_port::{mach_port_allocate, mach_port_destroy, mach_port_insert_right}, + message::{ + MACH_MSG_PORT_DESCRIPTOR, MACH_MSG_SUCCESS, MACH_MSG_TIMEOUT_NONE, MACH_MSG_TYPE_MAKE_SEND, + MACH_RCV_MSG, MACH_RCV_TIMED_OUT, MACH_RCV_TIMEOUT, mach_msg, mach_msg_body_t, + mach_msg_header_t, mach_msg_port_descriptor_t, mach_msg_trailer_t, + }, + port::{MACH_PORT_NULL, MACH_PORT_RIGHT_RECEIVE, mach_port_t}, + traps::mach_task_self, +}; +use thiserror::Error; +use uuid::Uuid; + +const IOSURFACE_PORT_MESSAGE_ID: i32 = 0x454c_5901; +const SERVICE_PREFIX: &str = "com.ely.browser.iosurface"; + +pub(crate) struct IOSurfaceMachReceiver { + service_name: String, + receive_port: mach_port_t, + pending_ports: BTreeMap, +} + +#[derive(Debug, Error)] +pub(crate) enum IOSurfaceMachError { + #[error("Mach service name contains an interior nul byte")] + InvalidServiceName, + + #[error("mach_port_allocate returned {code}")] + AllocatePort { code: i32 }, + + #[error("mach_port_insert_right returned {code}")] + InsertSendRight { code: i32 }, + + #[error("bootstrap_register returned {code}")] + RegisterService { code: i32 }, + + #[error("mach_msg receive timed out for IOSurface surface {surface_id:#x}")] + ReceiveTimedOut { surface_id: u64 }, + + #[error("mach_msg receive returned {code}")] + Receive { code: i32 }, + + #[error("received unexpected Mach message id {message_id}")] + UnexpectedMessage { message_id: i32 }, + + #[error("received invalid IOSurface Mach message")] + InvalidMessage, +} + +impl IOSurfaceMachReceiver { + pub(crate) fn new() -> Result { + let service_name = unique_service_name(); + let service_name_c = CString::new(service_name.as_str()) + .map_err(|_| IOSurfaceMachError::InvalidServiceName)?; + let mut receive_port = MACH_PORT_NULL; + + #[expect(unsafe_code)] + let task = unsafe { mach_task_self() }; + #[expect(unsafe_code)] + let allocate = + unsafe { mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &mut receive_port) }; + if allocate != KERN_SUCCESS { + return Err(IOSurfaceMachError::AllocatePort { code: allocate }); + } + + #[expect(unsafe_code)] + let insert = unsafe { + mach_port_insert_right(task, receive_port, receive_port, MACH_MSG_TYPE_MAKE_SEND) + }; + if insert != KERN_SUCCESS { + destroy_port(receive_port); + return Err(IOSurfaceMachError::InsertSendRight { code: insert }); + } + + #[expect(unsafe_code)] + #[allow(deprecated)] + let register = unsafe { + bootstrap_register(bootstrap_port, service_name_c.as_ptr() as *mut _, receive_port) + }; + if register != KERN_SUCCESS { + destroy_port(receive_port); + return Err(IOSurfaceMachError::RegisterService { code: register }); + } + + Ok(Self { service_name, receive_port, pending_ports: BTreeMap::new() }) + } + + pub(crate) fn service_name(&self) -> &str { + self.service_name.as_str() + } + + pub(crate) fn receive_port_for_surface( + &mut self, + surface_id: u64, + timeout: Duration, + ) -> Result { + if let Some(port) = self.pending_ports.remove(&surface_id) { + return Ok(port); + } + + let deadline = Instant::now() + timeout; + loop { + let remaining = deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + return Err(IOSurfaceMachError::ReceiveTimedOut { surface_id }); + } + let Some(received) = self.receive_one(remaining)? else { + return Err(IOSurfaceMachError::ReceiveTimedOut { surface_id }); + }; + if received.surface_id == surface_id { + return Ok(received.mach_port); + } + self.pending_ports.insert(received.surface_id, received.mach_port); + } + } + + fn receive_one( + &self, + timeout: Duration, + ) -> Result, IOSurfaceMachError> { + #[expect(unsafe_code)] + let mut received_message: ReceivedIOSurfacePortMessage = unsafe { mem::zeroed() }; + let timeout_ms = timeout_millis(timeout); + #[expect(unsafe_code)] + let result = unsafe { + mach_msg( + &mut received_message.message.header, + MACH_RCV_MSG | MACH_RCV_TIMEOUT, + 0, + mem::size_of::() as u32, + self.receive_port, + timeout_ms, + MACH_PORT_NULL, + ) + }; + + if result == MACH_RCV_TIMED_OUT { + return Ok(None); + } + if result != MACH_MSG_SUCCESS { + return Err(IOSurfaceMachError::Receive { code: result }); + } + let message = &mut received_message.message; + if message.header.msgh_id != IOSURFACE_PORT_MESSAGE_ID { + destroy_message(message); + return Err(IOSurfaceMachError::UnexpectedMessage { + message_id: message.header.msgh_id, + }); + } + if message.body.msgh_descriptor_count != 1 + || message.surface_port.type_ != MACH_MSG_PORT_DESCRIPTOR as u8 + || message.surface_port.name == MACH_PORT_NULL + { + destroy_message(message); + return Err(IOSurfaceMachError::InvalidMessage); + } + + Ok(Some(ReceivedSurfacePort { + surface_id: message.surface_id, + mach_port: message.surface_port.name, + })) + } +} + +impl Drop for IOSurfaceMachReceiver { + fn drop(&mut self) { + for port in std::mem::take(&mut self.pending_ports).into_values() { + deallocate_port(port); + } + destroy_port(self.receive_port); + } +} + +struct ReceivedSurfacePort { + surface_id: u64, + mach_port: mach_port_t, +} + +#[repr(C)] +struct IOSurfacePortMessage { + header: mach_msg_header_t, + body: mach_msg_body_t, + surface_port: mach_msg_port_descriptor_t, + surface_id: u64, +} + +#[repr(C)] +struct ReceivedIOSurfacePortMessage { + message: IOSurfacePortMessage, + _trailer: mach_msg_trailer_t, +} + +fn unique_service_name() -> String { + format!("{SERVICE_PREFIX}.{}", Uuid::now_v7().as_simple()) +} + +fn timeout_millis(timeout: Duration) -> u32 { + u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX).max(MACH_MSG_TIMEOUT_NONE + 1) +} + +fn destroy_message(message: &mut IOSurfacePortMessage) { + #[expect(unsafe_code)] + unsafe { + mach2::message::mach_msg_destroy(&mut message.header); + } +} + +fn destroy_port(port: mach_port_t) { + #[expect(unsafe_code)] + let task = unsafe { mach_task_self() }; + #[expect(unsafe_code)] + unsafe { + let _ = mach_port_destroy(task, port); + } +} + +fn deallocate_port(port: mach_port_t) { + #[expect(unsafe_code)] + let task = unsafe { mach_task_self() }; + #[expect(unsafe_code)] + unsafe { + let _ = mach2::mach_port::mach_port_deallocate(task, port); + } +} diff --git a/crates/ely_app/src/services/mod.rs b/crates/ely_app/src/services/mod.rs index 61167d2..ee2483f 100644 --- a/crates/ely_app/src/services/mod.rs +++ b/crates/ely_app/src/services/mod.rs @@ -2,6 +2,8 @@ pub mod download_checksums; pub mod download_files; pub mod http_downloads; #[cfg(target_os = "macos")] +pub(crate) mod iosurface_mach; +#[cfg(target_os = "macos")] pub(crate) mod iosurface_metal; pub mod plugin_package_store; pub mod plugin_packages; diff --git a/crates/ely_app/src/services/servo_live.rs b/crates/ely_app/src/services/servo_live.rs index 819f5c6..d066977 100644 --- a/crates/ely_app/src/services/servo_live.rs +++ b/crates/ely_app/src/services/servo_live.rs @@ -2,6 +2,7 @@ use std::{ io::{self, BufRead, BufReader, Read, Write}, path::PathBuf, process::{Child, ChildStdin, ChildStdout, Stdio}, + time::Duration, }; /// Environment variable that lets the user pick the rendering context @@ -18,13 +19,16 @@ use thiserror::Error; mod wire; use super::servo_sidecar_command::{ - SidecarCommandError, default_sidecar_command, rendering_context_from_env, + SidecarCommandError, SidecarRenderingContext, default_sidecar_command, + rendering_context_from_env, }; use wire::{ LiveFrameReport, LiveRequest, LiveResponse, LiveSurfaceHandle, log_frame_perf, log_iosurface_current, log_iosurface_handle, }; +#[cfg(target_os = "macos")] +use super::iosurface_mach::{IOSurfaceMachError, IOSurfaceMachReceiver}; #[cfg(target_os = "macos")] use super::iosurface_metal::IOSurfaceCache; #[cfg(target_os = "macos")] @@ -39,6 +43,8 @@ pub(crate) struct ServoLiveClient { /// software-path tabs never trigger construction. #[cfg(target_os = "macos")] iosurface_cache: IOSurfaceCache, + #[cfg(target_os = "macos")] + iosurface_receiver: Option, } impl ServoLiveClient { @@ -48,9 +54,18 @@ impl ServoLiveClient { return Err(ServoLiveError::SidecarBinaryUnavailable { path: path.to_path_buf() }); } + let rendering_context = rendering_context_from_env(); let mut command = command_target.command(); command.arg("live").arg("--profile-data-dir").arg(profile_data_dir); - command.arg("--rendering-context").arg(rendering_context_from_env().cli_arg()); + command.arg("--rendering-context").arg(rendering_context.cli_arg()); + #[cfg(target_os = "macos")] + let iosurface_receiver = if rendering_context == SidecarRenderingContext::Hardware { + let receiver = IOSurfaceMachReceiver::new()?; + command.arg("--iosurface-mach-service").arg(receiver.service_name()); + Some(receiver) + } else { + None + }; let mut child = command .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -67,6 +82,8 @@ impl ServoLiveClient { stdout: BufReader::new(stdout), #[cfg(target_os = "macos")] iosurface_cache: IOSurfaceCache::new(), + #[cfg(target_os = "macos")] + iosurface_receiver, }) } @@ -195,7 +212,13 @@ impl ServoLiveClient { &mut self, handle: &LiveSurfaceHandle, ) -> Result<(), ServoLiveError> { - match self.iosurface_cache.import(handle.mach_port_name, handle.surface_id) { + let mach_port_name = match self.iosurface_receiver.as_mut() { + Some(receiver) => { + receiver.receive_port_for_surface(handle.surface_id, Duration::from_secs(1))? + } + None => handle.mach_port_name, + }; + match self.iosurface_cache.import(mach_port_name, handle.surface_id) { Ok(()) => tracing::info!( target: "ely::servo::iosurface", surface_id = handle.surface_id, @@ -212,7 +235,7 @@ impl ServoLiveClient { ); return Err(ServoLiveError::IOSurfaceImportFailed { surface_id: handle.surface_id, - mach_port_name: handle.mach_port_name, + mach_port_name, message: error.to_string(), }); } @@ -438,6 +461,10 @@ pub(crate) enum ServoLiveError { #[error("servo live IOSurface {surface_id:#x} was selected before its pixel buffer import")] IOSurfacePixelBufferMissing { surface_id: u64 }, + #[cfg(target_os = "macos")] + #[error(transparent)] + IOSurfaceMach(#[from] IOSurfaceMachError), + #[error(transparent)] Json(#[from] serde_json::Error), diff --git a/crates/ely_servo_host/Cargo.toml b/crates/ely_servo_host/Cargo.toml index 6682a81..aaff3ab 100644 --- a/crates/ely_servo_host/Cargo.toml +++ b/crates/ely_servo_host/Cargo.toml @@ -14,6 +14,7 @@ hardware-render = [ "dep:glow", "dep:image", "dep:log", + "dep:mach2", "dep:surfman", "dep:objc2-io-surface", ] @@ -39,6 +40,7 @@ thiserror.workspace = true url = { workspace = true, optional = true } [target.'cfg(target_os = "macos")'.dependencies] +mach2 = { version = "0.6", optional = true } objc2-io-surface = { version = "0.3.2", optional = true } [lints] diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar.rs index 6b4f1bb..4d55409 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar.rs @@ -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"] diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/args.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/args.rs index a2c1216..92ab9b3 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar/args.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/args.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, /// 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) -> Result { 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) -> Result 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) -> Result 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!( diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/iosurface_mach.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/iosurface_mach.rs new file mode 100644 index 0000000..1dd0b45 --- /dev/null +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/iosurface_mach.rs @@ -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 { + 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::() 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, +) { + 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); + } +} diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs index 19a5353..0098485 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs @@ -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::(&line) { + let mut outcome = match serde_json::from_str::(&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)?; } diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs index b756103..785e807 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs @@ -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)]