Skip to content

fix(browser-tab): paste into webview form fields via guest.paste() on cmd/ctrl+v#1088

Merged
jSydorowicz21 merged 3 commits into
rcfrom
fix/1063-browser-tab-native-paste
Jun 9, 2026
Merged

fix(browser-tab): paste into webview form fields via guest.paste() on cmd/ctrl+v#1088
jSydorowicz21 merged 3 commits into
rcfrom
fix/1063-browser-tab-native-paste

Conversation

@jSydorowicz21

@jSydorowicz21 jSydorowicz21 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #1063

Problem

Pasting (Cmd/Ctrl+V) clipboard text into a focused input/textarea/contenteditable inside an embedded browser tab (Electron <webview>) did nothing. The webview permission handler denies clipboard-read to webviews as a security boundary, so Chromium's native paste silently fails inside browser-tab form fields.

Fix

src/main/app-lifecycle/window-manager.ts, in the webview before-input-event handler:

  • Detect the paste chord (meta || control) && !alt && !shift && key === 'v', call event.preventDefault(), and invoke guest.paste() - a privileged main-process Electron call that performs the paste without granting the page web-facing clipboard-read access.
  • Removed v from the residual text-editing passthrough list (now handled explicitly) in both the native handler and the injected in-page capture listener, keeping the two layers consistent.

Security

The clipboard-read permission denial is left intact. guest.paste() fires only in response to a real user keystroke and does not grant the page clipboard-read, so a malicious page cannot read the clipboard on demand. Mirrors how the right-click Paste menu item works.

Tests

  • Added tests asserting meta+v and control+v each call guest.paste() once with preventDefault, and that plain v / Cmd+Shift+V do not.
  • Full vitest suite on this branch: identical failed-file set to pristine rc baseline (zero regression); window-manager.test.ts passes.

Notes

Summary by CodeRabbit

  • Bug Fixes

    • Cmd/Ctrl+V in embedded/guest browser views is now correctly intercepted to perform a privileged paste action; plain "v" and other shortcuts retain expected handling and forwarding.
  • Tests

    • Added tests covering paste shortcut interception and fallback script behavior across modifier combinations, including ensured non-paste cases are handled/forwarded appropriately.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 085204a4-954b-4a5d-b99c-e3fc9d1c431b

📥 Commits

Reviewing files that changed from the base of the PR and between 26893e0 and 2e1b36a.

📒 Files selected for processing (2)
  • src/__tests__/main/app-lifecycle/window-manager.test.ts
  • src/renderer/components/MainPanel/BrowserTabView.tsx

📝 Walkthrough

Walkthrough

This PR adds clipboard paste support for form fields inside Maestro browser-tab guests. The implementation defines a paste() method on the guest interface, intercepts Cmd/Ctrl+V in the main-process keyboard handler to invoke that method instead of forwarding a renderer shortcut, and keeps in-page shortcut logic synchronized so paste is not double-intercepted or passed through unexpectedly.

Changes

Paste Support for Browser-Tab Guests

Layer / File(s) Summary
Paste interface contract
src/main/app-lifecycle/window-manager.ts, src/__tests__/main/app-lifecycle/window-manager.test.ts
BrowserTabGuestContents declares a paste(): void method; test mocks stub the method to enable assertion of paste invocation.
Keyboard handler and in-page sync
src/main/app-lifecycle/window-manager.ts, src/renderer/components/MainPanel/BrowserTabView.tsx
The guest before-input-event handler adds a dedicated Cmd/Ctrl+V path that prevents default and calls guest.paste(), and the injected in-page keyboard handler is updated to exclude v from the text-editing pass-through set so main and renderer logic match.
Paste interception test coverage
src/__tests__/main/app-lifecycle/window-manager.test.ts
Tests drive before-input-event for Cmd+V/Ctrl+V and non-paste cases to assert preventDefault() behavior, guestWebContents.paste() invocation or lack thereof, renderer shortcut forwarding, and that the injected fallback script omits v passthrough.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

approved, ready to merge

🐰 I nibbled at code with a twitchy nose,
Cmd+V now hops where the clipboard goes.
Guests receive the paste, neat and small,
Main and renderer agree — no more stall.
Hooray! Clipboard treats forms like a ball.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: enabling paste into webview form fields via guest.paste() on Cmd/Ctrl+V, which is the core change across all three files.
Linked Issues check ✅ Passed The PR fully addresses issue #1063 requirements: native paste works via guest.paste() on Cmd/Ctrl+V into form fields, clipboard-read security is preserved, text-editing shortcuts are maintained, and comprehensive unit tests validate the paste behavior and exclude invalid cases.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #1063: paste implementation in window-manager.ts, test coverage in window-manager.test.ts, and aligned keyboard interception logic in BrowserTabView.tsx. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1063-browser-tab-native-paste

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes silent paste failures (Cmd/Ctrl+V) inside Electron <webview> browser-tab form fields by intercepting the paste chord in the before-input-event handler and calling guest.paste(), which is a privileged main-process API that bypasses the clipboard-read permission denial enforced on guest webviews.

  • window-manager.ts: Adds an isPaste guard that calls event.preventDefault() + guest.paste() for Cmd/Ctrl+V (no shift, no alt), and removes v from the text-editing passthrough string so it is not also forwarded to the renderer as an app shortcut.
  • BrowserTabView.tsx and the injected shortcutInjection script: Both in-page capture-phase listeners have v removed from their passthrough strings, keeping the two layers consistent with the new main-process interception path.
  • Tests: Two new integration-style tests verify that Cmd+V and Ctrl+V each call guest.paste() exactly once with preventDefault, that plain v passes through untouched, and that the injected script no longer contains v in the passthrough character set.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to the Cmd/Ctrl+V chord in the webview before-input-event handler and does not touch any auth, data persistence, or security-boundary code.

The fix is minimal and self-contained: one new guard, one explicit privileged call, and two consistent passthrough-list removals across the main-process and renderer injections. The clipboard-read permission denial is preserved; guest.paste() fires only on a real user keystroke. The new tests exercise both the happy path (Cmd+V, Ctrl+V) and the non-paste cases (plain v, Cmd+Shift+V), and vi.clearAllMocks() in beforeEach ensures clean mock state between the two new tests. No regressions observed.

No files require special attention — the renderer-side keyboardInjection string change in BrowserTabView.tsx is not covered by the new unit tests, but it is a straightforward one-character removal that mirrors the already-tested main-process change.

Important Files Changed

Filename Overview
src/main/app-lifecycle/window-manager.ts Adds isPaste guard in before-input-event handler calling guest.paste(); removes 'v' from isTextEditing passthrough and from the injected shortcutInjection script. Logic is correct and well-commented.
src/tests/main/app-lifecycle/window-manager.test.ts Two new tests cover Cmd+V / Ctrl+V calling guest.paste() with preventDefault, plain-v passthrough, Cmd+Shift+V forwarding as app shortcut, and injected script string content. vi.clearAllMocks() in beforeEach ensures mock isolation.
src/renderer/components/MainPanel/BrowserTabView.tsx Removes 'v' from the renderer-side capture-phase keyboardInjection passthrough string, keeping it consistent with the main-process changes. Comment updated to explain rationale. No behavioural change when before-input-event already handles Cmd+V.

Sequence Diagram

sequenceDiagram
    participant User
    participant Electron as Electron (Main)
    participant Guest as Guest WebContents (webview)
    participant Page as Page JS (injected listeners)
    participant Renderer as App Renderer

    User->>Electron: Cmd/Ctrl+V keydown
    Electron->>Electron: before-input-event fires
    Note over Electron: isPaste = true, event.preventDefault()
    Electron->>Guest: guest.paste()
    Note over Guest: Privileged paste — no clipboard-read needed
    Note over Page: Event never reaches page (preventDefault consumed it)

    User->>Electron: Cmd+A keydown
    Electron->>Electron: before-input-event fires
    Note over Electron: isTextEditing = true, return (no preventDefault)
    Electron->>Page: Event dispatched to page
    Note over Page: acxz.indexOf(a) !== -1, return (native select-all proceeds)

    User->>Electron: Cmd+Shift+V keydown
    Electron->>Electron: before-input-event fires
    Note over Electron: isPaste=false (shift=true), isTextEditing=false, event.preventDefault()
    Electron->>Renderer: "IPC browser-tab:shortcutKey {key:v, shift:true}"
Loading

Reviews (2): Last reviewed commit: "fix(browser-tab): align capture-phase ke..." | Re-trigger Greptile

Comment thread src/main/app-lifecycle/window-manager.ts
Comment on lines +1372 to +1384
// Cmd+Shift+V (paste-and-match-style etc.) is not the plain paste chord.
const shiftEvent = { preventDefault: vi.fn() };
beforeInputHandler?.(shiftEvent, {
type: 'keyDown',
key: 'v',
code: 'KeyV',
meta: true,
control: false,
alt: false,
shift: true,
});

expect(mockGuestWebContents.paste).not.toHaveBeenCalled();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Ctrl+Alt+V (AltGr+V) and Cmd+Alt+V edge cases untested

The isPaste guard explicitly requires !input.alt, so Ctrl+Alt+V (which on Windows/Linux AltGr keyboards produces @ or other characters, not a paste) correctly passes through. There is no test for this combination, however, so a future change to the modifier logic that accidentally drops the !alt guard would not be caught. Adding a Ctrl+Alt+V case to the "does not hijack" test would make this invariant explicit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/__tests__/main/app-lifecycle/window-manager.test.ts (1)

1337-1385: ⚡ Quick win

This test does not prove Cmd/Ctrl+Shift+V is not hijacked.

Line 1337 says non-paste edit chords should pass through, but the only assertion is that guest.paste() stays untouched. The current handler still calls preventDefault() and forwards browser-tab:shortcutKey for Shift+V, so this test passes even when the page loses the chord. Either assert no preventDefault/send, or rename the test to the narrower guest.paste() contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/main/app-lifecycle/window-manager.test.ts` around lines 1337 -
1385, The test currently only asserts that mockGuestWebContents.paste() is not
called but doesn't verify that the handler doesn't intercept the chord (calls
preventDefault or forwards shortcut); update the test for beforeInputHandler
(retrieved from guestWebContentsEventHandlers) for the Cmd/Ctrl+Shift+V case to
also assert the event.preventDefault was NOT called and that
mockGuestWebContents.send was NOT called with 'browser-tab:shortcutKey' (or, if
you prefer the narrower intent, rename the test to indicate it only asserts
guest.paste() is not invoked). Ensure you reference the same beforeInputHandler,
mockGuestWebContents, and preventDefault/send checks used elsewhere in this test
file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/app-lifecycle/window-manager.ts`:
- Around line 373-377: The renderer-side guest shortcut filter in
BrowserTabView.tsx still includes 'v' in the allowlist ('acvxz'), which
mismatches the window-manager filter that uses 'acxz'; update the allowlist
string in BrowserTabView (the shortcut filter logic around the guest interceptor
in BrowserTabView.tsx lines ~376-416) to remove 'v' so it becomes 'acxz', and
update the surrounding comment to match the main-process comment about letting
standard text-editing shortcuts pass through (explicitly noting that Cmd/Ctrl+F
and Cmd/Ctrl+V are handled differently as in window-manager.ts).

---

Nitpick comments:
In `@src/__tests__/main/app-lifecycle/window-manager.test.ts`:
- Around line 1337-1385: The test currently only asserts that
mockGuestWebContents.paste() is not called but doesn't verify that the handler
doesn't intercept the chord (calls preventDefault or forwards shortcut); update
the test for beforeInputHandler (retrieved from guestWebContentsEventHandlers)
for the Cmd/Ctrl+Shift+V case to also assert the event.preventDefault was NOT
called and that mockGuestWebContents.send was NOT called with
'browser-tab:shortcutKey' (or, if you prefer the narrower intent, rename the
test to indicate it only asserts guest.paste() is not invoked). Ensure you
reference the same beforeInputHandler, mockGuestWebContents, and
preventDefault/send checks used elsewhere in this test file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0a53050a-e724-44b8-ad6a-398c446ff298

📥 Commits

Reviewing files that changed from the base of the PR and between 3387051 and 26893e0.

📒 Files selected for processing (2)
  • src/__tests__/main/app-lifecycle/window-manager.test.ts
  • src/main/app-lifecycle/window-manager.ts

Comment thread src/main/app-lifecycle/window-manager.ts
@pedramamini

Copy link
Copy Markdown
Collaborator

@jSydorowicz21 Thanks for the contribution, and for the thorough writeup and tests! The core approach is solid: routing Cmd/Ctrl+V through the privileged guest.paste() while leaving the clipboard-read denial intact is the right way to fix #1063 without opening a clipboard-read hole, and mirroring the right-click Paste path is a nice touch.

One change before this is ready to merge:

Align the third allowlist in BrowserTabView.tsx (CodeRabbit's Major finding is valid).

This PR removes v from the 'acvxz' text-editing allowlist in both spots in window-manager.ts (the native before-input-event handler and the main-process-injected bubble-phase fallback). But there is a third copy of the same allowlist that the PR doesn't touch:

  • src/renderer/components/MainPanel/BrowserTabView.tsx:393 - var te=hasMod&&!hasAlt&&!e.shiftKey&&'acvxz'.indexOf(k)!==-1;

That one is the renderer-injected capture-phase keydown listener on the same guest webview (the comment at lines 376-384 references "the main-process-injected bubble-phase one"). It still treats Cmd/Ctrl+V as page-native, which now disagrees with the native handler and the bubble-phase fallback you did update. This is the exact inconsistency your own test guards against in the bubble-phase script (expect(injectedScript).not.toContain("'acvxz'.indexOf(k)")) with the rationale "the page-level fallback must also exclude V... Otherwise it can race the privileged paste path." The capture-phase listener actually fires before the bubble-phase one, so it deserves the same treatment.

Could you update BrowserTabView.tsx:393 to 'acxz' and adjust the comment block at lines 381-384 (it currently lists a/c/v/x/z as keeping native behavior, and should match the main-process explanation that Cmd/Ctrl+V is now handled separately, alongside Cmd/Ctrl+F)?

Minor / optional (not blocking):

  • Greptile (P2): consider adding a Ctrl+Alt+V (AltGr+V) case to the "does not hijack" test, so the !input.alt guard stays explicit if the modifier logic is ever refactored.
  • CodeRabbit (nitpick): the Cmd+Shift+V case in the "does not hijack" test only asserts guest.paste() isn't called. To match the test name, you could also assert preventDefault was not called and the chord wasn't forwarded via browser-tab:shortcutKey - or narrow the assertion's intent in the test name.

No merge conflicts, so nothing to rebase. Once the BrowserTabView.tsx allowlist is aligned I'm happy to approve. Thanks again!

@jSydorowicz21

Copy link
Copy Markdown
Contributor Author

Thanks @pedramamini - good catch on the third allowlist. Pushed 2e1b36a49:

  • src/renderer/components/MainPanel/BrowserTabView.tsx: the renderer capture-phase keydown listener's allowlist is now 'acxz' (was 'acvxz'), matching the native before-input-event handler and the bubble-phase fallback. Since the capture-phase listener fires first, this was the racing copy. Updated the adjacent comment to note that both Cmd/Ctrl+F and Cmd/Ctrl+V are intentionally excluded (F reaches the find bar, V is handled via the privileged guest.paste() path).
  • Confirmed via grep that no real allowlist uses 'acvxz' anymore anywhere in src/; the only remaining occurrence is the negative test assertion that guards against it.
  • Tightened the Cmd+Shift+V test: it now asserts paste is NOT driven while the chord is still consumed (preventDefault called + forwarded as browser-tab:shortcutKey), matching actual handler behavior.

tsc clean on touched files; window-manager.test.ts + BrowserTabView.test.tsx green (64/64). The optional Ctrl+Alt+V (AltGr) case I left untested - the !input.alt guard already excludes it, and adding a synthetic AltGr keydown test would mostly assert the guard tautologically; happy to add it if you'd prefer.

@jSydorowicz21

Copy link
Copy Markdown
Contributor Author

@greptile re-review

@jSydorowicz21 jSydorowicz21 merged commit f73abed into rc Jun 9, 2026
5 checks passed
@jSydorowicz21 jSydorowicz21 deleted the fix/1063-browser-tab-native-paste branch June 9, 2026 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants