fix(desktop): mouse back and forward stay inside the browser tab#33276

merged13
main
fix/issue-33259
opened4d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 1h ago

Problem

A mouse Back press over page content in the desktop app's Browser tab could send the main chat back to the previous thread and leave the tab where it was. The guest preload cancelled thumb buttons, but it runs only in the guest's main frame. Any press it did not cancel reached Chromium's fallback, which navigates the outermost WebContents: for a <webview> guest, the host app. Reproduced on Linux X11 with real thumb-button input over a same-origin iframe, a cross-site iframe, and an iframe on a tab with no back history. The reporter also sees it over plain content on GNOME Wayland.

Change

  • The main process listens for before-mouse-event on each Browser tab guest, next to the existing before-input-event listener. Electron emits it for every widget in the guest, iframes included, before the renderer sees the event.
  • The listener drops thumb-button mouseDown/mouseUp, so Chromium's fallback never runs. On mouseUp it moves that tab back or forward when its history allows. At either end of the tab's history the press is still dropped and nothing navigates.
  • A dropped press never reaches the page, so the main process replays it to the guest preload. The preload feeds the existing human-input signal and the recording "Show mouse presses" marker, as a native press over the page did before. It skips presses while an iframe is hovered: their coordinates are frame-local, and the old preload never saw iframe presses either. A replayed press is always released, at its own point when the release lands over an iframe, so the press marker never stays held.
  • The preload's thumb-button listeners and the preview:mouse-navigate IPC channel were the old renderer-side guard and are removed.

Scope and approval

Fixes pingdotgg/t3code#33259

Triaged via the automated issue flow. The panel-chrome half of this symptom is #30854, fixed separately in #30882. This PR covers only page content inside the Browser tab.

Verification

  • vp test run apps/desktop/src/preview/Manager.test.ts: 68/68. PreviewManager > keeps mouse back and forward buttons inside the preview tab fails on main (guest has no before-mouse-event listener). It covers down/up, Back at the start of tab history (still dropped, no navigation), ordinary buttons passing through, and the zoom-corrected replay payload.
  • vp run check:changed: pass.
  • Two adversarial review rounds; round 1 found the held press marker on a release over an iframe, fixed in 18ae8285a4. apps/desktop/src/preview is otherwise unchanged since the captures below.
  • Runtime: real Electron 44.10.1 desktop app (vp run dev:desktop) on Xvfb/X11, 1920×900 window, DPR 1, light theme, real xdotool input (button 8 = Back, 9 = Forward), fresh synthetic state (T3CODE_DEV_SEED=0). Host history thread A → thread B; thread B's Browser tab on p2 with p1 behind it, with a same-origin and a cross-site (localhost vs 127.0.0.1) iframe. An external CDP readout under the window shows the host thread and the guest URL. Before = 934c7f8d8e (main), after = 385579457a.
CaseBefore (main)After
Back over plain contentvideo: chat B, tab → p1video: chat B, tab → p1
Back in same-origin iframevideo: chat → A, tab stays p2video: chat B, tab → p1
Back in cross-site iframevideo: chat → A, tab stays p2video: chat B, tab → p1
Back in iframe, tab has no back historyvideo: chat → Avideo: nothing moves
Recording, "Show mouse presses", Back over contentvideo: press ripple, tab → p1video: press ripple, tab → p1, saved recording
Recording, Back in cross-site iframe—video: tab → p1, no misplaced marker
Forward in cross-site iframe—video: chat B, tab → p2
Composer focused, pointer over tab, Back—video: chat B, tab → p1
Back held 1 s, then released—video: nothing while held, tab → p1 on release
Element picker active, Back in iframe—video: tab → p1, picker closes
Back over the main chat—video: chat → A (app navigation unchanged)

Before (main): Back in a cross-site iframe sends the chat to thread A. After: the chat stays on B and the tab shows p1.

What else changed: nothing beyond which surface navigates and the restored recording press marker. Settled stills for the Back rows have zero-threshold and 5% diffs; besides navigated page content, synthetic IDs, relative ages and antialiasing, the R1/R2/R7 pairs show a different title-bar updater icon, which comes from the two dev builds' updater state (this diff touches no updater code). Those rows are proven by their recordings. Full evidence, diffs, transcripts, seed and driver scripts: evidence report.

Known limits:

  • Pages in the Browser tab no longer receive thumb-button events (buttons 3/4), iframes included; the press is consumed by the tab's own Back/Forward.
  • A thumb press inside an iframe that sits in a shadow root can draw the recording press marker at the frame's local position. Navigation is unaffected.
  • Not yet captured: Forward over plain content, ends of tab history over plain content, non-unit page zoom, recording with markers off, and a recorded press released over an iframe (audit matrix).
  • A thumb press inside an iframe shows no recording press marker (iframe presses never did). Right after a page loads with the pointer already resting on a cross-site iframe, a press before the pointer moves can draw the marker at the iframe's local position, because Chromium reports no hover until the pointer moves. Navigation is unaffected either way.
  • cannot-verify wayland: the reporter's GNOME Wayland session, where plain content also misbehaves. The fix drops the press in the main process before any frame's renderer sees it, which covers any press that reaches the guest.
  • cannot-verify macos, cannot-verify windows: not run. The change uses the same Electron event on every platform.

AI-assisted; verified with the tests and tools listed above.

Checks

22 successful · 20 skipped
All checks passed
4e58cde

Added 3 commits

  • fix(desktop): mouse back and forward stay inside the browser tab4f97cbf
  • Merge remote-tracking branch 'origin/main' into fix/issue-332596594964
  • test(desktop): reproduce #33259 mouse back leaving the preview tab6d31892

[GPT-6.1 Sol][Codex] on behalf of shivam.

Handoff from the issue-watch investigation at main c73b38b379542cde87ec8bc855b70e7fae78fb5c:

  • Not reproduced. We stopped before creating the two test threads or opening a Browser guest. No thumb-button input was sent, no regression tests ran, and we did not test this PR.
  • On physical corex, Ubuntu Linux x86_64, vp run build:desktop passed, reporting 5/5 successful, with Node 26.12.1. The first attempt under Node 22.22.1 failed with ERR_UNKNOWN_FILE_EXTENSION for packages/shared/src/devProxy.ts.
  • The built Electron 44.10.1 desktop app launched through native Ozone Wayland using an isolated headless Sway compositor and test profile. DISPLAY was unset; the runtime log showed actual Wayland surface traffic. This confirms a native Wayland test environment is available on corex, but it does not exercise the reporter's GNOME compositor.
  • Read-only tracing found the preload runs only in the main frame and that Chromium's unconsumed thumb-button release navigates the outermost WebContents, consistent with the iframe path described here. We did not establish the reporter's plain-content Wayland trigger.

No before/after recording or navigation evidence was captured. All task-owned processes and the host test directory have been cleaned up; no source changes or pushes were made.

Synced from GitHub

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

  • Last reviewed commit: 385579457add6399b53fc79cfc2f01309346250d
  • Latest commit, not reviewed: 4e58cde007308a022737345d6b8f3f417012f7a3

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

PreviewManager now intercepts guest back and forward mouse buttons through Electron’s before-mouse-event listener. It prevents default host navigation and navigates guest history on release when available. The guest receives button events for pointer recording through a new IPC channel.

Changes: Guest mouse navigation

Layer / File(s)Summary
Intercept and route thumb-button events
apps/desktop/src/preview/GuestProtocol.ts, apps/desktop/src/preview/Manager.ts
PreviewManager intercepts back and forward button presses and releases, prevents default host navigation, and sends the phase, zoom-adjusted coordinates, and button code to the guest. On release, it navigates guest history when available. The previous mouse-navigation IPC listener is removed.
Replay presses and validate behavior
apps/desktop/src/preview/PickPreload.ts, apps/desktop/src/preview/RecordingCursor.ts, apps/desktop/src/preview/Manager.test.ts
PickPreload validates thumb-press events and forwards valid phases to RecordingCursor. RecordingCursor exposes a press method for recording pointer down and up events. Tests cover button interception, history behavior, and coordinate scaling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Possibly related issues

  • pingdotgg/t3code#33307 — This issue describes macOS swipe gestures, which use a separate event path from the mouse-button events handled here.
  • pingdotgg/t3code#30854 — This issue concerns Back and Forward input over the right-panel chrome. This change handles guest page-content events.

Suggested reviewers: shivamhwp, thatsnoi

Merge Risk: External image Minimal · previous review 38557

Thumb buttons are routed to Browser-tab history. No merge-blocking defect is established.

Pre-merge checks: Passed 4

Check nameStatusExplanation
Title checkPassedThe title clearly summarizes the main change: mouse Back and Forward actions stay within the desktop Browser tab.
Linked Issues checkPassed[#33259] requires mouse Back over Browser-tab page content to navigate the tab without changing the chat thread. The reviewed implementation handles guest before-mouse-event thumb-button events in t…
Out of Scope Changes checkPassedThe THUMB_PRESS_CHANNEL forwarding and RecordingCursor.press changes preserve human-input and recording behavior after the main process consumes the native thumb-button events. The supplied summar…
Description checkPassedThe description covers the problem, change, scope and approval, and detailed verification, including known limits. It notes AI assistance but does not identify the model and harness as requested by th…
Full details: Linked Issues check

Explanation

[#33259] requires mouse Back over Browser-tab page content to navigate the tab without changing the chat thread. The reviewed implementation handles guest before-mouse-event thumb-button events in the main process and navigates guest history on release. The PR reports runtime checks for page content and same-origin and cross-site iframes. The new Manager.test.ts coverage checks Back and Forward interception. Wayland was not tested, but the implementation addresses the renderer-cancellation gap identified by the issue by handling the event in the main process.

Full details: Out of Scope Changes check

Explanation

The THUMB_PRESS_CHANNEL forwarding and RecordingCursor.press changes preserve human-input and recording behavior after the main process consumes the native thumb-button events. The supplied summary and tests connect these changes to the interception. No unrelated changes are evident.

Full details: Description check

Explanation

The description covers the problem, change, scope and approval, and detailed verification, including known limits. It notes AI assistance but does not identify the model and harness as requested by the template.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
External image Recent review info

External image Run configuration

External image Commits

Reviewing files that changed from the base of the PR and between 1e13ec05da82592718baae75094cc24714bf6f29 and 385579457add6399b53fc79cfc2f01309346250d.

External image Files selected for processing: 1

  • apps/desktop/src/preview/PickPreload.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Synced from GitHub

Approvability

Verdict: Approved at 4e58cde

Macroscope's review found this PR approvable — Mouse back and forward now navigate the preview tab rather than the host application, while preserving input and recording-cursor feedback. The change is narrowly scoped to existing preview gestures and introduces no material operational or data risk.

You can add or adjust custom eligibility rules. Learn more.

Added 2 commits

  • Merge remote-tracking branch 'origin/main' into pr-3327657b9afb
  • test(desktop): thumb Back at the start of tab history stays in the tab120cdf7

Added 1 commit

  • fix(desktop): thumb presses still reach recordings and human input1e13ec0

Added 1 commit

  • fix(desktop): replay thumb presses only over the guest's own page3855794

Added 3 commits

  • Merge remote-tracking branch 'origin/main' into pr-33276ccad52e
  • Merge remote-tracking branch 'origin/main' into pr-33276a543452
  • fix(desktop): release a replayed thumb press even when it ends over a frame18ae828

Added 1 commit

  • Merge remote-tracking branch 'origin/main' into fix/issue-332590699332

Evidence

Both sides: real Electron 44.10.1 on Linux X11 under headless-desktop, 1600×900 window, light theme, fresh isolated databases and identical synthetic seeds. Native XTest mouse buttons 8/9; no DOM-dispatched mouse events. Recordings exclude the composer and mask fixture setup panels for privacy.

  1. Open Thread A, then B. Open Browser p1 and click through to p2; both contain a same-origin iframe.
  2. Hold/release Back inside the iframe, then hold/release Forward at the same coordinates.
  3. Open A then B, replace the Browser fixture, clear its history, verify no Back/Forward history, and repeat both buttons.

Driver · RPC seed · Fixture server · Capture renderer · Reproduction notes · Base observations · Head observations

Boundary setup explicitly clears webview history because a newly opened tab retained an initial blank entry. Screenshots were taken after matching pixels 500 ms apart; recordings show the interaction. AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Physical mouse hardware was unavailable; native X11 XTest events exercised the real Electron input path.
  • Cross-site iframes and main-frame content were not separately recorded; these runs cover same-origin iframe content.
  • Guest recording-cursor replay, zoom-coordinate handling, ordinary mouse buttons, and other operating systems were not exercised.
  • The no-history case was established with Electron webview.clearHistory(); a newly created tab alone retained an initial blank history entry.

Added 1 commit

  • Merge remote-tracking branch 'origin/main' into fix/issue-33259c281ec2
apps/desktop/src/preview/PickPreload.tslines 269–269Outdated · c281ec2Open thread
……@@ -249,34 +249,27 @@ const isolateOverlayKey = (event: KeyboardEvent): void => {
266+ }
267+ if (inFrame) return;
268+ replayedThumbPress = { x, y };
269+ ipcRenderer.send(HUMAN_INPUT_CHANNEL, { kind: "pointer", x, y, button });
MmacroscopeappBoton lines 269–2692h ago

🟡 Medium preview/PickPreload.ts:269

Back/Forward presses replayed here do not dismiss an open host menu or popover when guest focus does not change, because consuming the native before-mouse-event suppresses the page event and this replay omits POINTER_DOWN_HOST_CHANNEL. Send the host pointer-down notification alongside the human-input signal so HostedBrowserWebview can dismiss it.

No longer relevant as of 4e58cde007308a022737345d6b8f3f417012f7a3

Added 1 commit

  • fix(desktop): replayed thumb presses still dismiss host menus4e58cde

Evidence

Real Linux X11 desktop app, Electron 44.10.1 / Chromium 152.0.7977.169, 1600×900, light theme, fresh synthetic data on each revision. Native XTEST buttons 8/9 were pressed and released separately. No DOM thumb events or navigation IPC were injected.

Same driver on both revisions: open A then B; Browser p1→p2; hold/release Back inside the iframe; reopen B unconditionally to inspect its preserved tab; hold/release Forward. Then create a new boundary tab, confirm Back is disabled, and hold/release Back inside its iframe. Repeat with cross-site and same-origin frames. Separate recordings exercise both thumb buttons with the real Preview menu and Browser recorder enabled.

Base changes chat to A on Back release while its tab remains on p2. Head keeps chat on B and navigates only the tab, on release. At either iframe history boundary, head leaves both surfaces unchanged. Cross-site frames were confirmed as separate iframe targets. Boundary clips include fresh-tab setup and the complete interaction, providing independent evidence.

ObservationBeforeAfter
Commit1ea523608b4e58cde007
Complete run: both iframe types, Back/Forward and boundariesBefore recordingAfter recording
Cross-site Back releasedBefore: chat incorrectly opens Thread AAfter: Thread B stays open and Browser returns to p1
Inspect tab after BackBefore: reopening B reveals Browser still on p2Browser returned to p1, shown above; Forward then returns it to p2 while B stays selected.
Cross-site no-history boundaryBefore continuous boundary sequenceAfter continuous boundary sequence
Same-origin no-history boundaryBefore continuous boundary sequenceAfter continuous boundary sequence
Both thumb buttons: menu dismissal and human-input signalsBefore desktop recordingAfter desktop recording
Actual app-exported Browser recordingBefore thumb-press recordingAfter replayed thumb-press recording
Press ring extracted from app recordingBefore: thumb press drawn in recordingAfter: consumed thumb press still drawn in recording

On head, both consumed thumb presses dismiss the actual Preview menu and produce human-control plus recording down/up signals. The fixture HUD only mirrors genuine read-only desktopBridge events; it does not generate them. The rings above come from the app's exported video.

Capture driver, seed, fixture and reproduction instructions.

Supporting observations: base navigation, head navigation, base cross-site targets, head cross-site targets, base thumb events, head thumb events.