Add mouse hover tracking across the Servo input pipeline
Without continuous MouseMove events, Servo never updates hover state: no cursor changes over links, no :hover CSS effects, no mouseenter JavaScript events. The web page appears completely non-interactive. Track mouse position from GPUI on_mouse_move through the full IPC pipeline to Servo. Hover position is included with each ensure request so Servo updates hover state at frame rate (~60fps).
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
io::{self, BufRead, Write},
|
||||
path::PathBuf,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId, UrlText};
|
||||
use ely_servo_host::{
|
||||
KeyboardTextRequest, MouseClickRequest, NavigationRequest, PageZoomRequest, PermissionDecision,
|
||||
PermissionRequest, RenderedFrame, ResizeRequest, ScrollRequest, ServoHost, ServoHostError,
|
||||
ServoSurfaceSize, SoftwareServoHost, WebViewSnapshot, WebViewState,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::args::LiveArgs;
|
||||
|
||||
const LIVE_FRAME_WAIT_TIMEOUT: Duration = Duration::from_millis(60);
|
||||
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 mut host = SoftwareServoHost::new_with_config_dir(
|
||||
ServoSurfaceSize::new(1, 1),
|
||||
Some(args.profile_data_dir),
|
||||
)?;
|
||||
let mut sessions = HashMap::new();
|
||||
let stdin = io::stdin();
|
||||
let mut stdout = io::stdout().lock();
|
||||
|
||||
for line in stdin.lock().lines() {
|
||||
let line = line?;
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let response = match serde_json::from_str::<LiveRequest>(&line) {
|
||||
Ok(request) => handle_request(&mut host, &mut sessions, request),
|
||||
Err(error) => Err(LiveSidecarError::Json(error)),
|
||||
};
|
||||
write_response(&mut stdout, response)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
host: &mut SoftwareServoHost,
|
||||
sessions: &mut HashMap<String, LiveSession>,
|
||||
request: LiveRequest,
|
||||
) -> Result<LiveResponse, LiveSidecarError> {
|
||||
match request {
|
||||
LiveRequest::Ensure {
|
||||
tab_id,
|
||||
profile_id,
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
page_zoom_percent,
|
||||
scroll_delta_x,
|
||||
scroll_delta_y,
|
||||
click_x,
|
||||
click_y,
|
||||
hover_x,
|
||||
hover_y,
|
||||
typed_text,
|
||||
site_permissions,
|
||||
rgba_out,
|
||||
} => {
|
||||
let tab = TabId::parse(tab_id.clone())?;
|
||||
let profile = ProfileId::parse(profile_id)?;
|
||||
let url = UrlText::parse(url)?;
|
||||
let session = ensure_session(host, sessions, tab_id, &tab, &profile, width, height)?;
|
||||
|
||||
if apply_layout(host, session, width, height, page_zoom_percent)? {
|
||||
session.awaiting_visible_frame = true;
|
||||
}
|
||||
apply_permissions(host, session, &profile, site_permissions)?;
|
||||
if session.requested_url != url.as_str() {
|
||||
host.navigate(NavigationRequest {
|
||||
webview_id: session.webview_id.clone(),
|
||||
tab_id: tab,
|
||||
url: url.clone(),
|
||||
})?;
|
||||
session.requested_url = url.as_str().to_string();
|
||||
session.scroll_x = 0;
|
||||
session.scroll_y = 0;
|
||||
session.awaiting_visible_frame = true;
|
||||
}
|
||||
if apply_input(
|
||||
host,
|
||||
session,
|
||||
scroll_delta_x,
|
||||
scroll_delta_y,
|
||||
click_x,
|
||||
click_y,
|
||||
hover_x,
|
||||
hover_y,
|
||||
typed_text,
|
||||
)? {
|
||||
session.awaiting_visible_frame = true;
|
||||
}
|
||||
poll_frame(host, session, rgba_out.into())
|
||||
}
|
||||
LiveRequest::Poll { tab_id, rgba_out } => {
|
||||
let Some(session) = sessions.get_mut(&tab_id) else {
|
||||
return Ok(LiveResponse::empty());
|
||||
};
|
||||
poll_frame(host, session, rgba_out.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_response(
|
||||
stdout: &mut impl Write,
|
||||
response: Result<LiveResponse, LiveSidecarError>,
|
||||
) -> Result<(), LiveSidecarError> {
|
||||
let response = match response {
|
||||
Ok(response) => response,
|
||||
Err(error) => LiveResponse::error(error.to_string()),
|
||||
};
|
||||
serde_json::to_writer(&mut *stdout, &response)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_session<'a>(
|
||||
host: &mut SoftwareServoHost,
|
||||
sessions: &'a mut HashMap<String, LiveSession>,
|
||||
key: String,
|
||||
tab_id: &TabId,
|
||||
profile_id: &ProfileId,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<&'a mut LiveSession, LiveSidecarError> {
|
||||
if !sessions.contains_key(&key) {
|
||||
let webview_id = host.create_webview_with_size(
|
||||
tab_id.clone(),
|
||||
profile_id.clone(),
|
||||
ServoSurfaceSize::new(width, height),
|
||||
)?;
|
||||
sessions.insert(key.clone(), LiveSession::new(webview_id, width, height));
|
||||
}
|
||||
|
||||
sessions.get_mut(&key).ok_or(LiveSidecarError::SessionUnavailable)
|
||||
}
|
||||
|
||||
fn apply_layout(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &mut LiveSession,
|
||||
width: u32,
|
||||
height: u32,
|
||||
page_zoom_percent: u16,
|
||||
) -> Result<bool, LiveSidecarError> {
|
||||
let mut changed = false;
|
||||
if session.width != width || session.height != height {
|
||||
host.resize(ResizeRequest { webview_id: session.webview_id.clone(), width, height })?;
|
||||
session.width = width;
|
||||
session.height = height;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if session.page_zoom_percent != page_zoom_percent {
|
||||
host.set_page_zoom(PageZoomRequest {
|
||||
webview_id: session.webview_id.clone(),
|
||||
zoom_factor: f32::from(page_zoom_percent) / 100.0,
|
||||
})?;
|
||||
session.page_zoom_percent = page_zoom_percent;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
fn apply_permissions(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &LiveSession,
|
||||
profile_id: &ProfileId,
|
||||
permissions: Vec<LiveSitePermission>,
|
||||
) -> Result<(), LiveSidecarError> {
|
||||
for permission in permissions {
|
||||
host.set_permission(
|
||||
PermissionRequest {
|
||||
webview_id: session.webview_id.clone(),
|
||||
profile_id: profile_id.clone(),
|
||||
origin: ely_domain::SiteOrigin::parse(permission.origin)?,
|
||||
feature: ely_domain::SitePermissionFeature::parse(permission.feature.as_str())?,
|
||||
},
|
||||
PermissionDecision::from(ely_domain::SitePermissionDecision::parse(
|
||||
permission.decision.as_str(),
|
||||
)?),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_input(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &mut LiveSession,
|
||||
scroll_delta_x: i32,
|
||||
scroll_delta_y: i32,
|
||||
click_x: Option<u32>,
|
||||
click_y: Option<u32>,
|
||||
hover_x: Option<u32>,
|
||||
hover_y: Option<u32>,
|
||||
typed_text: Option<String>,
|
||||
) -> Result<bool, LiveSidecarError> {
|
||||
let mut changed = false;
|
||||
if scroll_delta_x != 0 || scroll_delta_y != 0 {
|
||||
host.scroll(ScrollRequest {
|
||||
webview_id: session.webview_id.clone(),
|
||||
delta_x: scroll_delta_x,
|
||||
delta_y: scroll_delta_y,
|
||||
})?;
|
||||
session.scroll_x = positive_scroll_component(session.scroll_x, scroll_delta_x);
|
||||
session.scroll_y = positive_scroll_component(session.scroll_y, scroll_delta_y);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if let (Some(x), Some(y)) = (hover_x, hover_y) {
|
||||
host.hover(x, y)?;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if let (Some(x), Some(y)) = (click_x, click_y) {
|
||||
host.click(MouseClickRequest { webview_id: session.webview_id.clone(), x, y })?;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if let Some(text) = typed_text {
|
||||
host.type_text(KeyboardTextRequest { webview_id: session.webview_id.clone(), text })?;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
fn poll_frame(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &mut LiveSession,
|
||||
rgba_out: PathBuf,
|
||||
) -> Result<LiveResponse, LiveSidecarError> {
|
||||
let started_at = Instant::now();
|
||||
let mut latest_frame = None;
|
||||
|
||||
loop {
|
||||
host.tick();
|
||||
let snapshot = host.snapshot(&session.webview_id)?;
|
||||
if snapshot.has_pending_frame() {
|
||||
host.paint(&session.webview_id)?;
|
||||
let snapshot = host.snapshot(&session.webview_id)?;
|
||||
let frame = host.last_rendered_frame()?;
|
||||
let has_visible_content =
|
||||
frame.non_white_pixel_count() > 0 && frame.content_pixel_count() > 0;
|
||||
fs::write(&rgba_out, frame.rgba_bytes())?;
|
||||
let response =
|
||||
LiveResponse::frame(LiveFrameReport::new(&snapshot, &frame, rgba_out.clone()));
|
||||
if has_visible_content {
|
||||
session.awaiting_visible_frame = false;
|
||||
return Ok(response);
|
||||
}
|
||||
if !session.awaiting_visible_frame {
|
||||
return Ok(response);
|
||||
}
|
||||
latest_frame = Some(response);
|
||||
}
|
||||
|
||||
if !session.awaiting_visible_frame {
|
||||
return Ok(LiveResponse::empty());
|
||||
}
|
||||
if started_at.elapsed() >= LIVE_FRAME_WAIT_TIMEOUT {
|
||||
return Ok(latest_frame.unwrap_or_else(LiveResponse::empty));
|
||||
}
|
||||
|
||||
thread::sleep(LIVE_FRAME_WAIT_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LiveSession {
|
||||
webview_id: ely_domain::WebViewId,
|
||||
requested_url: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
page_zoom_percent: u16,
|
||||
scroll_x: i32,
|
||||
scroll_y: i32,
|
||||
awaiting_visible_frame: bool,
|
||||
}
|
||||
|
||||
impl LiveSession {
|
||||
fn new(webview_id: ely_domain::WebViewId, width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
webview_id,
|
||||
requested_url: String::new(),
|
||||
width: width.max(1),
|
||||
height: height.max(1),
|
||||
page_zoom_percent: DEFAULT_ZOOM_PERCENT,
|
||||
scroll_x: 0,
|
||||
scroll_y: 0,
|
||||
awaiting_visible_frame: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum LiveRequest {
|
||||
Ensure {
|
||||
tab_id: String,
|
||||
profile_id: String,
|
||||
url: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
page_zoom_percent: u16,
|
||||
scroll_delta_x: i32,
|
||||
scroll_delta_y: i32,
|
||||
click_x: Option<u32>,
|
||||
click_y: Option<u32>,
|
||||
#[serde(default)]
|
||||
hover_x: Option<u32>,
|
||||
#[serde(default)]
|
||||
hover_y: Option<u32>,
|
||||
typed_text: Option<String>,
|
||||
site_permissions: Vec<LiveSitePermission>,
|
||||
rgba_out: String,
|
||||
},
|
||||
Poll {
|
||||
tab_id: String,
|
||||
rgba_out: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LiveSitePermission {
|
||||
origin: String,
|
||||
feature: String,
|
||||
decision: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LiveResponse {
|
||||
error: Option<String>,
|
||||
frame: Option<LiveFrameReport>,
|
||||
}
|
||||
|
||||
impl LiveResponse {
|
||||
fn empty() -> Self {
|
||||
Self { error: None, frame: None }
|
||||
}
|
||||
|
||||
fn frame(frame: LiveFrameReport) -> Self {
|
||||
Self { error: None, frame: Some(frame) }
|
||||
}
|
||||
|
||||
fn error(message: String) -> Self {
|
||||
Self { error: Some(message), frame: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LiveFrameReport {
|
||||
loaded_url: Option<String>,
|
||||
title: Option<String>,
|
||||
state: &'static str,
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba_path: PathBuf,
|
||||
rgba_byte_count: usize,
|
||||
non_white_pixel_count: u64,
|
||||
content_pixel_count: u64,
|
||||
sample_hash: u64,
|
||||
}
|
||||
|
||||
impl LiveFrameReport {
|
||||
fn new(snapshot: &WebViewSnapshot, frame: &RenderedFrame, rgba_path: PathBuf) -> Self {
|
||||
Self {
|
||||
loaded_url: snapshot.url().map(str::to_string),
|
||||
title: snapshot.title().map(str::to_string),
|
||||
state: state_label(snapshot.state()),
|
||||
width: frame.width(),
|
||||
height: frame.height(),
|
||||
rgba_path,
|
||||
rgba_byte_count: frame.rgba_bytes().len(),
|
||||
non_white_pixel_count: frame.non_white_pixel_count(),
|
||||
content_pixel_count: frame.content_pixel_count(),
|
||||
sample_hash: frame.sample_hash(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(super) enum LiveSidecarError {
|
||||
#[error("live session is unavailable after creation")]
|
||||
SessionUnavailable,
|
||||
|
||||
#[error(transparent)]
|
||||
Domain(#[from] ely_domain::DomainError),
|
||||
|
||||
#[error(transparent)]
|
||||
Host(#[from] ServoHostError),
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
fn state_label(state: &WebViewState) -> &'static str {
|
||||
match state {
|
||||
WebViewState::Created => "created",
|
||||
WebViewState::Loading => "loading",
|
||||
WebViewState::Complete => "complete",
|
||||
WebViewState::Sleeping => "sleeping",
|
||||
WebViewState::Crashed => "crashed",
|
||||
}
|
||||
}
|
||||
|
||||
fn positive_scroll_component(current: i32, delta: i32) -> i32 {
|
||||
let value = i64::from(current) + i64::from(delta);
|
||||
value.clamp(0, i64::from(i32::MAX)) as i32
|
||||
}
|
||||
@@ -23,7 +23,9 @@ use crate::{
|
||||
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest, PageZoomRequest,
|
||||
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_input::{
|
||||
send_keyboard_text, send_mouse_click, send_mouse_drag, send_mouse_hover, send_touch_tap,
|
||||
},
|
||||
runtime_permissions::{PermissionStore, set_permission_decision},
|
||||
runtime_waker::ServoWakeFlag,
|
||||
runtime_webview::{HostWebView, HostWebViewDelegate},
|
||||
@@ -52,7 +54,7 @@ impl ServoSurfaceSize {
|
||||
|
||||
pub struct SoftwareServoHost {
|
||||
servo: Servo,
|
||||
rendering_context: Rc<dyn RenderingContext>,
|
||||
default_surface_size: ServoSurfaceSize,
|
||||
webviews: HashMap<WebViewId, HostWebView>,
|
||||
permissions: PermissionStore,
|
||||
wake_requested: Arc<AtomicBool>,
|
||||
@@ -82,16 +84,19 @@ impl SoftwareServoHost {
|
||||
host
|
||||
}
|
||||
|
||||
pub fn create_webview_with_size(
|
||||
&mut self,
|
||||
tab_id: TabId,
|
||||
profile_id: ProfileId,
|
||||
size: ServoSurfaceSize,
|
||||
) -> Result<WebViewId, ServoHostError> {
|
||||
self.create_webview_in_context(tab_id, profile_id, size)
|
||||
}
|
||||
|
||||
fn new_started(
|
||||
size: ServoSurfaceSize,
|
||||
config_dir: Option<PathBuf>,
|
||||
) -> Result<Self, ServoHostError> {
|
||||
let rendering_context = Rc::new(
|
||||
servo::SoftwareRenderingContext::new(size.physical())
|
||||
.map_err(|_| ServoHostError::RenderingContextUnavailable)?,
|
||||
);
|
||||
rendering_context.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
|
||||
let wake_requested = Arc::new(AtomicBool::new(false));
|
||||
let mut builder = ServoBuilder::default()
|
||||
.event_loop_waker(Box::new(ServoWakeFlag::new(wake_requested.clone())));
|
||||
@@ -102,7 +107,7 @@ impl SoftwareServoHost {
|
||||
|
||||
Ok(Self {
|
||||
servo,
|
||||
rendering_context,
|
||||
default_surface_size: size,
|
||||
webviews: HashMap::new(),
|
||||
permissions: Rc::new(RefCell::new(HashMap::new())),
|
||||
wake_requested,
|
||||
@@ -117,19 +122,7 @@ impl ServoHost for SoftwareServoHost {
|
||||
tab_id: TabId,
|
||||
profile_id: ProfileId,
|
||||
) -> Result<WebViewId, ServoHostError> {
|
||||
let webview_id = WebViewId::new();
|
||||
let delegate =
|
||||
Rc::new(HostWebViewDelegate::new(profile_id.clone(), self.permissions.clone()));
|
||||
let webview = WebViewBuilder::new(&self.servo, self.rendering_context.clone())
|
||||
.delegate(delegate.clone())
|
||||
.build();
|
||||
|
||||
self.webviews.insert(
|
||||
webview_id.clone(),
|
||||
HostWebView { tab_id, profile_id, webview, delegate, requested_url: None },
|
||||
);
|
||||
|
||||
Ok(webview_id)
|
||||
self.create_webview_in_context(tab_id, profile_id, self.default_surface_size)
|
||||
}
|
||||
|
||||
fn navigate(&mut self, request: NavigationRequest) -> Result<(), ServoHostError> {
|
||||
@@ -138,7 +131,6 @@ impl ServoHost for SoftwareServoHost {
|
||||
})?;
|
||||
self.servo.spin_event_loop();
|
||||
let servo = self.servo.clone();
|
||||
let rendering_context = self.rendering_context.clone();
|
||||
let webview = self
|
||||
.webviews
|
||||
.get_mut(&request.webview_id)
|
||||
@@ -151,7 +143,7 @@ impl ServoHost for SoftwareServoHost {
|
||||
|
||||
webview.delegate.set_state(WebViewState::Loading);
|
||||
if should_create_initial_document {
|
||||
webview.webview = WebViewBuilder::new(&servo, rendering_context)
|
||||
webview.webview = WebViewBuilder::new(&servo, webview.rendering_context.clone())
|
||||
.delegate(webview.delegate.clone())
|
||||
.url(url)
|
||||
.build();
|
||||
@@ -188,7 +180,9 @@ impl ServoHost for SoftwareServoHost {
|
||||
.get(&request.webview_id)
|
||||
.ok_or_else(|| ServoHostError::WebViewNotFound { id: request.webview_id.clone() })?;
|
||||
|
||||
webview.webview.resize(PhysicalSize::new(request.width, request.height));
|
||||
let size = PhysicalSize::new(request.width, request.height);
|
||||
webview.rendering_context.resize(size);
|
||||
webview.webview.resize(size);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -202,6 +196,13 @@ impl ServoHost for SoftwareServoHost {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hover(&mut self, x: u32, y: u32) -> Result<(), ServoHostError> {
|
||||
if let Some(webview) = self.webviews.values().next() {
|
||||
send_mouse_hover(&webview.webview, x, y);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn click(&mut self, request: MouseClickRequest) -> Result<(), ServoHostError> {
|
||||
let webview = self
|
||||
.webviews
|
||||
@@ -319,16 +320,15 @@ impl ServoHost for SoftwareServoHost {
|
||||
}
|
||||
|
||||
fn paint(&mut self, webview_id: &WebViewId) -> Result<(), ServoHostError> {
|
||||
self.rendering_context
|
||||
.make_current()
|
||||
.map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
self.rendering_context.prepare_for_rendering();
|
||||
let rendering_context = self.webview(webview_id)?.rendering_context.clone();
|
||||
rendering_context.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
rendering_context.prepare_for_rendering();
|
||||
{
|
||||
let webview = self.webview(webview_id)?;
|
||||
webview.webview.paint();
|
||||
}
|
||||
let rendered_frame = self.read_rendered_frame()?;
|
||||
self.rendering_context.present();
|
||||
let rendered_frame = Self::read_rendered_frame(rendering_context.as_ref())?;
|
||||
rendering_context.present();
|
||||
self.webview(webview_id)?.delegate.mark_frame_presented();
|
||||
self.last_rendered_frame = Some(rendered_frame);
|
||||
Ok(())
|
||||
@@ -340,14 +340,56 @@ impl ServoHost for SoftwareServoHost {
|
||||
}
|
||||
|
||||
impl SoftwareServoHost {
|
||||
fn create_webview_in_context(
|
||||
&mut self,
|
||||
tab_id: TabId,
|
||||
profile_id: ProfileId,
|
||||
size: ServoSurfaceSize,
|
||||
) -> Result<WebViewId, ServoHostError> {
|
||||
let webview_id = WebViewId::new();
|
||||
let rendering_context = Self::new_rendering_context(size)?;
|
||||
let delegate =
|
||||
Rc::new(HostWebViewDelegate::new(profile_id.clone(), self.permissions.clone()));
|
||||
let webview = WebViewBuilder::new(&self.servo, rendering_context.clone())
|
||||
.delegate(delegate.clone())
|
||||
.build();
|
||||
|
||||
self.webviews.insert(
|
||||
webview_id.clone(),
|
||||
HostWebView {
|
||||
tab_id,
|
||||
profile_id,
|
||||
rendering_context,
|
||||
webview,
|
||||
delegate,
|
||||
requested_url: None,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(webview_id)
|
||||
}
|
||||
|
||||
fn new_rendering_context(
|
||||
size: ServoSurfaceSize,
|
||||
) -> Result<Rc<dyn RenderingContext>, ServoHostError> {
|
||||
let rendering_context = Rc::new(
|
||||
servo::SoftwareRenderingContext::new(size.physical())
|
||||
.map_err(|_| ServoHostError::RenderingContextUnavailable)?,
|
||||
);
|
||||
rendering_context.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
Ok(rendering_context)
|
||||
}
|
||||
|
||||
fn webview(&self, webview_id: &WebViewId) -> Result<&HostWebView, ServoHostError> {
|
||||
self.webviews
|
||||
.get(webview_id)
|
||||
.ok_or_else(|| ServoHostError::WebViewNotFound { id: webview_id.clone() })
|
||||
}
|
||||
|
||||
fn read_rendered_frame(&self) -> Result<RenderedFrame, ServoHostError> {
|
||||
let size = self.rendering_context.size();
|
||||
fn read_rendered_frame(
|
||||
rendering_context: &dyn RenderingContext,
|
||||
) -> Result<RenderedFrame, ServoHostError> {
|
||||
let size = rendering_context.size();
|
||||
let width =
|
||||
i32::try_from(size.width).map_err(|_| ServoHostError::RenderedFrameUnavailable)?;
|
||||
let height =
|
||||
@@ -356,8 +398,7 @@ impl SoftwareServoHost {
|
||||
DeviceIntPoint::new(0, 0),
|
||||
DeviceIntSize::new(width, height),
|
||||
);
|
||||
let image = self
|
||||
.rendering_context
|
||||
let image = rendering_context
|
||||
.read_to_image(frame_rect)
|
||||
.ok_or(ServoHostError::RenderedFrameUnavailable)?;
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ use servo::{
|
||||
|
||||
use crate::keyboard::key_and_code_for_character;
|
||||
|
||||
pub(super) fn send_mouse_hover(webview: &WebView, x: u32, y: u32) {
|
||||
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point(x, y))));
|
||||
}
|
||||
|
||||
pub(super) fn send_mouse_click(webview: &WebView, x: u32, y: u32) {
|
||||
let point = point(x, y);
|
||||
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
|
||||
|
||||
Reference in New Issue
Block a user