refactor(sidecar): split live session state
This commit is contained in:
@@ -23,6 +23,8 @@ mod live;
|
|||||||
mod live_output;
|
mod live_output;
|
||||||
#[path = "ely_servo_sidecar/live_protocol.rs"]
|
#[path = "ely_servo_sidecar/live_protocol.rs"]
|
||||||
mod live_protocol;
|
mod live_protocol;
|
||||||
|
#[path = "ely_servo_sidecar/live_session.rs"]
|
||||||
|
mod live_session;
|
||||||
#[path = "ely_servo_sidecar/perf.rs"]
|
#[path = "ely_servo_sidecar/perf.rs"]
|
||||||
mod perf;
|
mod perf;
|
||||||
#[path = "ely_servo_sidecar/report.rs"]
|
#[path = "ely_servo_sidecar/report.rs"]
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId, UrlText};
|
use ely_domain::{ProfileId, TabId, UrlText};
|
||||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||||
use ely_servo_host::WebViewState;
|
use ely_servo_host::WebViewState;
|
||||||
use ely_servo_host::{
|
use ely_servo_host::{
|
||||||
IOSurfaceIdentity, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
|
IOSurfaceIdentity, NavigationRequest, RenderingContextKind, ServoHost, ServoSurfaceSize,
|
||||||
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
|
|
||||||
RenderingContextKind, ResizeRequest, ScrollRequest, ServoHost, ServoSurfaceSize,
|
|
||||||
SoftwareServoHost,
|
SoftwareServoHost,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,8 +18,9 @@ use super::args::LiveArgs;
|
|||||||
use super::iosurface_mach::{IOSurfaceMachSender, send_surface_port_if_needed};
|
use super::iosurface_mach::{IOSurfaceMachSender, send_surface_port_if_needed};
|
||||||
use super::live_output::{populate_surface_fields, write_outcome};
|
use super::live_output::{populate_surface_fields, write_outcome};
|
||||||
pub(super) use super::live_protocol::LiveSidecarError;
|
pub(super) use super::live_protocol::LiveSidecarError;
|
||||||
use super::live_protocol::{
|
use super::live_protocol::{LiveFrameReport, LiveOutcome, LiveRequest, PartialFrameTimings};
|
||||||
LiveFrameReport, LiveOutcome, LiveRequest, LiveSitePermission, PartialFrameTimings,
|
use super::live_session::{
|
||||||
|
LiveInput, LiveSession, apply_input, apply_layout, apply_permissions, ensure_session,
|
||||||
};
|
};
|
||||||
use super::perf::{FramePerfAggregator, FramePerfSummary, elapsed_ns};
|
use super::perf::{FramePerfAggregator, FramePerfSummary, elapsed_ns};
|
||||||
|
|
||||||
@@ -195,167 +194,6 @@ fn handle_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
device_pixel_ratio: f32,
|
|
||||||
) -> Result<bool, LiveSidecarError> {
|
|
||||||
let mut changed = false;
|
|
||||||
// Push the device pixel ratio BEFORE resize. Servo's WebView
|
|
||||||
// defaults hidpi to 1.0; without this the first layout treats
|
|
||||||
// physical-pixel viewport widths as CSS-pixel widths and the page
|
|
||||||
// lays out half the size you'd expect on a Retina display. The
|
|
||||||
// sidecar-side `LiveSession::hidpi_scale_milli` keeps a u32 of
|
|
||||||
// (scale × 1000) so f32 jitter from JSON parsing doesn't churn
|
|
||||||
// the setter every frame.
|
|
||||||
let hidpi_scale_milli = encode_hidpi_scale_milli(device_pixel_ratio);
|
|
||||||
if session.hidpi_scale_milli != hidpi_scale_milli {
|
|
||||||
host.set_hidpi_scale(ely_servo_host::HidpiScaleRequest {
|
|
||||||
webview_id: session.webview_id.clone(),
|
|
||||||
scale_factor: hidpi_scale_milli_to_f32(hidpi_scale_milli),
|
|
||||||
})?;
|
|
||||||
session.hidpi_scale_milli = hidpi_scale_milli;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 encode_hidpi_scale_milli(scale: f32) -> u32 {
|
|
||||||
if !scale.is_finite() || scale <= 0.0 {
|
|
||||||
return 1_000;
|
|
||||||
}
|
|
||||||
let scaled = (scale * 1_000.0).round();
|
|
||||||
scaled.clamp(500.0, 5_000.0) as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hidpi_scale_milli_to_f32(milli: u32) -> f32 {
|
|
||||||
milli as f32 / 1_000.0
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
input: LiveInput,
|
|
||||||
) -> Result<bool, LiveSidecarError> {
|
|
||||||
let mut changed = false;
|
|
||||||
if input.scroll_delta_x != 0 || input.scroll_delta_y != 0 {
|
|
||||||
let (point_x, point_y) = input.scroll_point()?;
|
|
||||||
host.scroll(ScrollRequest {
|
|
||||||
webview_id: session.webview_id.clone(),
|
|
||||||
delta_x: input.scroll_delta_x,
|
|
||||||
delta_y: input.scroll_delta_y,
|
|
||||||
point_x,
|
|
||||||
point_y,
|
|
||||||
})?;
|
|
||||||
session.scroll_x = positive_scroll_component(session.scroll_x, input.scroll_delta_x);
|
|
||||||
session.scroll_y = positive_scroll_component(session.scroll_y, input.scroll_delta_y);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(x), Some(y)) = (input.hover_x, input.hover_y) {
|
|
||||||
host.hover(MouseHoverRequest { webview_id: session.webview_id.clone(), x, y })?;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(x), Some(y)) = (input.click_x, input.click_y) {
|
|
||||||
host.click(MouseClickRequest { webview_id: session.webview_id.clone(), x, y })?;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text) = input.typed_text {
|
|
||||||
host.type_text(KeyboardTextRequest { webview_id: session.webview_id.clone(), text })?;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(changed)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LiveInput {
|
|
||||||
scroll_delta_x: i32,
|
|
||||||
scroll_delta_y: i32,
|
|
||||||
scroll_point_x: Option<u32>,
|
|
||||||
scroll_point_y: Option<u32>,
|
|
||||||
click_x: Option<u32>,
|
|
||||||
click_y: Option<u32>,
|
|
||||||
hover_x: Option<u32>,
|
|
||||||
hover_y: Option<u32>,
|
|
||||||
typed_text: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LiveInput {
|
|
||||||
fn scroll_point(&self) -> Result<(u32, u32), LiveSidecarError> {
|
|
||||||
let point = match (self.scroll_point_x, self.scroll_point_y) {
|
|
||||||
(Some(x), Some(y)) => (x, y),
|
|
||||||
_ => return Err(LiveSidecarError::IncompleteScrollPoint),
|
|
||||||
};
|
|
||||||
Ok(point)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_frame(
|
fn poll_frame(
|
||||||
host: &mut SoftwareServoHost,
|
host: &mut SoftwareServoHost,
|
||||||
session: &mut LiveSession,
|
session: &mut LiveSession,
|
||||||
@@ -457,77 +295,10 @@ fn hardware_snapshot_has_visible_document(snapshot: &ely_servo_host::WebViewSnap
|
|||||||
snapshot.title().is_some() || matches!(snapshot.state(), WebViewState::Complete)
|
snapshot.title().is_some() || matches!(snapshot.state(), WebViewState::Complete)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct LiveSession {
|
|
||||||
webview_id: ely_domain::WebViewId,
|
|
||||||
requested_url: String,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
page_zoom_percent: u16,
|
|
||||||
/// Last hidpi factor pushed to Servo, encoded as `(scale × 1000)`.
|
|
||||||
/// Stored as a u32 so equality is cheap and stable across the
|
|
||||||
/// f32 jitter that JSON parsing can introduce. Init to 0 so the
|
|
||||||
/// first apply_layout call always pushes a real value.
|
|
||||||
hidpi_scale_milli: u32,
|
|
||||||
scroll_x: i32,
|
|
||||||
scroll_y: i32,
|
|
||||||
awaiting_visible_frame: bool,
|
|
||||||
/// Sticky for the lifetime of a single URL: flipped to `true`
|
|
||||||
/// the first time `poll_frame` sees a paint with real content
|
|
||||||
/// (non-white, non-empty) and reset to `false` on every navigate.
|
|
||||||
/// After it's `true`, the visible-content gate stops gating —
|
|
||||||
/// scroll/click/hover/type all return on the first
|
|
||||||
/// `has_pending_frame=true` (~3 ms) instead of waiting the full
|
|
||||||
/// `LIVE_FRAME_WAIT_TIMEOUT`. The gate stays armed for the
|
|
||||||
/// initial paint of each new URL so loading frames are still
|
|
||||||
/// skipped.
|
|
||||||
ever_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: 0,
|
|
||||||
height: 0,
|
|
||||||
page_zoom_percent: DEFAULT_ZOOM_PERCENT,
|
|
||||||
hidpi_scale_milli: 0,
|
|
||||||
scroll_x: 0,
|
|
||||||
scroll_y: 0,
|
|
||||||
awaiting_visible_frame: false,
|
|
||||||
ever_visible_frame: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_pixel_ratio(&self) -> f32 {
|
|
||||||
hidpi_scale_milli_to_f32(self.hidpi_scale_milli)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_live_session_forces_first_resize_after_hidpi() {
|
|
||||||
let session = LiveSession::new(ely_domain::WebViewId::new(), 1280, 720);
|
|
||||||
|
|
||||||
assert_ne!(
|
|
||||||
session.width, 1280,
|
|
||||||
"first apply_layout must resize after hidpi has been pushed",
|
|
||||||
);
|
|
||||||
assert_ne!(
|
|
||||||
session.height, 720,
|
|
||||||
"first apply_layout must resize after hidpi has been pushed",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn awaiting_visible_frame_forces_paint_without_pending_flag() {
|
fn awaiting_visible_frame_forces_paint_without_pending_flag() {
|
||||||
assert!(should_paint_live_frame(false, true));
|
assert!(should_paint_live_frame(false, true));
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId};
|
||||||
|
use ely_servo_host::{
|
||||||
|
KeyboardTextRequest, MouseClickRequest, MouseHoverRequest, PageZoomRequest, PermissionDecision,
|
||||||
|
PermissionRequest, ResizeRequest, ScrollRequest, ServoHost, ServoSurfaceSize,
|
||||||
|
SoftwareServoHost,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::live_protocol::{LiveSidecarError, LiveSitePermission};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct LiveSession {
|
||||||
|
pub(super) webview_id: ely_domain::WebViewId,
|
||||||
|
pub(super) requested_url: String,
|
||||||
|
pub(super) width: u32,
|
||||||
|
pub(super) height: u32,
|
||||||
|
page_zoom_percent: u16,
|
||||||
|
/// Last hidpi factor pushed to Servo, encoded as `(scale × 1000)`.
|
||||||
|
/// Stored as a u32 so equality is cheap and stable across the
|
||||||
|
/// f32 jitter that JSON parsing can introduce. Init to 0 so the
|
||||||
|
/// first apply_layout call always pushes a real value.
|
||||||
|
hidpi_scale_milli: u32,
|
||||||
|
pub(super) scroll_x: i32,
|
||||||
|
pub(super) scroll_y: i32,
|
||||||
|
pub(super) awaiting_visible_frame: bool,
|
||||||
|
/// Sticky for the lifetime of a single URL: flipped to `true`
|
||||||
|
/// the first time `poll_frame` sees a paint with real content
|
||||||
|
/// (non-white, non-empty) and reset to `false` on every navigate.
|
||||||
|
/// After it's `true`, the visible-content gate stops gating:
|
||||||
|
/// scroll/click/hover/type all return on the first
|
||||||
|
/// `has_pending_frame=true` (~3 ms) instead of waiting the full
|
||||||
|
/// `LIVE_FRAME_WAIT_TIMEOUT`. The gate stays armed for the
|
||||||
|
/// initial paint of each new URL so loading frames are still
|
||||||
|
/// skipped.
|
||||||
|
pub(super) ever_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: 0,
|
||||||
|
height: 0,
|
||||||
|
page_zoom_percent: DEFAULT_ZOOM_PERCENT,
|
||||||
|
hidpi_scale_milli: 0,
|
||||||
|
scroll_x: 0,
|
||||||
|
scroll_y: 0,
|
||||||
|
awaiting_visible_frame: false,
|
||||||
|
ever_visible_frame: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn device_pixel_ratio(&self) -> f32 {
|
||||||
|
hidpi_scale_milli_to_f32(self.hidpi_scale_milli)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn apply_layout(
|
||||||
|
host: &mut SoftwareServoHost,
|
||||||
|
session: &mut LiveSession,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
page_zoom_percent: u16,
|
||||||
|
device_pixel_ratio: f32,
|
||||||
|
) -> Result<bool, LiveSidecarError> {
|
||||||
|
let mut changed = false;
|
||||||
|
// Push the device pixel ratio BEFORE resize. Servo's WebView
|
||||||
|
// defaults hidpi to 1.0; without this the first layout treats
|
||||||
|
// physical-pixel viewport widths as CSS-pixel widths and the page
|
||||||
|
// lays out half the size you'd expect on a Retina display.
|
||||||
|
let hidpi_scale_milli = encode_hidpi_scale_milli(device_pixel_ratio);
|
||||||
|
if session.hidpi_scale_milli != hidpi_scale_milli {
|
||||||
|
host.set_hidpi_scale(ely_servo_host::HidpiScaleRequest {
|
||||||
|
webview_id: session.webview_id.clone(),
|
||||||
|
scale_factor: hidpi_scale_milli_to_f32(hidpi_scale_milli),
|
||||||
|
})?;
|
||||||
|
session.hidpi_scale_milli = hidpi_scale_milli;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) 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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn apply_input(
|
||||||
|
host: &mut SoftwareServoHost,
|
||||||
|
session: &mut LiveSession,
|
||||||
|
input: LiveInput,
|
||||||
|
) -> Result<bool, LiveSidecarError> {
|
||||||
|
let mut changed = false;
|
||||||
|
if input.scroll_delta_x != 0 || input.scroll_delta_y != 0 {
|
||||||
|
let (point_x, point_y) = input.scroll_point()?;
|
||||||
|
host.scroll(ScrollRequest {
|
||||||
|
webview_id: session.webview_id.clone(),
|
||||||
|
delta_x: input.scroll_delta_x,
|
||||||
|
delta_y: input.scroll_delta_y,
|
||||||
|
point_x,
|
||||||
|
point_y,
|
||||||
|
})?;
|
||||||
|
session.scroll_x = positive_scroll_component(session.scroll_x, input.scroll_delta_x);
|
||||||
|
session.scroll_y = positive_scroll_component(session.scroll_y, input.scroll_delta_y);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(x), Some(y)) = (input.hover_x, input.hover_y) {
|
||||||
|
host.hover(MouseHoverRequest { webview_id: session.webview_id.clone(), x, y })?;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(x), Some(y)) = (input.click_x, input.click_y) {
|
||||||
|
host.click(MouseClickRequest { webview_id: session.webview_id.clone(), x, y })?;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text) = input.typed_text {
|
||||||
|
host.type_text(KeyboardTextRequest { webview_id: session.webview_id.clone(), text })?;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LiveInput {
|
||||||
|
pub(super) scroll_delta_x: i32,
|
||||||
|
pub(super) scroll_delta_y: i32,
|
||||||
|
pub(super) scroll_point_x: Option<u32>,
|
||||||
|
pub(super) scroll_point_y: Option<u32>,
|
||||||
|
pub(super) click_x: Option<u32>,
|
||||||
|
pub(super) click_y: Option<u32>,
|
||||||
|
pub(super) hover_x: Option<u32>,
|
||||||
|
pub(super) hover_y: Option<u32>,
|
||||||
|
pub(super) typed_text: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveInput {
|
||||||
|
fn scroll_point(&self) -> Result<(u32, u32), LiveSidecarError> {
|
||||||
|
let point = match (self.scroll_point_x, self.scroll_point_y) {
|
||||||
|
(Some(x), Some(y)) => (x, y),
|
||||||
|
_ => return Err(LiveSidecarError::IncompleteScrollPoint),
|
||||||
|
};
|
||||||
|
Ok(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_hidpi_scale_milli(scale: f32) -> u32 {
|
||||||
|
if !scale.is_finite() || scale <= 0.0 {
|
||||||
|
return 1_000;
|
||||||
|
}
|
||||||
|
let scaled = (scale * 1_000.0).round();
|
||||||
|
scaled.clamp(500.0, 5_000.0) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hidpi_scale_milli_to_f32(milli: u32) -> f32 {
|
||||||
|
milli as f32 / 1_000.0
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_live_session_forces_first_resize_after_hidpi() {
|
||||||
|
let session = LiveSession::new(ely_domain::WebViewId::new(), 1280, 720);
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
session.width, 1280,
|
||||||
|
"first apply_layout must resize after hidpi has been pushed",
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
session.height, 720,
|
||||||
|
"first apply_layout must resize after hidpi has been pushed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user