Merge #799: cancel the Ctrl+V keydown once the paste trap has the clipboard (Firefox double paste)

This commit is contained in:
Codeman maintainer committed 2026-11-11 10:21:44 +01:00
commit 1d7288dc4f
7 files changed
+126 -26

No files matched your search

+4 -2
View File
@@ -960,9 +960,11 @@ Feedback is silent on success except ONCE per page load (a feature that works by
Measured against a live install, one `Ctrl+V` each: Firefox delivers two paste events, Chromium and WebKit one. Handling both wrote the clipboard text to the PTY twice, which is why `Ctrl+V` pasted twice while right-click → Paste pasted once. That menu path involves no keydown, so route 2 cannot exist for it.
⚠️ **Route 2 alone is enough in all three engines, so route 1 is redundant where it can be measured.** Strip the `execCommand('paste')` call out and each of the three still delivers exactly one paste event to the trap, Firefox included. The call is kept anyway, because the trap technique was written for the mobile engines that a desktop measurement cannot reach, and a browser that resolves the key's default action against the element focused when the keydown began would send its paste to xterm's textarea instead. Text paste survives that on xterm's own `handlePasteEvent`; image paste does not, since the trap's listener is the only place clipboard image blobs are read. Removing the call is therefore a decision about mobile coverage, not a cleanup.
⚠️ **Once route 1 has delivered, the trap cancels the keydown.** Firefox dispatches the `execCommand` paste synchronously, inside the call. It delivers the key's own paste as a separate task about 10 ms later, measured in Playwright's Firefox. That later event races the `setTimeout(0)` that removes the trap and refocuses the terminal. When the timer wins, the event lands on xterm's textarea, and xterm's own `handlePasteEvent` sends the clipboard a second time; the trap's one-shot flag never sees that event. This race is why a one-shot flag alone left `Ctrl+V` pasting twice "sometimes". Both callers, the main pane's key handler and a tile's, pass the keydown as `target.keyEvent`. `_handleImagePaste()` calls its `preventDefault()` when the paste listener has already run by the time `execCommand` returns, so the browser never issues route 2. Chromium and WebKit dispatch nothing from route 1, so they keep the default action, which delivers their only paste event. ⚠️ Never cancel the keydown unconditionally: in those engines that loses the paste entirely.
The guard is a one-shot flag on each trap rather than a browser test or a reading of `execCommand`'s return value, so any count of events produces one insert. Tests: `test/image-paste-trap.test.ts`.
⚠️ **The `execCommand('paste')` call stays.** The trap technique was written for mobile engines that a desktop measurement cannot reach. A browser that resolves the key's default action against the element focused when the keydown began would send its paste to xterm's textarea. Text paste survives that on xterm's own `handlePasteEvent`; image paste does not, since the trap's listener is the only place clipboard image blobs are read. Removing the call is therefore a decision about mobile coverage, not a cleanup.
The one-shot flag on each trap stays as a second guard, so any count of events that reach the trap produces one insert. Tests: `test/image-paste-trap.test.ts`.
### Settings surface: App Settings, Session Options, Add Case