Electron InputEvent 对象详解:sendInputEvent 合成输入事件的完整参考与源码剖析
本文以 Electron 的 InputEvent 对象 为核心,完整梳理 type 与 modifiers 两个字段的全部取值,并结合 KeyboardInputEvent、MouseInputEvent、MouseWheelInputEvent 三个子类,讲清如何通过 webContents.sendInputEvent() 向页面注入键盘、鼠标与滚轮事件。文末进一步深入到 C++ 分发实现 与 gin 转换器,说明事件解析、修饰键别名映射与轮询相位补偿的底层细节,帮助你在自动化测试与页面控制场景中正确构造并发送合成输入事件。
一、InputEvent 是什么:基类结构与字段全表
InputEvent 是 Electron 中表示一次输入事件的基础结构体,所有具体的输入事件对象(键盘、鼠标、滚轮)都继承自它。它本身只有两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string |
事件类型。可取 undefined 或下表中列出的 40 个字符串之一 |
modifiers |
string[](可选) |
事件的修饰键数组,可取 17 个字符串值(见下表) |
type 字段的全部取值
type 的值按输入源可分为五组,完整枚举如下(依据 input-event.md):
| 分组 | 取值 |
|---|---|
| 鼠标事件 | mouseDown、mouseUp、mouseMove、mouseEnter、mouseLeave、contextMenu、mouseWheel |
| 键盘事件 | rawKeyDown、keyDown、keyUp、char |
| 手势滚动/缩放 | gestureScrollBegin、gestureScrollEnd、gestureScrollUpdate、gestureFlingStart、gestureFlingCancel、gesturePinchBegin、gesturePinchEnd、gesturePinchUpdate |
| 手势点按 | gestureTapDown、gestureShowPress、gestureTap、gestureTapCancel、gestureShortPress、gestureLongPress、gestureLongTap、gestureTwoFingerTap、gestureTapUnconfirmed、gestureDoubleTap |
| 触摸与指针 | touchStart、touchMove、touchEnd、touchCancel、touchScrollStarted、pointerDown、pointerUp、pointerMove、pointerRawUpdate、pointerCancel、pointerCausedUaAction |
modifiers 字段的全部取值
modifiers 是一个字符串数组,表示事件触发时处于按下/开启状态的修饰键:
| 取值 | 含义 |
|---|---|
shift |
Shift 键 |
control / ctrl |
Ctrl 键(两个名称等价) |
alt |
Alt 键 |
meta / command / cmd |
Meta 键(macOS 的 Command,三个名称等价) |
iskeypad |
按键来自小键盘 |
isautorepeat |
按键处于自动重复状态 |
leftbuttondown / middlebuttondown / rightbuttondown |
左/中/右鼠标按钮处于按下状态 |
capslock / numlock |
大写锁定 / 数字锁定开启 |
left / right |
事件与左/右键相关 |
继承体系:三个具体子类
InputEvent 是纯基类,实际传给 sendInputEvent 的是它的子类。三个子类的文档与基类字段之外的扩展字段如下:
MouseInputEvent(extends InputEvent):
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string |
可取 mouseDown、mouseUp、mouseEnter、mouseLeave、contextMenu、mouseWheel、mouseMove |
x / y |
Integer |
事件在页面中的坐标 |
button |
string(可选) |
按下的按钮:left、middle、right |
globalX / globalY |
Integer(可选) |
全局(屏幕)坐标 |
movementX / movementY |
Integer(可选) |
相对移动量 |
clickCount |
Integer(可选) |
点击计数 |
KeyboardInputEvent(extends InputEvent):
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string |
可取 rawKeyDown、keyDown、keyUp、char |
keyCode |
string |
将作为键盘事件发送的字符,只能使用合法的 Accelerator 键码(如 'a'、'Escape'、'Tab') |
MouseWheelInputEvent(extends MouseInputEvent):
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string |
只能为 mouseWheel |
deltaX / deltaY |
Integer(可选) |
滚动增量 |
wheelTicksX / wheelTicksY |
Integer(可选) |
滚轮刻度增量 |
accelerationRatioX / accelerationRatioY |
Integer(可选) |
加速度比 |
hasPreciseScrollingDeltas |
boolean(可选) |
是否为精确滚动增量 |
canScroll |
boolean(可选) |
页面是否可滚动 |
二、实战用法:webContents.sendInputEvent()
InputEvent 结构体的主要消费入口是 webContents.sendInputEvent(inputEvent):
const { app, BrowserWindow } = require('electron');
app.whenReady().then(() => {
const win = new BrowserWindow({ width: 800, height: 600 });
win.focus(); // 关键:窗口必须处于聚焦状态,sendInputEvent 才能生效
win.loadFile('index.html').then(() => {
const wc = win.webContents;
// 键盘:按 Ctrl+Shift+Z 组合键
wc.sendInputEvent({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] });
wc.sendInputEvent({ type: 'keyUp', keyCode: 'Z', modifiers: ['shift', 'ctrl'] });
// 键盘:输入一个字符(char 事件直接产生文本)
wc.sendInputEvent({ type: 'char', keyCode: 'a' });
// 鼠标:在 (100, 100) 位置模拟一次左键单击
wc.sendInputEvent({ type: 'mouseDown', button: 'left', x: 100, y: 100 });
wc.sendInputEvent({ type: 'mouseUp', button: 'left', x: 100, y: 100 });
// 滚轮:向下滚动
wc.sendInputEvent({ type: 'mouseWheel', x: 100, y: 100, deltaY: -120 });
});
});
要点说明:
- 参数类型:
inputEvent接受 MouseInputEvent | MouseWheelInputEvent | KeyboardInputEvent 三种对象之一,它们都包含InputEvent基类的type/modifiers字段。 - 聚焦前提:官方文档明确提示,
sendInputEvent()生效要求包含该内容的BrowserWindow处于聚焦状态(见 web-contents.md 中的 NOTE)。这是使用中最容易踩的坑:无头测试或隐藏窗口场景下需先focus()。 - keyCode 的合法性:
KeyboardInputEvent.keyCode必须使用合法的 Accelerator 键码(如'A'、'Escape'、'Tab'),否则事件构造会失败。 - webview 标签页同样支持:
<webview>.sendInputEvent(event)直接转发到webContents.sendInputEvent。sendInputEvent在 webview 同步方法白名单中被归类为异步方法,见 web-view-methods.ts。
三、源码剖析:事件是如何被解析与分发的
3.1 type 字符串 → blink 枚举的转换
类型解析发生在 blink_converter.cc:Converter<blink::WebInputEvent::Type>::FromV8 通过 BLINK_EVENT_TYPES() 宏将 JS 侧的 type 字符串逐个映射到 blink::WebInputEvent::Type 枚举(如 mouseDown → kMouseDown、keyDown → kKeyDown)。两个值得注意的实现细节:
- 匹配不区分大小写:宏内使用
base::EqualsCaseInsensitiveASCII比较,因此'Keydown'与'keyDown'等价。 - 枚举覆盖面比 API 更大:宏表包含了上文第一组表格中的全部 40 个类型(含 gesture、touch、pointer 系列),即所有
type取值都能被解析,解析器本身不会报错。
随后 GetWebInputEventType 从事件对象中取出 type 字段用于分发决策;Converter<blink::WebInputEvent>::FromV8 再把 modifiers 数组按位或合并为 blink::WebInputEvent::Modifiers 位掩码,并由 C++ 侧自动填充时间戳 base::TimeTicks::Now(),JS 调用者无需也不能指定时间戳。
3.2 modifiers 的名称规范与别名映射
修饰键的字符串名与位掩码的映射定义在 blink_converter.cc,分两张表:
- 规范表(既可传入也可返回):
shift、control、alt、meta、iskeypad、isautorepeat、leftbuttondown、middlebuttondown、rightbuttondown、capslock、numlock、left、right。 - 别名字典(只接受、不返回):
cmd与command都映射到meta,ctrl映射到control。
这说明源码层面 ctrl/command/cmd 只是为书写习惯提供的别名,事件对象序列化回 JS 时只会输出规范名。另外从 ToV8 实现 可以看出,键盘/鼠标事件会转换为对应子类对象返回,其余类型则退化为只含 type 与 modifiers 的普通对象——这也解释了为什么基类文档只定义这两个字段。
3.3 SendInputEvent 的三条分发路径
C++ 侧入口是 WebContents::SendInputEvent(经 第 5043 行 注册为 JS 方法 sendInputEvent)。它先解析 type,再走三条互斥路径:
路径一:鼠标事件(IsMouseEventType)
转换为 blink::WebMouseEvent 后,若 WebContents 是离屏渲染(OSR)模式,走 GetOffScreenRenderWidgetHostView()->SendMouseEvent();否则调用 rwh->ForwardMouseEvent() 转发给 content::RenderWidgetHost。
路径二:键盘事件(IsKeyboardEventType)
构造 input::NativeWebKeyboardEvent,其中有一个向后兼容行为:如果传入 type: 'keyDown',C++ 侧会静默将其改写为 rawKeyDown 再转发(源码注释标明是为兼容旧用法,见 第 3963–3966 行)。键盘事件的 keyCode 字符串还会经 KeyboardCodeFromStr 解析为 ui::KeyboardCode,并推导 dom_code / dom_key;对 char 与 rawKeyDown 类型,当键码对应的是可打印字符(如 '+'、空格)时会用字符本身而非键名,保证页面收到正确的文本(blink_converter.cc 第 311–319 行)。
路径三:滚轮事件(kMouseWheel)
这是实现中最精巧的一处。Chromium 期望滚轮事件携带完整的相位(phase)信息并对此做 DCHECK 校验,因此非 OSR 模式下,SendInputEvent 会做如下处理(第 3976–3990 行):
- 把用户事件标记为
phase = kPhaseBegan、dispatch_type = kBlocking后转发; - 紧接着再合成一条
delta_x/delta_y均为 0 的kPhaseEnded事件(dispatch_type = kEventNonBlocking)以结束本次滚动序列。
也就是说,JS 侧发一条 mouseWheel 事件,C++ 侧实际向渲染进程投递了两条事件。这一补偿逻辑是页面滚动行为“一次到位”、不会停在中间相位的原因。
兜底:三条路径的 ConvertFromV8 全部失败时,会抛出 Invalid event object 异常(第 3996–3997 行)。从源码结构看,虽然 40 种 type 都能通过解析,但 SendInputEvent 只实际分发鼠标、键盘、滚轮三类;gesture/touch/pointer 类型目前会走到兜底逻辑,构造这类对象传入不会得到有效分发。
3.4 测试用例中的典型用法
仓库测试套件大量使用 sendInputEvent 驱动页面行为,可作为构造事件的真实参照:
- api-web-contents-spec.ts:
describe('sendInputEvent(event)')覆盖组合键({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })、char输入、以及'Space'/'Plus'这类字符键的处理; - api-browser-window-spec.ts:用
{ type: 'keyDown', keyCode: 'Escape' }触发页面行为; - chromium-spec.ts:构造
Tab/Shift+Tab按键事件验证焦点在窗口与 webview 之间的移动; - autofill-spec.ts:用
Tab键切换表单焦点后依次发送char事件填写自动补全字段。
这些用例共同印证了实战规律:键盘输入通常由 keyDown + char + keyUp 组合模拟,点击由 mouseDown + mouseUp 成对模拟。
四、使用注意事项小结
- 窗口必须先聚焦:
BrowserWindow未聚焦时sendInputEvent可能不产生效果,自动化脚本中记得在发送前win.focus()。 - keyCode 必须合法:只能使用 Accelerator 支持的键码字符串,
char类型用于向可编辑元素输入文本,keyDown/keyUp用于触发按键处理逻辑。 keyDown会被改写为rawKeyDown:从源码看这是刻意的向后兼容行为,依赖e.type === 'keydown'的页面逻辑需注意区分。- 修饰键别名:
ctrl、command、cmd可自由书写,但规范名是control、meta;事件序列化回 JS 时只会返回规范名。 - 滚轮事件是成对投递的:单条
mouseWheel输入会在 C++ 侧展开为kPhaseBegan+ 合成kPhaseEnded两条事件,页面收到的滚动是完整闭环的。 - 坐标语义:
x/y是页面坐标,globalX/globalY是屏幕全局坐标,两者不可混用。
通过本文,你可以完整掌握 InputEvent 基类与三个子类的全部字段、sendInputEvent 的正确调用姿势,以及从 JS 对象到 blink::WebInputEvent 的解析与分发链路,从而在 Electron 应用开发与自动化测试中可靠地构造合成输入事件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00