From dabb965afd1952e01989f05f5944bb451d8956ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 18 May 2026 14:24:52 -0400 Subject: [PATCH] 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. --- crates/ely_app/src/shell/mod.rs | 1 + crates/ely_app/src/shell/web_surface.rs | 220 +---------------- crates/ely_app/src/shell/web_surface_input.rs | 222 ++++++++++++++++++ 3 files changed, 228 insertions(+), 215 deletions(-) create mode 100644 crates/ely_app/src/shell/web_surface_input.rs diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs index 10a5033..d1833a5 100644 --- a/crates/ely_app/src/shell/mod.rs +++ b/crates/ely_app/src/shell/mod.rs @@ -32,6 +32,7 @@ mod web_surface_cadence; mod web_surface_controller; mod web_surface_frame; mod web_surface_geometry; +mod web_surface_input; mod web_surface_keyboard; mod web_surface_metadata; mod web_surface_permissions; diff --git a/crates/ely_app/src/shell/web_surface.rs b/crates/ely_app/src/shell/web_surface.rs index c674b2f..3dd47b5 100644 --- a/crates/ely_app/src/shell/web_surface.rs +++ b/crates/ely_app/src/shell/web_surface.rs @@ -2,30 +2,27 @@ use std::collections::BTreeMap; use std::time::{Duration, Instant}; use ely_domain::{BrowserTab, TabId}; -use gpui::{Bounds, NativeSurfaceHandle, Pixels, Point}; use crate::services::ProfileDataMode; use super::{ web_surface_cadence::IDLE_POLL_INTERVAL, web_surface_frame::WebSurfaceFrame, - web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceSize}, web_surface_permissions::WebSurfaceSitePermission, web_surface_runtime::{WebSurfaceRuntime, WebSurfaceRuntimeFrame}, web_surface_state::{ - PerTabSurface, WebSurfaceClickState, WebSurfaceEnsureKey, WebSurfaceInputOutcome, - WebSurfaceKeyboardFocusState, WebSurfacePendingInput, WebSurfaceScrollState, - WebSurfaceState, WebSurfaceTextInputState, WebSurfaceTickResult, + PerTabSurface, WebSurfaceEnsureKey, WebSurfaceKeyboardFocusState, WebSurfacePendingInput, + WebSurfaceState, WebSurfaceTickResult, }, }; pub(super) struct WebSurfaceStore { runtime: WebSurfaceRuntime, /// Single owner of every per-tab invariant. See [`PerTabSurface`]. - surfaces: BTreeMap, + pub(super) surfaces: BTreeMap, /// Singleton because only one tab at a time holds keyboard focus /// across the whole window. Lives on the store, not per-tab. - keyboard_focus: Option, + pub(super) keyboard_focus: Option, } impl WebSurfaceStore { @@ -207,213 +204,6 @@ impl WebSurfaceStore { } } - pub(super) fn record_scroll_delta( - &mut self, - tab_id: &TabId, - requested_url: &str, - delta: Point, - position: Point, - 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, - 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, - 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, - 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, - 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( &mut self, 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) } diff --git a/crates/ely_app/src/shell/web_surface_input.rs b/crates/ely_app/src/shell/web_surface_input.rs new file mode 100644 index 0000000..29e585e --- /dev/null +++ b/crates/ely_app/src/shell/web_surface_input.rs @@ -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, + position: Point, + 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, + 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, + 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, + 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, + 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 + } +}