Bridge Servo screenshot capture
This commit is contained in:
@@ -23,4 +23,10 @@ pub enum ServoHostError {
|
||||
|
||||
#[error("servo rendered frame is unavailable")]
|
||||
RenderedFrameUnavailable,
|
||||
|
||||
#[error("servo screenshot capture timed out for {id}")]
|
||||
ScreenshotTimedOut { id: WebViewId },
|
||||
|
||||
#[error("servo screenshot capture failed: {reason}")]
|
||||
ScreenshotUnavailable { reason: String },
|
||||
}
|
||||
|
||||
@@ -256,6 +256,11 @@ pub struct KeyboardTextRequest {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ScreenshotRequest {
|
||||
pub webview_id: WebViewId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PermissionRequest {
|
||||
pub webview_id: WebViewId,
|
||||
@@ -292,6 +297,11 @@ pub trait ServoHost {
|
||||
|
||||
fn type_text(&mut self, request: KeyboardTextRequest) -> Result<(), ServoHostError>;
|
||||
|
||||
fn capture_screenshot(
|
||||
&mut self,
|
||||
request: ScreenshotRequest,
|
||||
) -> Result<RenderedFrame, ServoHostError>;
|
||||
|
||||
fn set_permission(
|
||||
&mut self,
|
||||
request: PermissionRequest,
|
||||
|
||||
@@ -6,12 +6,14 @@ mod keyboard;
|
||||
mod runtime;
|
||||
#[cfg(feature = "servo-engine")]
|
||||
mod runtime_input;
|
||||
#[cfg(feature = "servo-engine")]
|
||||
mod runtime_waker;
|
||||
|
||||
pub use error::ServoHostError;
|
||||
pub use host::{
|
||||
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest,
|
||||
PermissionDecision, PermissionRequest, RenderedFrame, RenderedFrameSummary, ResizeRequest,
|
||||
ScrollRequest, ServoHost, TouchTapRequest, WebViewSnapshot, WebViewState,
|
||||
ScreenshotRequest, ScrollRequest, ServoHost, TouchTapRequest, WebViewSnapshot, WebViewState,
|
||||
};
|
||||
#[cfg(feature = "servo-engine")]
|
||||
pub use runtime::{ServoSurfaceSize, SoftwareServoHost};
|
||||
|
||||
@@ -6,25 +6,30 @@ use std::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use dpi::PhysicalSize;
|
||||
use ely_domain::{ProfileId, TabId, WebViewId};
|
||||
use servo::{
|
||||
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, DeviceVector2D, EventLoopWaker,
|
||||
LoadStatus, RenderingContext, Scroll, Servo, ServoBuilder, WebView, WebViewBuilder,
|
||||
WebViewDelegate, WebViewPoint, WebViewVector,
|
||||
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, DeviceVector2D, LoadStatus,
|
||||
RenderingContext, Scroll, Servo, ServoBuilder, WebView, WebViewBuilder, WebViewDelegate,
|
||||
WebViewPoint, WebViewVector,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest,
|
||||
PermissionDecision, PermissionRequest, RenderedFrame, ResizeRequest, ScrollRequest, ServoHost,
|
||||
ServoHostError, TouchTapRequest, WebViewSnapshot, WebViewState,
|
||||
PermissionDecision, PermissionRequest, RenderedFrame, ResizeRequest, ScreenshotRequest,
|
||||
ScrollRequest, ServoHost, ServoHostError, TouchTapRequest, WebViewSnapshot, WebViewState,
|
||||
runtime_input::{send_keyboard_text, send_mouse_click, send_mouse_drag, send_touch_tap},
|
||||
runtime_waker::ServoWakeFlag,
|
||||
};
|
||||
|
||||
static SERVO_RUNTIME_STARTED: AtomicBool = AtomicBool::new(false);
|
||||
const SCREENSHOT_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
const SCREENSHOT_POLL_INTERVAL: Duration = Duration::from_millis(2);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ServoSurfaceSize {
|
||||
@@ -221,6 +226,41 @@ impl ServoHost for SoftwareServoHost {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capture_screenshot(
|
||||
&mut self,
|
||||
request: ScreenshotRequest,
|
||||
) -> Result<RenderedFrame, ServoHostError> {
|
||||
let webview = self.webview(&request.webview_id)?.webview.clone();
|
||||
let captured_image = Rc::new(RefCell::new(None));
|
||||
let callback_image = captured_image.clone();
|
||||
webview.take_screenshot(None, move |result| {
|
||||
callback_image.replace(Some(result));
|
||||
});
|
||||
|
||||
let started_at = Instant::now();
|
||||
while captured_image.borrow().is_none() {
|
||||
if started_at.elapsed() >= SCREENSHOT_TIMEOUT {
|
||||
return Err(ServoHostError::ScreenshotTimedOut { id: request.webview_id.clone() });
|
||||
}
|
||||
|
||||
self.tick();
|
||||
if self.snapshot(&request.webview_id)?.has_pending_frame() {
|
||||
self.paint(&request.webview_id)?;
|
||||
}
|
||||
thread::sleep(SCREENSHOT_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
let Some(result) = captured_image.borrow_mut().take() else {
|
||||
return Err(ServoHostError::RenderedFrameUnavailable);
|
||||
};
|
||||
let image = result.map_err(|error| ServoHostError::ScreenshotUnavailable {
|
||||
reason: format!("{error:?}"),
|
||||
})?;
|
||||
let frame = RenderedFrame::from_rgba_bytes(image.width(), image.height(), image.into_raw());
|
||||
self.last_rendered_frame = Some(frame.clone());
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
fn set_permission(
|
||||
&mut self,
|
||||
request: PermissionRequest,
|
||||
@@ -456,24 +496,3 @@ impl PermissionKey {
|
||||
Self { profile_id, tab_id, feature }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ServoWakeFlag {
|
||||
requested: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ServoWakeFlag {
|
||||
fn new(requested: Arc<AtomicBool>) -> Self {
|
||||
Self { requested }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker for ServoWakeFlag {
|
||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn wake(&self) {
|
||||
self.requested.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use servo::EventLoopWaker;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct ServoWakeFlag {
|
||||
requested: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ServoWakeFlag {
|
||||
pub(super) fn new(requested: Arc<AtomicBool>) -> Self {
|
||||
Self { requested }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker for ServoWakeFlag {
|
||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn wake(&self) {
|
||||
self.requested.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user