Cover web surface live scrolling

This commit is contained in:
2026-05-13 04:31:58 -04:00
parent d4046ca4e7
commit 2302a53b39
@@ -19,7 +19,7 @@ use crate::{
}, },
shell::{ shell::{
web_surface_frame::WebSurfaceFrame, web_surface_frame::WebSurfaceFrame,
web_surface_geometry::{WebSurfaceScrollOffset, WebSurfaceSize}, web_surface_geometry::WebSurfaceSize,
web_surface_state::{WebSurfaceInputOutcome, WebSurfaceState}, web_surface_state::{WebSurfaceInputOutcome, WebSurfaceState},
}, },
}; };
@@ -33,6 +33,10 @@ const LIVE_SITE_RENDER_ATTEMPTS: usize = 3;
const LIVE_SITE_WAIT_TIMEOUT: Duration = Duration::from_secs(20); const LIVE_SITE_WAIT_TIMEOUT: Duration = Duration::from_secs(20);
const LIVE_SITE_WAIT_INTERVAL: Duration = Duration::from_millis(2); const LIVE_SITE_WAIT_INTERVAL: Duration = Duration::from_millis(2);
const LIVE_SITE_CHILD_ENV: &str = "ELY_APP_WEB_SURFACE_LIVE_CHILD"; const LIVE_SITE_CHILD_ENV: &str = "ELY_APP_WEB_SURFACE_LIVE_CHILD";
const LIVE_SITE_SCROLL_DOWN_Y: i32 = 360;
const LIVE_SITE_SCROLL_UP_Y: i32 = -240;
const LIVE_SITE_SCROLL_POINT_X: f32 = 320.0;
const LIVE_SITE_SCROLL_POINT_Y: f32 = 320.0;
#[test] #[test]
fn web_surface_cases_cover_prd_reference_urls() -> Result<(), Box<dyn Error>> { fn web_surface_cases_cover_prd_reference_urls() -> Result<(), Box<dyn Error>> {
@@ -53,6 +57,13 @@ fn web_surface_opens_and_renders_prd_reference_sites() -> Result<(), Box<dyn Err
}) })
} }
#[test]
fn web_surface_scrolls_prd_site_down_and_up() -> Result<(), Box<dyn Error>> {
run_isolated_live_site_test("web_surface_scrolls_prd_site_down_and_up", || {
assert_web_surface_scrolls_prd_site()
})
}
fn run_isolated_live_site_test( fn run_isolated_live_site_test(
test_name: &str, test_name: &str,
test: impl FnOnce() -> Result<(), Box<dyn Error>>, test: impl FnOnce() -> Result<(), Box<dyn Error>>,
@@ -92,6 +103,69 @@ fn assert_web_surfaces_render(cases: &[LiveSiteCase]) -> Result<(), Box<dyn Erro
Ok(()) Ok(())
} }
fn assert_web_surface_scrolls_prd_site() -> Result<(), Box<dyn Error>> {
let mut store = WebSurfaceStore::new();
let profile_id = ProfileId::new();
let case = PRD_TOP_SITE_CASES
.iter()
.find(|case| case.url == "https://servo.org/")
.ok_or("missing servo.org live-site case")?;
let tab = web_tab(profile_id, case.url)?;
assert_eq!(
store.record_viewport_size(tab.id(), live_surface_bounds(), 1.0),
WebSurfaceInputOutcome::Applied,
"{}",
case.url,
);
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
let initial = wait_for_ready_frame_at_scroll(&mut store, tab.id(), case, 0, None)?;
assert_eq!(
store.record_scroll_delta(
tab.id(),
case.url,
point(px(0.0), px(LIVE_SITE_SCROLL_DOWN_Y as f32)),
live_scroll_point(),
1.0,
),
WebSurfaceInputOutcome::Applied,
"{}",
case.url,
);
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
let down = wait_for_ready_frame_at_scroll(
&mut store,
tab.id(),
case,
LIVE_SITE_SCROLL_DOWN_Y,
Some(initial.sample_hash()),
)?;
assert_eq!(
store.record_scroll_delta(
tab.id(),
case.url,
point(px(0.0), px(LIVE_SITE_SCROLL_UP_Y as f32)),
live_scroll_point(),
1.0,
),
WebSurfaceInputOutcome::Applied,
"{}",
case.url,
);
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
wait_for_ready_frame_at_scroll(
&mut store,
tab.id(),
case,
LIVE_SITE_SCROLL_DOWN_Y + LIVE_SITE_SCROLL_UP_Y,
Some(down.sample_hash()),
)?;
store.close_surface(tab.id());
Ok(())
}
fn render_web_surface_frame( fn render_web_surface_frame(
store: &mut WebSurfaceStore, store: &mut WebSurfaceStore,
profile_id: &ProfileId, profile_id: &ProfileId,
@@ -140,7 +214,7 @@ fn wait_for_ready_frame(
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)) => {
validate_prd_frame(frame, case)?; validate_prd_frame(frame, case, 0)?;
return Ok(frame.clone()); return Ok(frame.clone());
} }
Some(WebSurfaceState::Failed { message, .. }) => { Some(WebSurfaceState::Failed { message, .. }) => {
@@ -153,7 +227,62 @@ fn wait_for_ready_frame(
} }
} }
fn validate_prd_frame(frame: &WebSurfaceFrame, case: &LiveSiteCase) -> Result<(), String> { fn wait_for_ready_frame_at_scroll(
store: &mut WebSurfaceStore,
tab_id: &TabId,
case: &LiveSiteCase,
expected_scroll_y: i32,
previous_sample_hash: Option<u64>,
) -> Result<WebSurfaceFrame, String> {
let started_at = Instant::now();
let mut last_error = None;
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));
match store.state(tab_id) {
Some(WebSurfaceState::Ready(frame))
if frame.scroll_offset().y() == expected_scroll_y =>
{
if let Err(error) = validate_prd_frame(frame, case, expected_scroll_y) {
last_error = Some(error);
thread::sleep(LIVE_SITE_WAIT_INTERVAL);
continue;
}
if !frame.has_hardware_surface()
&& let Some(previous_sample_hash) = previous_sample_hash
&& frame.sample_hash() == previous_sample_hash
{
last_error = Some(format!(
"{} scroll y={expected_scroll_y} sample hash unchanged",
case.url
));
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);
}
}
fn validate_prd_frame(
frame: &WebSurfaceFrame,
case: &LiveSiteCase,
expected_scroll_y: i32,
) -> Result<(), String> {
require( require(
frame.size() frame.size()
== WebSurfaceSize { == WebSurfaceSize {
@@ -164,7 +293,7 @@ fn validate_prd_frame(frame: &WebSurfaceFrame, case: &LiveSiteCase) -> Result<()
format!("{} size: {:?}", case.url, frame.size()), format!("{} size: {:?}", case.url, frame.size()),
)?; )?;
require( require(
frame.scroll_offset() == WebSurfaceScrollOffset::default(), frame.scroll_offset().y() == expected_scroll_y,
format!("{} scroll: {:?}", case.url, frame.scroll_offset()), format!("{} scroll: {:?}", case.url, frame.scroll_offset()),
)?; )?;
require_render_state_is_open(frame.render_state(), case.url)?; require_render_state_is_open(frame.render_state(), case.url)?;
@@ -176,8 +305,13 @@ fn validate_prd_frame(frame: &WebSurfaceFrame, case: &LiveSiteCase) -> Result<()
frame.title_label().contains(case.title_fragment), frame.title_label().contains(case.title_fragment),
format!("title: {}", frame.title_label()), format!("title: {}", frame.title_label()),
)?; )?;
let expected_detail = if expected_scroll_y == 0 {
format!("{} 934x657", frame.render_state())
} else {
format!("{} 934x657 y={expected_scroll_y}", frame.render_state())
};
require( require(
frame.detail_label() == format!("{} 934x657", frame.render_state()), frame.detail_label() == expected_detail,
format!("{} detail: {}", case.url, frame.detail_label()), format!("{} detail: {}", case.url, frame.detail_label()),
)?; )?;
if frame.has_hardware_surface() { if frame.has_hardware_surface() {
@@ -221,6 +355,10 @@ fn live_surface_bounds() -> Bounds<gpui::Pixels> {
) )
} }
fn live_scroll_point() -> gpui::Point<gpui::Pixels> {
point(px(LIVE_SITE_SCROLL_POINT_X), px(LIVE_SITE_SCROLL_POINT_Y))
}
fn web_tab(profile_id: ProfileId, url: &str) -> Result<BrowserTab, Box<dyn Error>> { fn web_tab(profile_id: ProfileId, url: &str) -> Result<BrowserTab, Box<dyn Error>> {
Ok(BrowserTab::new(TabId::new(), SpaceId::new(), profile_id, "Web", UrlText::parse(url)?)) Ok(BrowserTab::new(TabId::new(), SpaceId::new(), profile_id, "Web", UrlText::parse(url)?))
} }