Fold WebSurfaceStore into a single PerTabSurface owner

Eleven parallel BTreeMaps (click_points, hover_points,
pending_scroll_deltas, scroll_offsets, typed_texts, viewport_bounds,
viewport_sizes, pending_viewport_sizes, states, etc.) made every
input bug a coordination problem across 11 disjoint maps with no
compile-time guarantee they stayed in sync. Collapse them into
BTreeMap<TabId, PerTabSurface>: one owner per tab, one lookup per
input event. keyboard_focus stays at the store level because only
one tab in the window can hold focus at a time.

Public API and behavior are unchanged; the previously-fixed
"scroll keeps keyboard_focus" semantics are preserved. cargo test
ely_browser_core + cargo test ely_app --bin ely_app green.
This commit is contained in:
2026-05-10 01:08:03 -04:00
parent 21da001bf5
commit dc06d5590d
2 changed files with 139 additions and 93 deletions
+54 -2
View File
@@ -1,8 +1,11 @@
use ely_domain::TabId;
use gpui::{Bounds, Pixels};
use super::{
web_surface_frame::WebSurfaceFrame,
web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollOffset},
web_surface_geometry::{
WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceScrollOffset, WebSurfaceSize,
},
};
pub(super) struct WebSurfaceScrollState {
@@ -41,7 +44,7 @@ pub(super) struct WebSurfaceTextInputState {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct WebSurfacePendingInput {
pub(super) scroll_offset: WebSurfaceScrollOffset,
pub(super) scroll_delta: Option<super::web_surface_geometry::WebSurfaceScrollDelta>,
pub(super) scroll_delta: Option<WebSurfaceScrollDelta>,
pub(super) click_point: Option<WebSurfaceClickPoint>,
pub(super) hover_point: Option<WebSurfaceClickPoint>,
pub(super) typed_text: Option<String>,
@@ -52,3 +55,52 @@ pub(super) enum WebSurfaceState {
Ready(WebSurfaceFrame),
Failed { message: String },
}
/// All per-tab surface invariants in one owner.
///
/// Replaces the previous 11 parallel `BTreeMap<TabId, _>` fields on
/// `WebSurfaceStore`. Every per-tab field lives here so a single
/// lookup yields the full input/render context for that tab; the
/// remaining cross-tab state (the runtime backend and the singular
/// `keyboard_focus` pointer to the active tab) stays on the store.
///
/// All inputs that depend on a measured viewport take their
/// pre-conditions from this struct (`viewport_bounds`,
/// `viewport_size`), so "viewport not ready" turns into a guard at
/// the call site instead of a `get` on a parallel map that may or
/// may not be populated.
pub(super) struct PerTabSurface {
pub(super) viewport_bounds: Option<Bounds<Pixels>>,
pub(super) viewport_size: Option<WebSurfaceSize>,
pub(super) pending_viewport_size: Option<WebSurfaceSize>,
pub(super) hover_point: Option<WebSurfaceClickPoint>,
pub(super) click_point: Option<WebSurfaceClickState>,
pub(super) pending_scroll_delta: Option<WebSurfaceScrollDelta>,
pub(super) scroll_offset: Option<WebSurfaceScrollState>,
pub(super) typed_text: Option<WebSurfaceTextInputState>,
pub(super) state: Option<WebSurfaceState>,
}
impl PerTabSurface {
pub(super) fn new() -> Self {
Self {
viewport_bounds: None,
viewport_size: None,
pending_viewport_size: None,
hover_point: None,
click_point: None,
pending_scroll_delta: None,
scroll_offset: None,
typed_text: None,
state: None,
}
}
pub(super) fn scroll_offset_for(&self, requested_url: &str) -> WebSurfaceScrollOffset {
self.scroll_offset
.as_ref()
.filter(|state| state.requested_url == requested_url)
.map(|state| state.offset)
.unwrap_or_default()
}
}