refactor(shell): peel input recorders off WebSurfaceStore

`web_surface.rs` had crept to 548 lines; six unrelated input-recorder
methods (scroll delta, viewport size, native surface, hover point,
click point, typed text) dominated the file. Move them into a sibling
`web_surface_input.rs` so the store file (now 338 lines) is the
ensure / tick / lifecycle facade and the input file is a flat set of
`record_*` methods on the same struct.

`surfaces`, `keyboard_focus`, and `surface_mut` become `pub(super)` so
the sibling can reach them; everything else stays private.
This commit is contained in:
2026-05-18 14:24:52 -04:00
parent db7cd2f9bc
commit dabb965afd
3 changed files with 228 additions and 215 deletions
+1
View File
@@ -32,6 +32,7 @@ mod web_surface_cadence;
mod web_surface_controller; mod web_surface_controller;
mod web_surface_frame; mod web_surface_frame;
mod web_surface_geometry; mod web_surface_geometry;
mod web_surface_input;
mod web_surface_keyboard; mod web_surface_keyboard;
mod web_surface_metadata; mod web_surface_metadata;
mod web_surface_permissions; mod web_surface_permissions;
+5 -215
View File
@@ -2,30 +2,27 @@ use std::collections::BTreeMap;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ely_domain::{BrowserTab, TabId}; use ely_domain::{BrowserTab, TabId};
use gpui::{Bounds, NativeSurfaceHandle, Pixels, Point};
use crate::services::ProfileDataMode; use crate::services::ProfileDataMode;
use super::{ use super::{
web_surface_cadence::IDLE_POLL_INTERVAL, web_surface_cadence::IDLE_POLL_INTERVAL,
web_surface_frame::WebSurfaceFrame, web_surface_frame::WebSurfaceFrame,
web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceSize},
web_surface_permissions::WebSurfaceSitePermission, web_surface_permissions::WebSurfaceSitePermission,
web_surface_runtime::{WebSurfaceRuntime, WebSurfaceRuntimeFrame}, web_surface_runtime::{WebSurfaceRuntime, WebSurfaceRuntimeFrame},
web_surface_state::{ web_surface_state::{
PerTabSurface, WebSurfaceClickState, WebSurfaceEnsureKey, WebSurfaceInputOutcome, PerTabSurface, WebSurfaceEnsureKey, WebSurfaceKeyboardFocusState, WebSurfacePendingInput,
WebSurfaceKeyboardFocusState, WebSurfacePendingInput, WebSurfaceScrollState, WebSurfaceState, WebSurfaceTickResult,
WebSurfaceState, WebSurfaceTextInputState, WebSurfaceTickResult,
}, },
}; };
pub(super) struct WebSurfaceStore { pub(super) struct WebSurfaceStore {
runtime: WebSurfaceRuntime, runtime: WebSurfaceRuntime,
/// Single owner of every per-tab invariant. See [`PerTabSurface`]. /// Single owner of every per-tab invariant. See [`PerTabSurface`].
surfaces: BTreeMap<TabId, PerTabSurface>, pub(super) surfaces: BTreeMap<TabId, PerTabSurface>,
/// Singleton because only one tab at a time holds keyboard focus /// Singleton because only one tab at a time holds keyboard focus
/// across the whole window. Lives on the store, not per-tab. /// across the whole window. Lives on the store, not per-tab.
keyboard_focus: Option<WebSurfaceKeyboardFocusState>, pub(super) keyboard_focus: Option<WebSurfaceKeyboardFocusState>,
} }
impl WebSurfaceStore { impl WebSurfaceStore {
@@ -207,213 +204,6 @@ impl WebSurfaceStore {
} }
} }
pub(super) fn record_scroll_delta(
&mut self,
tab_id: &TabId,
requested_url: &str,
delta: Point<Pixels>,
position: Point<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
let Some(delta) = WebSurfaceScrollDelta::from_point(delta, scale_factor) else {
return WebSurfaceInputOutcome::DroppedZeroDelta;
};
let Some(bounds) = self.surfaces.get(tab_id).and_then(|surface| surface.viewport_bounds)
else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(point) =
WebSurfaceClickPoint::from_window_position(bounds, position, scale_factor)
else {
return WebSurfaceInputOutcome::DroppedOutOfBounds;
};
let surface = self.surface_mut(tab_id);
let flush_throttled = surface.input_flush_is_throttled(Instant::now());
let scroll = surface
.scroll_offset
.get_or_insert_with(|| WebSurfaceScrollState::new(requested_url.to_string()));
if scroll.requested_url != requested_url {
*scroll = WebSurfaceScrollState::new(requested_url.to_string());
}
scroll.offset = scroll.offset.scrolled_by(delta);
surface.pending_scroll_delta = Some(match surface.pending_scroll_delta {
Some(current) => current.combined_with(delta),
None => delta,
});
surface.pending_scroll_point = Some(point);
surface.mark_pending_input_started();
surface.click_point = None;
if flush_throttled {
WebSurfaceInputOutcome::Buffered
} else {
WebSurfaceInputOutcome::Applied
}
}
pub(super) fn record_viewport_size(
&mut self,
tab_id: &TabId,
bounds: Bounds<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
let Some(size) = WebSurfaceSize::from_bounds(bounds, scale_factor) else {
return WebSurfaceInputOutcome::DroppedInvalidBounds;
};
let surface = self.surface_mut(tab_id);
surface.viewport_bounds = Some(bounds);
let Some(current_size) = surface.viewport_size else {
surface.viewport_size = Some(size);
return WebSurfaceInputOutcome::Applied;
};
if current_size == size {
return WebSurfaceInputOutcome::NoChange;
}
surface.viewport_size = Some(size);
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_native_surface(
&mut self,
tab_id: &TabId,
native_surface: NativeSurfaceHandle,
) -> WebSurfaceInputOutcome {
let surface = self.surface_mut(tab_id);
if surface
.native_surface
.as_ref()
.is_some_and(|current| current.identity() == native_surface.identity())
{
return WebSurfaceInputOutcome::NoChange;
}
surface.native_surface = Some(native_surface);
surface.last_ensure_key = None;
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_hover_point(
&mut self,
tab_id: &TabId,
position: Point<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
self.record_hover_point_at(tab_id, position, scale_factor, Instant::now())
}
fn record_hover_point_at(
&mut self,
tab_id: &TabId,
position: Point<Pixels>,
scale_factor: f32,
now: Instant,
) -> WebSurfaceInputOutcome {
let Some(surface) = self.surfaces.get_mut(tab_id) else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(bounds) = surface.viewport_bounds else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(point) =
WebSurfaceClickPoint::from_window_position(bounds, position, scale_factor)
else {
return WebSurfaceInputOutcome::DroppedOutOfBounds;
};
if surface.hover_point == Some(point) {
return WebSurfaceInputOutcome::NoChange;
}
if surface.hover_is_throttled(now) {
return WebSurfaceInputOutcome::NoChange;
}
surface.hover_point = Some(point);
surface.mark_hover_enqueued(now);
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_click_point(
&mut self,
tab_id: &TabId,
requested_url: &str,
position: Point<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
let Some(bounds) = self.surfaces.get(tab_id).and_then(|surface| surface.viewport_bounds)
else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(point) =
WebSurfaceClickPoint::from_window_position(bounds, position, scale_factor)
else {
return WebSurfaceInputOutcome::DroppedOutOfBounds;
};
let scroll_offset = self
.surfaces
.get(tab_id)
.map(|surface| surface.scroll_offset_for(requested_url))
.unwrap_or_default();
let state =
WebSurfaceClickState { requested_url: requested_url.to_string(), scroll_offset, point };
self.keyboard_focus = Some(WebSurfaceKeyboardFocusState {
tab_id: tab_id.clone(),
requested_url: requested_url.to_string(),
scroll_offset: state.scroll_offset,
click_point: state.point,
});
let surface = self.surface_mut(tab_id);
surface.typed_text = None;
surface.click_point = Some(state);
surface.mark_pending_input_started();
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_typed_text(
&mut self,
tab_id: &TabId,
requested_url: &str,
text: &str,
) -> WebSurfaceInputOutcome {
if text.is_empty() {
return WebSurfaceInputOutcome::DroppedEmptyText;
}
let Some(focus) = self.keyboard_focus.as_ref() else {
return WebSurfaceInputOutcome::DroppedNoKeyboardFocus;
};
if focus.tab_id != *tab_id || focus.requested_url != requested_url {
return WebSurfaceInputOutcome::DroppedFocusMismatch;
}
let scroll_offset = focus.scroll_offset;
let click_point = focus.click_point;
let surface = self.surface_mut(tab_id);
let entry = surface.typed_text.get_or_insert_with(|| WebSurfaceTextInputState {
requested_url: requested_url.to_string(),
scroll_offset,
click_point,
text: String::new(),
});
if entry.requested_url != requested_url
|| entry.scroll_offset != scroll_offset
|| entry.click_point != click_point
{
*entry = WebSurfaceTextInputState {
requested_url: requested_url.to_string(),
scroll_offset,
click_point,
text: String::new(),
};
}
entry.text.push_str(text);
surface.mark_pending_input_started();
WebSurfaceInputOutcome::Applied
}
fn take_pending_input( fn take_pending_input(
&mut self, &mut self,
tab_id: &TabId, tab_id: &TabId,
@@ -512,7 +302,7 @@ impl WebSurfaceStore {
}) })
} }
fn surface_mut(&mut self, tab_id: &TabId) -> &mut PerTabSurface { pub(super) fn surface_mut(&mut self, tab_id: &TabId) -> &mut PerTabSurface {
self.surfaces.entry(tab_id.clone()).or_insert_with(PerTabSurface::new) self.surfaces.entry(tab_id.clone()).or_insert_with(PerTabSurface::new)
} }
@@ -0,0 +1,222 @@
use std::time::Instant;
use ely_domain::TabId;
use gpui::{Bounds, NativeSurfaceHandle, Pixels, Point};
use super::{
web_surface::WebSurfaceStore,
web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceSize},
web_surface_state::{
WebSurfaceClickState, WebSurfaceInputOutcome, WebSurfaceKeyboardFocusState,
WebSurfaceScrollState, WebSurfaceTextInputState,
},
};
impl WebSurfaceStore {
pub(super) fn record_scroll_delta(
&mut self,
tab_id: &TabId,
requested_url: &str,
delta: Point<Pixels>,
position: Point<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
let Some(delta) = WebSurfaceScrollDelta::from_point(delta, scale_factor) else {
return WebSurfaceInputOutcome::DroppedZeroDelta;
};
let Some(bounds) = self.surfaces.get(tab_id).and_then(|surface| surface.viewport_bounds)
else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(point) =
WebSurfaceClickPoint::from_window_position(bounds, position, scale_factor)
else {
return WebSurfaceInputOutcome::DroppedOutOfBounds;
};
let surface = self.surface_mut(tab_id);
let flush_throttled = surface.input_flush_is_throttled(Instant::now());
let scroll = surface
.scroll_offset
.get_or_insert_with(|| WebSurfaceScrollState::new(requested_url.to_string()));
if scroll.requested_url != requested_url {
*scroll = WebSurfaceScrollState::new(requested_url.to_string());
}
scroll.offset = scroll.offset.scrolled_by(delta);
surface.pending_scroll_delta = Some(match surface.pending_scroll_delta {
Some(current) => current.combined_with(delta),
None => delta,
});
surface.pending_scroll_point = Some(point);
surface.mark_pending_input_started();
surface.click_point = None;
if flush_throttled {
WebSurfaceInputOutcome::Buffered
} else {
WebSurfaceInputOutcome::Applied
}
}
pub(super) fn record_viewport_size(
&mut self,
tab_id: &TabId,
bounds: Bounds<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
let Some(size) = WebSurfaceSize::from_bounds(bounds, scale_factor) else {
return WebSurfaceInputOutcome::DroppedInvalidBounds;
};
let surface = self.surface_mut(tab_id);
surface.viewport_bounds = Some(bounds);
let Some(current_size) = surface.viewport_size else {
surface.viewport_size = Some(size);
return WebSurfaceInputOutcome::Applied;
};
if current_size == size {
return WebSurfaceInputOutcome::NoChange;
}
surface.viewport_size = Some(size);
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_native_surface(
&mut self,
tab_id: &TabId,
native_surface: NativeSurfaceHandle,
) -> WebSurfaceInputOutcome {
let surface = self.surface_mut(tab_id);
if surface
.native_surface
.as_ref()
.is_some_and(|current| current.identity() == native_surface.identity())
{
return WebSurfaceInputOutcome::NoChange;
}
surface.native_surface = Some(native_surface);
surface.last_ensure_key = None;
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_hover_point(
&mut self,
tab_id: &TabId,
position: Point<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
self.record_hover_point_at(tab_id, position, scale_factor, Instant::now())
}
pub(super) fn record_hover_point_at(
&mut self,
tab_id: &TabId,
position: Point<Pixels>,
scale_factor: f32,
now: Instant,
) -> WebSurfaceInputOutcome {
let Some(surface) = self.surfaces.get_mut(tab_id) else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(bounds) = surface.viewport_bounds else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(point) =
WebSurfaceClickPoint::from_window_position(bounds, position, scale_factor)
else {
return WebSurfaceInputOutcome::DroppedOutOfBounds;
};
if surface.hover_point == Some(point) {
return WebSurfaceInputOutcome::NoChange;
}
if surface.hover_is_throttled(now) {
return WebSurfaceInputOutcome::NoChange;
}
surface.hover_point = Some(point);
surface.mark_hover_enqueued(now);
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_click_point(
&mut self,
tab_id: &TabId,
requested_url: &str,
position: Point<Pixels>,
scale_factor: f32,
) -> WebSurfaceInputOutcome {
let Some(bounds) = self.surfaces.get(tab_id).and_then(|surface| surface.viewport_bounds)
else {
return WebSurfaceInputOutcome::DroppedNoViewportBounds;
};
let Some(point) =
WebSurfaceClickPoint::from_window_position(bounds, position, scale_factor)
else {
return WebSurfaceInputOutcome::DroppedOutOfBounds;
};
let scroll_offset = self
.surfaces
.get(tab_id)
.map(|surface| surface.scroll_offset_for(requested_url))
.unwrap_or_default();
let state =
WebSurfaceClickState { requested_url: requested_url.to_string(), scroll_offset, point };
self.keyboard_focus = Some(WebSurfaceKeyboardFocusState {
tab_id: tab_id.clone(),
requested_url: requested_url.to_string(),
scroll_offset: state.scroll_offset,
click_point: state.point,
});
let surface = self.surface_mut(tab_id);
surface.typed_text = None;
surface.click_point = Some(state);
surface.mark_pending_input_started();
WebSurfaceInputOutcome::Applied
}
pub(super) fn record_typed_text(
&mut self,
tab_id: &TabId,
requested_url: &str,
text: &str,
) -> WebSurfaceInputOutcome {
if text.is_empty() {
return WebSurfaceInputOutcome::DroppedEmptyText;
}
let Some(focus) = self.keyboard_focus.as_ref() else {
return WebSurfaceInputOutcome::DroppedNoKeyboardFocus;
};
if focus.tab_id != *tab_id || focus.requested_url != requested_url {
return WebSurfaceInputOutcome::DroppedFocusMismatch;
}
let scroll_offset = focus.scroll_offset;
let click_point = focus.click_point;
let surface = self.surface_mut(tab_id);
let entry = surface.typed_text.get_or_insert_with(|| WebSurfaceTextInputState {
requested_url: requested_url.to_string(),
scroll_offset,
click_point,
text: String::new(),
});
if entry.requested_url != requested_url
|| entry.scroll_offset != scroll_offset
|| entry.click_point != click_point
{
*entry = WebSurfaceTextInputState {
requested_url: requested_url.to_string(),
scroll_offset,
click_point,
text: String::new(),
};
}
entry.text.push_str(text);
surface.mark_pending_input_started();
WebSurfaceInputOutcome::Applied
}
}