fix: present frames during continuous input

This commit is contained in:
2026-07-10 20:21:35 -04:00
parent 303a3aa926
commit d5feda9da5
5 changed files with 52 additions and 6 deletions
+3 -2
View File
@@ -90,8 +90,9 @@ Real and verified:
macOS Metal toolchain so `cargo run`'s shader-build failure is fixed.
- Scroll input: consecutive same-document wheel requests coalesce at the
profile worker boundary while preserving total device-pixel distance;
clicks, text, and allow-once permission transfers retain strict ordering.
Verified by a blocked-worker burst regression and the live-site scroll e2e.
frames from the active render epoch remain presentable while newer input is
queued. Clicks, text, and allow-once permission transfers retain strict
ordering. Verified by generation, blocked-worker burst, and live-site tests.
- Website color scheme: resolved System/Light/Dark state crosses sidecar
protocol v4 and reaches `WebView::notify_theme_change`; GPUI appearance
events trigger a shell repaint, and first-navigation WebView replacement
@@ -104,9 +104,11 @@ impl WebSurfaceRuntime {
let submitted_at = Instant::now();
let started_loading = {
let session = session_for_scope(&mut self.sessions, tab.id(), scope.clone());
let started_loading = session.started_loading(&requested_url, size, zoom_percent);
let started_loading =
session.started_loading(&requested_url, size, zoom_percent, self.color_scheme);
if started_loading {
session.pending_user_navigation = false;
session.frame_generation_floor = Some(generation);
}
if user_navigation_input {
session.pending_user_navigation = true;
@@ -114,6 +116,7 @@ impl WebSurfaceRuntime {
session.requested_url = requested_url.clone();
session.size = size;
session.zoom_percent = zoom_percent;
session.color_scheme = Some(self.color_scheme);
session.scroll_offset = next_scroll_offset;
session.generation = Some(generation);
session.cadence.note_ensure(input_kind, started_loading, submitted_at);
@@ -296,7 +299,7 @@ impl WebSurfaceRuntime {
continue;
};
if !self.sessions.get(&tab_id_obj).is_some_and(|session| {
&session.scope == scope && session.generation == Some(generation)
&session.scope == scope && session.accepts_frame_generation(generation)
}) {
continue;
}
@@ -145,6 +145,35 @@ fn late_frame_is_discarded_after_tab_session_closes() -> Result<(), String> {
Ok(())
}
#[test]
fn earlier_frame_from_same_render_epoch_remains_presentable() -> Result<(), String> {
let mut runtime = WebSurfaceRuntime::new_with_client_factory(empty_client_factory);
let tab_id = TabId::new();
let profile_id = ProfileId::new();
let tab = web_tab(tab_id.clone(), profile_id.clone(), "https://example.com/page")?;
runtime.ensure_tab(&tab, surface_size(), ProfileDataMode::Transient, &[], pending_input())?;
let earlier = current_generation(&runtime, &tab_id)?;
runtime.ensure_tab(&tab, surface_size(), ProfileDataMode::Transient, &[], pending_input())?;
let latest = current_generation(&runtime, &tab_id)?;
let mut frames = Vec::new();
runtime.collect_responses(
&scope(&profile_id),
vec![WorkerResponse::Frame {
generation: earlier,
tab_id: tab_id.as_str().to_string(),
frame: live_frame(),
}],
Instant::now(),
&mut frames,
);
assert!(earlier < latest);
assert!(matches!(frames.as_slice(), [WebSurfaceRuntimeFrame::Ready { .. }]));
Ok(())
}
#[test]
fn scope_change_returns_submitted_permission_grants_on_the_next_tick() -> Result<(), String> {
let mut runtime = WebSurfaceRuntime::new_with_client_factory(empty_client_factory);
@@ -57,7 +57,9 @@ fn successful_frame_resets_scope_retry_state() {
let scope = WebSurfaceRuntimeScope::new(ProfileId::new(), ProfileDataMode::Transient);
let tab_id = TabId::new();
let generation = RequestGeneration::new(1);
session_for_scope(&mut runtime.sessions, &tab_id, scope.clone()).generation = Some(generation);
let session = session_for_scope(&mut runtime.sessions, &tab_id, scope.clone());
session.generation = Some(generation);
session.frame_generation_floor = Some(generation);
runtime.note_scope_failure(&scope, Instant::now());
let mut frames = Vec::new();
@@ -8,7 +8,7 @@ use crate::services::{
transient_profile_data_dir,
},
};
use ely_domain::{ProfileId, TabId};
use ely_domain::{ColorScheme, ProfileId, TabId};
use super::{
web_surface_cadence::WebSurfacePollCadence,
@@ -42,6 +42,8 @@ pub(super) struct WebSurfaceSession {
pub(super) scroll_offset: WebSurfaceScrollOffset,
pub(super) pending_user_navigation: bool,
pub(super) generation: Option<RequestGeneration>,
pub(super) frame_generation_floor: Option<RequestGeneration>,
pub(super) color_scheme: Option<ColorScheme>,
pub(super) cadence: WebSurfacePollCadence,
}
@@ -55,6 +57,8 @@ impl WebSurfaceSession {
scroll_offset: WebSurfaceScrollOffset::default(),
pending_user_navigation: false,
generation: None,
frame_generation_floor: None,
color_scheme: None,
cadence: WebSurfacePollCadence::default(),
}
}
@@ -64,10 +68,17 @@ impl WebSurfaceSession {
requested_url: &str,
size: WebSurfaceSize,
zoom_percent: u16,
color_scheme: ColorScheme,
) -> bool {
self.requested_url != requested_url
|| self.size != size
|| self.zoom_percent != zoom_percent
|| self.color_scheme != Some(color_scheme)
}
pub(super) fn accepts_frame_generation(&self, generation: RequestGeneration) -> bool {
self.frame_generation_floor.is_some_and(|floor| generation >= floor)
&& self.generation.is_some_and(|latest| generation <= latest)
}
pub(super) fn url_change_for(