Skip to content

browser: Virtualize sidebar tab list and pre-collect tab render data#57

Open
Yabuku-xD wants to merge 2 commits into
Glass-HQ:mainfrom
Yabuku-xD:perf/sidebar-virtualization
Open

browser: Virtualize sidebar tab list and pre-collect tab render data#57
Yabuku-xD wants to merge 2 commits into
Glass-HQ:mainfrom
Yabuku-xD:perf/sidebar-virtualization

Conversation

@Yabuku-xD
Copy link
Copy Markdown

Summary

Two improvements to the non-macOS tab rendering path. (macOS uses the native ModeNavigationHost path and is unaffected.)

Sidebar virtualization (render_sidebar, BrowserView):

  • Replaces the previous overflow_y_scroll container (which built element trees for all tabs on every paint) with gpui::uniform_list, which renders only the rows visible in the viewport. With 50 tabs and ~10 visible at a time, element construction drops from O(n) → O(visible) per paint frame.
  • Adds UniformListScrollHandle to BrowserView to preserve scroll position across renders.
  • Splits the sidebar into a fixed pinned-tab grid (always visible, never scrolled) and the virtualized unpinned list. Pinned tab data is pre-collected once via collect_tab_row_data() before the element tree is built.
  • Extracts per-row rendering into render_sidebar_tab_rows() — the uniform_list processor called lazily for visible rows only.

Tab strip data pre-collection (render_tab_strip):

  • Adds TabRowData struct and collect_tab_row_data() helper to snapshot all tab state in a single pass (one entity map read per tab). Both the pinned dock and unpinned strip now iterate a pre-collected Vec instead of calling tab.read(cx) mid-render.

Test plan

  • cargo check -p browser passes with no errors
  • Sidebar scrolls correctly with 10+ tabs (scroll position preserved across renders)
  • Pinned tabs appear above the scrollable list and don't scroll away
  • Tab hover, close, and right-click context menu work correctly in both views
  • New tab button remains pinned at the bottom

Release Notes:

  • N/A

@Yabuku-xD Yabuku-xD requested a review from naaiyy as a code owner April 21, 2026 23:45
@github-actions github-actions Bot added the first contribution Opened by a first-time contributor label Apr 21, 2026
@Yabuku-xD Yabuku-xD force-pushed the perf/sidebar-virtualization branch from bcbbd7b to f051be1 Compare April 22, 2026 00:38
Four targeted improvements to the browser crate's rendering pipeline:

1. Gate FrameReady on suspension state (tab.rs): Skip emitting
   TabEvent::FrameReady for suspended tabs. CEF already stops rendering
   hidden browsers, but this prevents any in-flight frames from
   propagating cx.notify() after suspension.

2. Gate FrameReady cx.notify() on surface visibility (browser_view.rs):
   Only trigger a re-render when BrowserSurfaceState is Visible. In-flight
   FrameReady events queued just before a mode switch no longer cause
   spurious layout work on the hidden browser view.

3. Split RenderState Mutex (render_handler.rs, tab.rs, client.rs,
   life_span_handler.rs): Extract current_frame into its own
   Arc<Mutex<Option<CVPixelBuffer>>> separate from the geometry fields
   (width, height, scale_factor). CEF's view_rect and screen_info
   callbacks no longer share a lock with the 60fps frame write path,
   eliminating cross-contention between geometry queries and frame writes.

4. Compute pinned_count once per render pass (tab_strip.rs,
   browser_view.rs): Both render_tab_strip and render_sidebar previously
   ran an independent O(n) tab scan to count pinned tabs. The count is
   now computed once in the render() method via a pinned_tab_count()
   helper and passed as a parameter to both render methods.
Two improvements to the non-macOS tab rendering path:

Sidebar virtualization (render_sidebar):
- Replace the O(n) unpinned tab list with gpui::uniform_list, which
  renders only the rows visible in the viewport. With 50 tabs and ~10
  visible, element construction drops from O(50) to O(10-15) per paint
  frame. A UniformListScrollHandle field is added to BrowserView to
  track scroll state across renders.
- Split the sidebar into a fixed pinned-tab grid (always visible, never
  scrolled) and a virtualized unpinned list. Pinned tab data is
  pre-collected once via collect_tab_row_data() before the element tree
  is built.
- Extract the per-row rendering logic into render_sidebar_tab_rows(),
  the uniform_list processor called lazily for visible rows only.

Tab strip data pre-collection (render_tab_strip):
- Add TabRowData struct and collect_tab_row_data() helper to snapshot
  all tab state in a single pass (one entity read per tab). Both the
  pinned dock and unpinned strip iterate the pre-collected Vec instead
  of re-reading entity state mid-render.
@Yabuku-xD Yabuku-xD force-pushed the perf/sidebar-virtualization branch from f051be1 to 66efada Compare April 22, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

first contribution Opened by a first-time contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants