Route web surface scrolls to Servo hit point
This commit is contained in:
@@ -150,6 +150,8 @@ fn apply_scroll_if_requested(
|
||||
webview_id: webview_id.clone(),
|
||||
delta_x: args.scroll_x,
|
||||
delta_y: args.scroll_y,
|
||||
point_x: 0,
|
||||
point_y: 0,
|
||||
})?;
|
||||
wait_for_changed_or_settled_frame(host, webview_id, previous_frame_hash)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ use ely_servo_host::{
|
||||
};
|
||||
|
||||
use super::args::LiveArgs;
|
||||
pub(super) use super::live_protocol::LiveSidecarError;
|
||||
use super::live_protocol::{
|
||||
LiveFrameReport, LiveOutcome, LiveRequest, LiveSitePermission, PartialFrameTimings,
|
||||
};
|
||||
pub(super) use super::live_protocol::LiveSidecarError;
|
||||
use super::perf::{FramePerfAggregator, FramePerfSummary, FrameStageTimings, elapsed_ns};
|
||||
|
||||
/// Per-`Ensure` budget for Servo to paint after input dispatch.
|
||||
@@ -65,13 +65,7 @@ pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
),
|
||||
Err(error) => Err(LiveSidecarError::Json(error)),
|
||||
};
|
||||
write_outcome(
|
||||
&mut stdout,
|
||||
&mut perf,
|
||||
&mut pending_summary,
|
||||
outcome,
|
||||
frame_started_at,
|
||||
)?;
|
||||
write_outcome(&mut stdout, &mut perf, &mut pending_summary, outcome, frame_started_at)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -102,6 +96,8 @@ fn handle_request(
|
||||
device_pixel_ratio,
|
||||
scroll_delta_x,
|
||||
scroll_delta_y,
|
||||
scroll_point_x,
|
||||
scroll_point_y,
|
||||
click_x,
|
||||
click_y,
|
||||
hover_x,
|
||||
@@ -115,14 +111,7 @@ fn handle_request(
|
||||
let session =
|
||||
ensure_session(host, sessions, tab_id.clone(), &tab, &profile, width, height)?;
|
||||
|
||||
if apply_layout(
|
||||
host,
|
||||
session,
|
||||
width,
|
||||
height,
|
||||
page_zoom_percent,
|
||||
device_pixel_ratio,
|
||||
)? {
|
||||
if apply_layout(host, session, width, height, page_zoom_percent, device_pixel_ratio)? {
|
||||
session.awaiting_visible_frame = true;
|
||||
}
|
||||
apply_permissions(host, session, &profile, site_permissions)?;
|
||||
@@ -141,17 +130,18 @@ fn handle_request(
|
||||
// the gate skip blank loading frames again.
|
||||
session.ever_visible_frame = false;
|
||||
}
|
||||
if apply_input(
|
||||
host,
|
||||
session,
|
||||
let input = LiveInput {
|
||||
scroll_delta_x,
|
||||
scroll_delta_y,
|
||||
scroll_point_x,
|
||||
scroll_point_y,
|
||||
click_x,
|
||||
click_y,
|
||||
hover_x,
|
||||
hover_y,
|
||||
typed_text,
|
||||
)? {
|
||||
};
|
||||
if apply_input(host, session, input)? {
|
||||
// Tell poll_frame to actually wait for Servo to paint
|
||||
// a response to this input. The visible-content gate
|
||||
// is bypassed on the hardware path inside poll_frame,
|
||||
@@ -162,7 +152,13 @@ fn handle_request(
|
||||
}
|
||||
let webview_id = session.webview_id.clone();
|
||||
let mut outcome = poll_frame(host, session, rendering_context_kind)?;
|
||||
populate_surface_fields(host, &webview_id, &tab_id, published_surface_ids, &mut outcome);
|
||||
populate_surface_fields(
|
||||
host,
|
||||
&webview_id,
|
||||
&tab_id,
|
||||
published_surface_ids,
|
||||
&mut outcome,
|
||||
);
|
||||
Ok(outcome)
|
||||
}
|
||||
LiveRequest::Poll { tab_id } => {
|
||||
@@ -171,7 +167,13 @@ fn handle_request(
|
||||
};
|
||||
let webview_id = session.webview_id.clone();
|
||||
let mut outcome = poll_frame(host, session, rendering_context_kind)?;
|
||||
populate_surface_fields(host, &webview_id, &tab_id, published_surface_ids, &mut outcome);
|
||||
populate_surface_fields(
|
||||
host,
|
||||
&webview_id,
|
||||
&tab_id,
|
||||
published_surface_ids,
|
||||
&mut outcome,
|
||||
);
|
||||
Ok(outcome)
|
||||
}
|
||||
}
|
||||
@@ -255,18 +257,18 @@ fn write_outcome(
|
||||
// header so the client knows nothing follows). At 1080p × 60 fps
|
||||
// that's 8 MB × 60 = ~480 MB/s of pipe traffic eliminated.
|
||||
let drop_rgba_payload = outcome.response.current_surface_id.is_some();
|
||||
if drop_rgba_payload {
|
||||
if let Some(report) = outcome.response.frame.as_mut() {
|
||||
report.rgba_byte_count = 0;
|
||||
}
|
||||
if drop_rgba_payload
|
||||
&& let Some(report) = outcome.response.frame.as_mut()
|
||||
{
|
||||
report.rgba_byte_count = 0;
|
||||
}
|
||||
let write_started_at = Instant::now();
|
||||
serde_json::to_writer(&mut *stdout, &outcome.response)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
if !drop_rgba_payload {
|
||||
if let Some(frame) = outcome.frame.as_ref() {
|
||||
stdout.write_all(frame.rgba_bytes())?;
|
||||
}
|
||||
if !drop_rgba_payload
|
||||
&& let Some(frame) = outcome.frame.as_ref()
|
||||
{
|
||||
stdout.write_all(frame.rgba_bytes())?;
|
||||
}
|
||||
stdout.flush()?;
|
||||
if frame_present {
|
||||
@@ -390,37 +392,34 @@ fn apply_permissions(
|
||||
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>,
|
||||
input: LiveInput,
|
||||
) -> Result<bool, LiveSidecarError> {
|
||||
let mut changed = false;
|
||||
if scroll_delta_x != 0 || scroll_delta_y != 0 {
|
||||
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: scroll_delta_x,
|
||||
delta_y: scroll_delta_y,
|
||||
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, scroll_delta_x);
|
||||
session.scroll_y = positive_scroll_component(session.scroll_y, scroll_delta_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)) = (hover_x, hover_y) {
|
||||
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)) = (click_x, click_y) {
|
||||
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) = typed_text {
|
||||
if let Some(text) = input.typed_text {
|
||||
host.type_text(KeyboardTextRequest { webview_id: session.webview_id.clone(), text })?;
|
||||
changed = true;
|
||||
}
|
||||
@@ -428,6 +427,28 @@ fn apply_input(
|
||||
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(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &mut LiveSession,
|
||||
@@ -469,8 +490,7 @@ fn poll_frame(
|
||||
let has_visible_content = match rendering_context_kind {
|
||||
RenderingContextKind::Software => {
|
||||
session.ever_visible_frame
|
||||
|| (frame.non_white_pixel_count() > 0
|
||||
&& frame.content_pixel_count() > 0)
|
||||
|| (frame.non_white_pixel_count() > 0 && frame.content_pixel_count() > 0)
|
||||
}
|
||||
#[cfg(feature = "hardware-render")]
|
||||
RenderingContextKind::Hardware => true,
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
use std::io;
|
||||
|
||||
use ely_servo_host::{IOSurfaceHandle, RenderedFrame, ServoHostError, WebViewSnapshot, WebViewState};
|
||||
use ely_servo_host::{
|
||||
IOSurfaceHandle, RenderedFrame, ServoHostError, WebViewSnapshot, WebViewState,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -29,6 +31,8 @@ pub(super) enum LiveRequest {
|
||||
device_pixel_ratio: f32,
|
||||
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>,
|
||||
#[serde(default)]
|
||||
@@ -195,6 +199,9 @@ pub(super) enum LiveSidecarError {
|
||||
#[error("live session is unavailable after creation")]
|
||||
SessionUnavailable,
|
||||
|
||||
#[error("scroll input requires both scroll_point_x and scroll_point_y")]
|
||||
IncompleteScrollPoint,
|
||||
|
||||
#[error(transparent)]
|
||||
Domain(#[from] ely_domain::DomainError),
|
||||
|
||||
|
||||
@@ -262,17 +262,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregator_emits_summary_after_window_size_records() {
|
||||
fn aggregator_emits_summary_after_window_size_records() -> Result<(), &'static str> {
|
||||
let mut aggregator =
|
||||
FramePerfAggregator::new("software", FramePerfAggregator::DEFAULT_WINDOW_SIZE);
|
||||
for index in 0..(FramePerfAggregator::DEFAULT_WINDOW_SIZE - 1) {
|
||||
let result = aggregator.record(constant_timing());
|
||||
assert!(result.is_none(), "should not flush at frame {index}");
|
||||
}
|
||||
let summary = aggregator.record(constant_timing());
|
||||
let summary = summary.expect("aggregator must flush at window boundary");
|
||||
let summary = aggregator
|
||||
.record(constant_timing())
|
||||
.ok_or("aggregator must flush at window boundary")?;
|
||||
assert_eq!(summary.window, FramePerfAggregator::DEFAULT_WINDOW_SIZE);
|
||||
assert_eq!(summary.context, "software");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -286,7 +288,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregator_percentiles_track_increasing_paint_durations() {
|
||||
fn aggregator_percentiles_track_increasing_paint_durations() -> Result<(), &'static str> {
|
||||
let mut aggregator = FramePerfAggregator::new("software", 4);
|
||||
let paint_durations_us = [10u64, 100, 1_000, 10_000];
|
||||
let mut summary = None;
|
||||
@@ -298,11 +300,12 @@ mod tests {
|
||||
Duration::from_micros(paint_us + 2),
|
||||
));
|
||||
}
|
||||
let summary = summary.expect("4-frame window must flush");
|
||||
let summary = summary.ok_or("4-frame window must flush")?;
|
||||
assert!(
|
||||
summary.paint_p50_us < summary.paint_p99_us,
|
||||
"p99 must dominate p50 for increasing samples: {summary:?}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn constant_timing() -> FrameStageTimings {
|
||||
|
||||
@@ -220,6 +220,8 @@ pub struct ScrollRequest {
|
||||
pub webview_id: WebViewId,
|
||||
pub delta_x: i32,
|
||||
pub delta_y: i32,
|
||||
pub point_x: u32,
|
||||
pub point_y: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -16,17 +16,15 @@ use dpi::PhysicalSize;
|
||||
use ely_domain::{ProfileId, TabId, WebViewId};
|
||||
use euclid::Scale;
|
||||
use servo::{
|
||||
DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel,
|
||||
DevicePoint, DeviceVector2D, Opts, RenderingContext, Scroll, Servo, ServoBuilder,
|
||||
WebViewBuilder, WebViewPoint, WebViewVector,
|
||||
DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint,
|
||||
DeviceVector2D, Opts, RenderingContext, Scroll, Servo, ServoBuilder, WebViewBuilder,
|
||||
WebViewPoint, WebViewVector,
|
||||
};
|
||||
|
||||
/// Wrap an `f32` scale factor in Servo's typed `Scale<f32, DeviceIndependentPixel,
|
||||
/// DevicePixel>`. The clamp guards against `NaN`/`inf` reaching Servo's
|
||||
/// layout (which assumes a positive finite scale).
|
||||
fn hidpi_scale_from_factor(
|
||||
scale_factor: f32,
|
||||
) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||
fn hidpi_scale_from_factor(scale_factor: f32) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
|
||||
let safe = if scale_factor.is_finite() && scale_factor > 0.0 {
|
||||
scale_factor.clamp(0.5, 5.0)
|
||||
} else {
|
||||
@@ -37,10 +35,10 @@ fn hidpi_scale_from_factor(
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest,
|
||||
MouseHoverRequest, NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
|
||||
RenderedFrame, ResizeRequest, ScreenshotRequest, ScrollRequest, ServoHost, ServoHostError,
|
||||
TouchTapRequest, WebViewSnapshot, WebViewState,
|
||||
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest, MouseHoverRequest,
|
||||
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_mouse_hover, send_touch_tap,
|
||||
},
|
||||
@@ -265,7 +263,7 @@ impl ServoHost for SoftwareServoHost {
|
||||
request.delta_x as f32,
|
||||
request.delta_y as f32,
|
||||
))),
|
||||
WebViewPoint::Device(DevicePoint::zero()),
|
||||
WebViewPoint::Device(DevicePoint::new(request.point_x as f32, request.point_y as f32)),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -544,9 +542,7 @@ impl SoftwareServoHost {
|
||||
crate::HardwareOffscreenContext::new(size.physical())
|
||||
.map_err(|_| ServoHostError::RenderingContextUnavailable)?,
|
||||
);
|
||||
hardware
|
||||
.make_current()
|
||||
.map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
hardware.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
Ok(RenderingContextHandles {
|
||||
rendering_context: hardware.clone(),
|
||||
hardware_context: Some(hardware),
|
||||
@@ -576,10 +572,7 @@ impl SoftwareServoHost {
|
||||
/// Re-asserting per dispatch keeps the invariant on the dispatch
|
||||
/// path instead of spread across creation, navigation, and
|
||||
/// tab-switching.
|
||||
fn webview_for_input(
|
||||
&self,
|
||||
webview_id: &WebViewId,
|
||||
) -> Result<&HostWebView, ServoHostError> {
|
||||
fn webview_for_input(&self, webview_id: &WebViewId) -> Result<&HostWebView, ServoHostError> {
|
||||
let webview = self.webview(webview_id)?;
|
||||
webview.webview.show();
|
||||
webview.webview.focus();
|
||||
|
||||
Reference in New Issue
Block a user