diff --git a/Sources/Panels/CmuxWebView.swift b/Sources/Panels/CmuxWebView.swift index 68a13282..bcd77ed2 100644 --- a/Sources/Panels/CmuxWebView.swift +++ b/Sources/Panels/CmuxWebView.swift @@ -113,13 +113,19 @@ final class CmuxWebView: WKWebView { } override func performKeyEquivalent(with event: NSEvent) -> Bool { - // Preserve Cmd+Return/Enter for web content (e.g. editors/forms). Do not - // route it through app/menu key equivalents, which can trigger unintended actions. - let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) - if flags.contains(.command), event.keyCode == 36 || event.keyCode == 76 { + if event.keyCode == 36 || event.keyCode == 76 { + // Always bypass app/menu key-equivalent routing for Return/Enter so WebKit + // receives the keyDown path used by form submission handlers. return false } + let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + // Menu/app shortcut routing is only needed for Command equivalents + // (New Tab, Close Tab, tab switching, split commands, etc). + guard flags.contains(.command) else { + return super.performKeyEquivalent(with: event) + } + // Let the app menu handle key equivalents first (New Tab, Close Tab, tab switching, etc). if let menu = NSApp.mainMenu, menu.performKeyEquivalent(with: event) { return true diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 8af869e0..ca812481 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -149,6 +149,42 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase { XCTAssertTrue(spy.invoked) } + func testReturnDoesNotRouteToMainMenuWhenWebViewIsFirstResponder() { + let spy = ActionSpy() + installMenu(spy: spy, key: "\r", modifiers: []) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + let event = makeKeyDownEvent(key: "\r", modifiers: [], keyCode: 36) // kVK_Return + XCTAssertNotNil(event) + + XCTAssertFalse(webView.performKeyEquivalent(with: event!)) + XCTAssertFalse(spy.invoked) + } + + func testCmdReturnDoesNotRouteToMainMenuWhenWebViewIsFirstResponder() { + let spy = ActionSpy() + installMenu(spy: spy, key: "\r", modifiers: [.command]) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + let event = makeKeyDownEvent(key: "\r", modifiers: [.command], keyCode: 36) // kVK_Return + XCTAssertNotNil(event) + + XCTAssertFalse(webView.performKeyEquivalent(with: event!)) + XCTAssertFalse(spy.invoked) + } + + func testKeypadEnterDoesNotRouteToMainMenuWhenWebViewIsFirstResponder() { + let spy = ActionSpy() + installMenu(spy: spy, key: "\r", modifiers: []) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + let event = makeKeyDownEvent(key: "\r", modifiers: [], keyCode: 76) // kVK_ANSI_KeypadEnter + XCTAssertNotNil(event) + + XCTAssertFalse(webView.performKeyEquivalent(with: event!)) + XCTAssertFalse(spy.invoked) + } + @MainActor func testCanBlockFirstResponderAcquisitionWhenPaneIsUnfocused() { _ = NSApplication.shared