fix(web-surface): bound live input paints
This commit is contained in:
@@ -125,8 +125,8 @@ fn assert_web_surface_scrolls_prd_site() -> Result<(), Box<dyn Error>> {
|
|||||||
let profile_id = ProfileId::new();
|
let profile_id = ProfileId::new();
|
||||||
let case = PRD_TOP_SITE_CASES
|
let case = PRD_TOP_SITE_CASES
|
||||||
.iter()
|
.iter()
|
||||||
.find(|case| case.url == "https://servo.org/")
|
.find(|case| case.url == "https://github.com")
|
||||||
.ok_or("missing servo.org live-site case")?;
|
.ok_or("missing github.com live-site case")?;
|
||||||
let tab = web_tab(profile_id, case.url)?;
|
let tab = web_tab(profile_id, case.url)?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -299,19 +299,14 @@ fn wait_for_ready_frame(
|
|||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
|
||||||
return Err(last_error.unwrap_or_else(|| format!("timed out rendering {}", case.url)));
|
|
||||||
}
|
|
||||||
|
|
||||||
store.tick(std::slice::from_ref(tab_id));
|
store.tick(std::slice::from_ref(tab_id));
|
||||||
match store.state(tab_id) {
|
match store.state(tab_id) {
|
||||||
Some(WebSurfaceState::Ready(frame)) => {
|
Some(WebSurfaceState::Ready(frame)) => {
|
||||||
if let Err(error) = validate_prd_frame(frame, case, 0) {
|
if let Err(error) = validate_prd_frame(frame, case, 0) {
|
||||||
last_error = Some(error);
|
last_error = Some(error);
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
} else {
|
||||||
continue;
|
return Ok(frame.clone());
|
||||||
}
|
}
|
||||||
return Ok(frame.clone());
|
|
||||||
}
|
}
|
||||||
Some(WebSurfaceState::Failed { message, .. }) => {
|
Some(WebSurfaceState::Failed { message, .. }) => {
|
||||||
return Err(format!("{} failed: {message}", case.url));
|
return Err(format!("{} failed: {message}", case.url));
|
||||||
@@ -319,6 +314,10 @@ fn wait_for_ready_frame(
|
|||||||
Some(WebSurfaceState::Loading { .. }) | None => {}
|
Some(WebSurfaceState::Loading { .. }) | None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
||||||
|
return Err(last_error.unwrap_or_else(|| format!("timed out rendering {}", case.url)));
|
||||||
|
}
|
||||||
|
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,12 +333,6 @@ fn wait_for_ready_frame_at_scroll(
|
|||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
|
||||||
return Err(last_error.unwrap_or_else(|| {
|
|
||||||
format!("timed out rendering {} at scroll y={expected_scroll_y}", case.url)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
store.tick(std::slice::from_ref(tab_id));
|
store.tick(std::slice::from_ref(tab_id));
|
||||||
match store.state(tab_id) {
|
match store.state(tab_id) {
|
||||||
Some(WebSurfaceState::Ready(frame))
|
Some(WebSurfaceState::Ready(frame))
|
||||||
@@ -347,10 +340,7 @@ fn wait_for_ready_frame_at_scroll(
|
|||||||
{
|
{
|
||||||
if let Err(error) = validate_prd_frame(frame, case, expected_scroll_y) {
|
if let Err(error) = validate_prd_frame(frame, case, expected_scroll_y) {
|
||||||
last_error = Some(error);
|
last_error = Some(error);
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
} else if !frame.has_hardware_surface()
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !frame.has_hardware_surface()
|
|
||||||
&& let Some(previous_sample_hash) = previous_sample_hash
|
&& let Some(previous_sample_hash) = previous_sample_hash
|
||||||
&& frame.sample_hash() == previous_sample_hash
|
&& frame.sample_hash() == previous_sample_hash
|
||||||
{
|
{
|
||||||
@@ -358,18 +348,29 @@ fn wait_for_ready_frame_at_scroll(
|
|||||||
"{} scroll y={expected_scroll_y} sample hash unchanged",
|
"{} scroll y={expected_scroll_y} sample hash unchanged",
|
||||||
case.url
|
case.url
|
||||||
));
|
));
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
} else {
|
||||||
continue;
|
return Ok(frame.clone());
|
||||||
}
|
}
|
||||||
return Ok(frame.clone());
|
|
||||||
}
|
}
|
||||||
Some(WebSurfaceState::Ready(_)) => {}
|
Some(WebSurfaceState::Ready(frame)) => {
|
||||||
|
last_error = Some(format!(
|
||||||
|
"{} expected scroll y={expected_scroll_y}, observed {}",
|
||||||
|
case.url,
|
||||||
|
frame.detail_label()
|
||||||
|
));
|
||||||
|
}
|
||||||
Some(WebSurfaceState::Failed { message, .. }) => {
|
Some(WebSurfaceState::Failed { message, .. }) => {
|
||||||
return Err(format!("{} failed: {message}", case.url));
|
return Err(format!("{} failed: {message}", case.url));
|
||||||
}
|
}
|
||||||
Some(WebSurfaceState::Loading { .. }) | None => {}
|
Some(WebSurfaceState::Loading { .. }) | None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
||||||
|
return Err(last_error.unwrap_or_else(|| {
|
||||||
|
format!("timed out rendering {} at scroll y={expected_scroll_y}", case.url)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,12 +386,6 @@ fn wait_for_ready_frame_at_size(
|
|||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
|
||||||
return Err(last_error.unwrap_or_else(|| {
|
|
||||||
format!("timed out rendering {} at {expected_width}x{expected_height}", case.url)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
store.tick(std::slice::from_ref(tab_id));
|
store.tick(std::slice::from_ref(tab_id));
|
||||||
match store.state(tab_id) {
|
match store.state(tab_id) {
|
||||||
Some(WebSurfaceState::Ready(frame))
|
Some(WebSurfaceState::Ready(frame))
|
||||||
@@ -401,10 +396,9 @@ fn wait_for_ready_frame_at_size(
|
|||||||
validate_prd_frame_at_size(frame, case, expected_width, expected_height)
|
validate_prd_frame_at_size(frame, case, expected_width, expected_height)
|
||||||
{
|
{
|
||||||
last_error = Some(error);
|
last_error = Some(error);
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
} else {
|
||||||
continue;
|
return Ok(frame.clone());
|
||||||
}
|
}
|
||||||
return Ok(frame.clone());
|
|
||||||
}
|
}
|
||||||
Some(WebSurfaceState::Ready(_)) => {}
|
Some(WebSurfaceState::Ready(_)) => {}
|
||||||
Some(WebSurfaceState::Failed { message, .. }) => {
|
Some(WebSurfaceState::Failed { message, .. }) => {
|
||||||
@@ -413,6 +407,12 @@ fn wait_for_ready_frame_at_size(
|
|||||||
Some(WebSurfaceState::Loading { .. }) | None => {}
|
Some(WebSurfaceState::Loading { .. }) | None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
||||||
|
return Err(last_error.unwrap_or_else(|| {
|
||||||
|
format!("timed out rendering {} at {expected_width}x{expected_height}", case.url)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,6 +427,25 @@ fn wait_for_ready_frame_at_css_size(
|
|||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
store.tick(std::slice::from_ref(tab_id));
|
||||||
|
match store.state(tab_id) {
|
||||||
|
Some(WebSurfaceState::Ready(frame))
|
||||||
|
if frame.size().width == expected.physical_width
|
||||||
|
&& frame.size().height == expected.physical_height =>
|
||||||
|
{
|
||||||
|
if let Err(error) = validate_prd_frame_at_css_size(frame, case, expected) {
|
||||||
|
last_error = Some(error);
|
||||||
|
} else {
|
||||||
|
return Ok(frame.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(WebSurfaceState::Ready(_)) => {}
|
||||||
|
Some(WebSurfaceState::Failed { message, .. }) => {
|
||||||
|
return Err(format!("{} failed: {message}", case.url));
|
||||||
|
}
|
||||||
|
Some(WebSurfaceState::Loading { .. }) | None => {}
|
||||||
|
}
|
||||||
|
|
||||||
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
if started_at.elapsed() >= LIVE_SITE_WAIT_TIMEOUT {
|
||||||
return Err(last_error.unwrap_or_else(|| {
|
return Err(last_error.unwrap_or_else(|| {
|
||||||
format!(
|
format!(
|
||||||
@@ -440,26 +459,6 @@ fn wait_for_ready_frame_at_css_size(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
store.tick(std::slice::from_ref(tab_id));
|
|
||||||
match store.state(tab_id) {
|
|
||||||
Some(WebSurfaceState::Ready(frame))
|
|
||||||
if frame.size().width == expected.physical_width
|
|
||||||
&& frame.size().height == expected.physical_height =>
|
|
||||||
{
|
|
||||||
if let Err(error) = validate_prd_frame_at_css_size(frame, case, expected) {
|
|
||||||
last_error = Some(error);
|
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return Ok(frame.clone());
|
|
||||||
}
|
|
||||||
Some(WebSurfaceState::Ready(_)) => {}
|
|
||||||
Some(WebSurfaceState::Failed { message, .. }) => {
|
|
||||||
return Err(format!("{} failed: {message}", case.url));
|
|
||||||
}
|
|
||||||
Some(WebSurfaceState::Loading { .. }) | None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId, UrlText};
|
use ely_domain::{DEFAULT_ZOOM_PERCENT, ProfileId, TabId, UrlText};
|
||||||
|
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||||
|
use ely_servo_host::WebViewState;
|
||||||
use ely_servo_host::{
|
use ely_servo_host::{
|
||||||
IOSurfaceIdentity, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
|
IOSurfaceIdentity, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
|
||||||
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
|
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
|
||||||
RenderingContextKind, ResizeRequest, ScrollRequest, ServoHost, ServoSurfaceSize,
|
RenderingContextKind, ResizeRequest, ScrollRequest, ServoHost, ServoSurfaceSize,
|
||||||
SoftwareServoHost, WebViewState,
|
SoftwareServoHost,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::args::LiveArgs;
|
use super::args::LiveArgs;
|
||||||
@@ -361,7 +363,7 @@ fn poll_frame(
|
|||||||
) -> Result<LiveOutcome, LiveSidecarError> {
|
) -> Result<LiveOutcome, LiveSidecarError> {
|
||||||
host.tick();
|
host.tick();
|
||||||
let snapshot = host.snapshot(&session.webview_id)?;
|
let snapshot = host.snapshot(&session.webview_id)?;
|
||||||
if !snapshot.has_pending_frame() {
|
if !should_paint_live_frame(snapshot.has_pending_frame(), session.awaiting_visible_frame) {
|
||||||
return Ok(LiveOutcome::empty());
|
return Ok(LiveOutcome::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,6 +381,10 @@ fn poll_frame(
|
|||||||
Ok(LiveOutcome::empty())
|
Ok(LiveOutcome::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_paint_live_frame(has_pending_frame: bool, awaiting_visible_frame: bool) -> bool {
|
||||||
|
has_pending_frame || awaiting_visible_frame
|
||||||
|
}
|
||||||
|
|
||||||
fn paint_pending_frame(
|
fn paint_pending_frame(
|
||||||
host: &mut SoftwareServoHost,
|
host: &mut SoftwareServoHost,
|
||||||
session: &mut LiveSession,
|
session: &mut LiveSession,
|
||||||
@@ -419,21 +425,11 @@ fn paint_hardware_surface_frame(
|
|||||||
if !session.ever_visible_frame {
|
if !session.ever_visible_frame {
|
||||||
return paint_initial_hardware_surface_frame(host, session);
|
return paint_initial_hardware_surface_frame(host, session);
|
||||||
}
|
}
|
||||||
|
// Cross-process IOSurface lookup can block the app-side worker for
|
||||||
let paint_started_at = Instant::now();
|
// seconds on macOS. Live app frames use readback so scroll/click
|
||||||
host.paint_without_readback(&session.webview_id)?;
|
// input stays bounded by the paint barrier instead of the surface
|
||||||
let snapshot = host.snapshot(&session.webview_id)?;
|
// import path.
|
||||||
let paint_ns = elapsed_ns(paint_started_at);
|
paint_readback_frame(host, session)
|
||||||
let encode_started_at = Instant::now();
|
|
||||||
let report = LiveFrameReport::new_hardware_surface(
|
|
||||||
&snapshot,
|
|
||||||
session.width,
|
|
||||||
session.height,
|
|
||||||
session.device_pixel_ratio(),
|
|
||||||
);
|
|
||||||
let encode_ns = elapsed_ns(encode_started_at);
|
|
||||||
let timings = PartialFrameTimings { paint_ns, encode_ns };
|
|
||||||
Ok((LiveOutcome::from_report(report, timings), true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||||
@@ -531,4 +527,15 @@ mod tests {
|
|||||||
"first apply_layout must resize after hidpi has been pushed",
|
"first apply_layout must resize after hidpi has been pushed",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn awaiting_visible_frame_forces_paint_without_pending_flag() {
|
||||||
|
assert!(should_paint_live_frame(false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn idle_poll_waits_for_pending_frame() {
|
||||||
|
assert!(!should_paint_live_frame(false, false));
|
||||||
|
assert!(should_paint_live_frame(true, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user