From 80cff6dad3a96f6b9c796093568870f323e136c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 15 May 2026 21:21:14 -0400 Subject: [PATCH] Stop the web surface from strobing on transient errors Two paths in `WebSurfaceStore::tick` were downgrading a perfectly good Ready frame back to Failed / Loading on every transient hiccup: - `WebSurfaceRuntimeFrame::Failed` overwrote the surface state unconditionally. Combined with the 125 Hz tick (which submits a Poll for every visible tab and any transient ensure / poll error becomes a Failed response), even one parse glitch wiped the rendered page. - The `initial_display_gate_message` and `should_hold_initial_frame` checks for incoming Ready frames hardcoded `has_previous_frame = false`, so a stray empty paint pass after the page had already rendered would knock the surface back to Loading or Failed. Detect whether the surface already has a `Ready(_)` state and: - ignore Failed responses (logging through `tracing` for diagnostics) while a real frame is on screen; - pass that "had ready" signal into the gate / hold checks so they only apply to the very first paint, not later refreshes. The page now stays put even when Servo emits a momentary error; only the first-paint failure path can mark the surface Failed. --- crates/ely_app/src/shell/web_surface.rs | 52 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/crates/ely_app/src/shell/web_surface.rs b/crates/ely_app/src/shell/web_surface.rs index 2f5b4fe..4d86ad6 100644 --- a/crates/ely_app/src/shell/web_surface.rs +++ b/crates/ely_app/src/shell/web_surface.rs @@ -118,21 +118,34 @@ impl WebSurfaceStore { for frame in frames { match frame { WebSurfaceRuntimeFrame::Ready { tab_id, frame, url_change } => { - match self.initial_display_gate_message(&tab_id, &frame, false) { + let had_ready = matches!( + self.surfaces.get(&tab_id).and_then(|surface| surface.state.as_ref()), + Some(WebSurfaceState::Ready(_)) + ); + match self.initial_display_gate_message(&tab_id, &frame, had_ready) { Ok(()) => {} Err(message) => { - self.surface_mut(&tab_id).state = - Some(WebSurfaceState::Failed { message }); - result.changed = true; + // Only transition to Failed when there is + // nothing on screen yet — once a real frame + // has rendered, transient gate failures + // (e.g. a stray empty-paint pass) must not + // wipe it out. + if !had_ready { + self.surface_mut(&tab_id).state = + Some(WebSurfaceState::Failed { message }); + result.changed = true; + } continue; } } - if self.should_hold_initial_frame(&tab_id, &frame, false) { - self.surface_mut(&tab_id).state = Some(WebSurfaceState::Loading { - requested_url: frame.requested_url.clone(), - previous_frame: None, - }); - result.changed = true; + if self.should_hold_initial_frame(&tab_id, &frame, had_ready) { + if !had_ready { + self.surface_mut(&tab_id).state = Some(WebSurfaceState::Loading { + requested_url: frame.requested_url.clone(), + previous_frame: None, + }); + result.changed = true; + } continue; } if let Some(metadata) = WebSurfacePageMetadata::from_frame(&tab_id, &frame) { @@ -145,6 +158,25 @@ impl WebSurfaceStore { } } WebSurfaceRuntimeFrame::Failed { tab_id, message } => { + let had_ready = matches!( + self.surfaces.get(&tab_id).and_then(|surface| surface.state.as_ref()), + Some(WebSurfaceState::Ready(_)) + ); + if had_ready { + // Keep the last good frame on screen — the + // worker emits Failed for any transient ensure + // / poll error (parse glitch, momentary IPC + // hiccup) and downgrading every one of them + // strobes the page. The error still surfaces + // through `tracing` for diagnostics. + tracing::warn!( + target: "ely::web_surface", + tab_id = %tab_id, + message = %message, + "transient surface error; keeping last frame", + ); + continue; + } self.surface_mut(&tab_id).state = Some(WebSurfaceState::Failed { message }); result.changed = true; }