use std::sync::Arc; use gpui::RenderImage; use image::{ImageBuffer, Rgba}; use thiserror::Error; use crate::services::servo_sidecar::SidecarSnapshot; use super::{ web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollOffset, WebSurfaceSize}, web_surface_image::renderable_image_buffer, }; #[derive(Clone)] pub(super) struct WebSurfaceFrame { pub(super) requested_url: String, loaded_url: Option, title: Option, render_state: String, width: u32, height: u32, scroll_offset: WebSurfaceScrollOffset, click_point: Option, typed_text: Option, #[cfg(all(test, feature = "live-site-smoke"))] non_white_pixel_count: u64, #[cfg(all(test, feature = "live-site-smoke"))] content_pixel_count: u64, #[cfg(all(test, feature = "live-site-smoke"))] sample_hash: u64, pub(super) image: Arc, } impl WebSurfaceFrame { pub(super) fn from_snapshot( requested_url: String, scroll_offset: WebSurfaceScrollOffset, click_point: Option, typed_text: Option, snapshot: SidecarSnapshot, ) -> Result { let width = snapshot.width(); let height = snapshot.height(); let loaded_url = snapshot.loaded_url().map(str::to_string); let title = snapshot.title().map(str::to_string); let render_state = snapshot.render_state().to_string(); #[cfg(all(test, feature = "live-site-smoke"))] let non_white_pixel_count = snapshot.non_white_pixel_count(); #[cfg(all(test, feature = "live-site-smoke"))] let content_pixel_count = snapshot.content_pixel_count(); #[cfg(all(test, feature = "live-site-smoke"))] let sample_hash = snapshot.sample_hash(); let rgba_bytes = snapshot.into_rgba_bytes(); let Some(buffer) = ImageBuffer::, _>::from_raw(width, height, rgba_bytes) else { return Err(WebSurfaceError::InvalidFrameBuffer { width, height }); }; let image_buffer = renderable_image_buffer(buffer); Ok(Self { requested_url, loaded_url, title, render_state, width, height, scroll_offset, click_point, typed_text, #[cfg(all(test, feature = "live-site-smoke"))] non_white_pixel_count, #[cfg(all(test, feature = "live-site-smoke"))] content_pixel_count, #[cfg(all(test, feature = "live-site-smoke"))] sample_hash, image: Arc::new(RenderImage::new([image::Frame::new(image_buffer)])), }) } pub(super) fn title_label(&self) -> String { self.title.clone().unwrap_or_else(|| self.requested_url.clone()) } pub(super) fn url_label(&self) -> &str { self.loaded_url.as_deref().unwrap_or(self.requested_url.as_str()) } pub(super) fn detail_label(&self) -> String { let mut detail = format!("{} {}", self.render_state(), self.scroll_offset.detail_label(self.size())); if let Some(click_point) = self.click_point { detail = format!("{detail} {}", click_point.detail_label()); } match self.typed_text.as_ref() { Some(typed_text) => format!("{detail} text={}b", typed_text.len()), None => detail, } } pub(super) fn size(&self) -> WebSurfaceSize { WebSurfaceSize { width: self.width, height: self.height } } pub(super) fn render_state(&self) -> &str { self.render_state.as_str() } pub(super) fn scroll_offset(&self) -> WebSurfaceScrollOffset { self.scroll_offset } pub(super) fn click_point(&self) -> Option { self.click_point } pub(super) fn typed_text(&self) -> Option<&str> { self.typed_text.as_deref() } #[cfg(all(test, feature = "live-site-smoke"))] pub(super) fn non_white_pixel_count(&self) -> u64 { self.non_white_pixel_count } #[cfg(all(test, feature = "live-site-smoke"))] pub(super) fn content_pixel_count(&self) -> u64 { self.content_pixel_count } #[cfg(all(test, feature = "live-site-smoke"))] pub(super) fn sample_hash(&self) -> u64 { self.sample_hash } } #[derive(Debug, Error)] pub(super) enum WebSurfaceError { #[error("invalid servo frame buffer for {width}x{height}")] InvalidFrameBuffer { width: u32, height: u32 }, }