Compare commits

...

1 Commits

Author SHA1 Message Date
Shelley Vohr
1b11c304ba fix: dispatch ALT+SPACE keydown to renderer on Windows
Chromium's DesktopWindowTreeHostWin::HandleKeyEvent() intentionally
drops ALT+SPACE keydown events so that the subsequent WM_SYSCHAR
message can open the system menu. This means that when an Electron app
listens for 'system-context-menu' and calls preventDefault(), the
ALT+SPACE keydown never reaches the renderer and 'before-input-event'
never fires.

Override HandleKeyEvent() to intercept ALT+SPACE keydown and dispatch
it via SendEventToSink() so it flows through the normal input pipeline
to the renderer. The subsequent WM_SYSCHAR is still handled by
HandleIMEMessage() to emit 'system-context-menu', preserving existing
behavior for apps that don't prevent default.
2026-03-04 16:09:36 +01:00
4 changed files with 60 additions and 0 deletions

View File

@@ -175,6 +175,24 @@ bool ElectronDesktopWindowTreeHostWin::HandleIMEMessage(UINT message,
l_param, result);
}
void ElectronDesktopWindowTreeHostWin::HandleKeyEvent(ui::KeyEvent* event) {
// views::DesktopWindowTreeHostWin::HandleKeyEvent() discards ALT+SPACE
// keydown events so WM_SYSCHAR can show the system menu. In Electron,
// we want ALT+SPACE to be dispatched to the renderer so it fires keydown
// events, particularly when the 'system-context-menu' event is
// default-prevented. The subsequent WM_SYSCHAR is still handled by
// HandleIMEMessage() to emit 'system-context-menu'.
if ((event->type() == ui::EventType::kKeyPressed) &&
(event->key_code() == ui::VKEY_SPACE) &&
(event->flags() & ui::EF_ALT_DOWN) &&
!(event->flags() & ui::EF_CONTROL_DOWN)) {
SendEventToSink(event);
return;
}
views::DesktopWindowTreeHostWin::HandleKeyEvent(event);
}
void ElectronDesktopWindowTreeHostWin::HandleVisibilityChanged(bool visible) {
if (native_window_view_->widget())
native_window_view_->widget()->OnNativeWidgetVisibilityChanged(visible);

View File

@@ -48,6 +48,7 @@ class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
WPARAM w_param,
LPARAM l_param,
LRESULT* result) override;
void HandleKeyEvent(ui::KeyEvent* event) override;
void HandleVisibilityChanged(bool visible) override;
void SetAllowScreenshots(bool allow) override;
void Restore() override;

View File

@@ -2568,6 +2568,32 @@ describe('BrowserWindow module', () => {
});
});
ifdescribe(process.platform === 'win32')('BrowserWindow system-context-menu', () => {
it('dispatches the keyboard event to the renderer if prevented', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
w.once('system-context-menu', (evt) => {
evt.preventDefault();
});
await w.loadFile(path.join(fixtures, 'pages', 'keydown-alt-space.html'));
const altSpace = once(ipcMain, 'alt-space-renderer');
// Simulate Alt+Space to create system-context-menu event.
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Space', modifiers: ['alt'] });
w.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Space', modifiers: ['alt'] });
await altSpace;
});
});
ifdescribe(process.platform === 'win32')('BrowserWindow.{get|set}AccentColor', () => {
afterEach(closeAllWindows);

View File

@@ -0,0 +1,15 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const { ipcRenderer } = require('electron');
document.addEventListener("keydown", (e) => {
if (e.key === " " && e.altKey) {
ipcRenderer.send('alt-space-renderer');
}
});
</script>
</body>
</html>