1. 简介
在 KeyboardEvent
上,code
属性编码了一个值,表示被按下按键的物理位置。该值忽略当前的区域(如 "en-US")、布局(如 "dvorak")和修饰键状态(如 "Shift +
Control"),因此非常适合那些希望将键盘作为通用按钮集来使用的应用(如游戏)。code
属性的理念是为每个物理键提供一个平台无关的 扫描码。
key
属性则包含了按键按下后生成的值,会受到区域、布局和修饰键的影响。几乎每个 Unicode 字符都是有效的 `key` 属性值,同时还包含许多特殊的命名值(见 KeyboardEvent
key 属性值),因此可能的 key
值有成千上万种。
由于大多数用户拥有与其区域及布局匹配的物理键盘,我们合理地假设 key
值可以很好地代表印在按键上的字符。当用户选择了不同的键盘布局时,这一假设虽然不再成立,但此时用户也很清楚实际输出字符和键帽印刷不符的情况。
本文描述的 API 提供了一种简单方法,从基础的 code
获取 key
的映射。
2. 键盘映射 API
键盘映射 API 扩展了 Keyboard
接口,增加了与当前键盘布局相关的属性和方法。
2.1. Navigator 接口
keyboard
属性在 Navigator
对象上的定义详见 Keyboard-Lock。
2.2. KeyboardLayoutMap 接口
当前仅有一个引擎支持。
Opera55+Edge79+
Edge(旧版)不支持IE不支持
Firefox for Android不支持iOS Safari不支持Chrome for Android69+Android WebView69+Samsung Internet10.0+Opera Mobile48+
[Exposed =Window ]interface {KeyboardLayoutMap readonly maplike <DOMString ,DOMString >; };
KeyboardLayoutMap
是一个只读集合,用于将 code
值映射为 key
值。
2.3. Keyboard 接口
当前仅有一个引擎支持。
Opera55+Edge79+
Edge(旧版)不支持IE不支持
Firefox for Android不支持iOS Safari不支持Chrome for Android68+Android WebView68+Samsung Internet10.0+Opera Mobile48+
partial interface Keyboard {Promise <KeyboardLayoutMap >();getLayoutMap attribute EventHandler ; };onlayoutchange
注: 基础的 Keyboard
接口定义见 [Keyboard-Lock]。
2.3.1. getLayoutMap()
当前仅有一个引擎支持。
Opera56+Edge79+
Edge(旧版)不支持IE不支持
Firefox for Android不支持iOS Safari不支持Chrome for Android69+Android WebView69+Samsung Internet10.0+Opera Mobile48+
调用 getLayoutMap() 时,用户代理必须执行以下步骤:
-
令 p 为一个新的
Promise。 -
如果 this 的 相关全局对象 的 关联文档不被允许使用名为 "keyboard-map" 的 策略控制特性,
-
拒绝 p,错误为 "
SecurityError"DOMException。 -
返回 p
-
-
按下列步骤 并行执行:
-
令 map 为一个新的,初始为空的
KeyboardLayoutMap。 -
遍历 书写系统按键表格的 "KeyboardEvent code" 列中的每个值 code:
-
用 map 解决 p。
-
-
返回 p。
用户代理可自行选择对 map 进行缓存,只要在键盘布局更改时更新(或失效)缓存内容即可。
2.4. 死键与组合字符
由于键盘映射 API 的目的是为用户键盘上的键提供可读描述,死键或 组合字符需要转换为可以直接使用的独立形式。
下表定义了常见死键和 组合字符如何映射为独立字符。
| 死键 名称 | Unicode 组合 | 独立 字符 | Unicode 独立字符 |
|---|---|---|---|
| Grave | U+0300 | "`" | U+0060 |
| Acute | U+0301 | "'" | U+0027 |
| Circumflex | U+0302 | "^" | U+005e |
| Tilde | U+0303 | "~" | U+007e |
| Diaeresis | U+0308 | "¨" | U+00a8 |
2.5. 支持 ASCII 的键盘布局
支持 ASCII 的键盘布局指:
-
无需任何修饰键即可输入所有基本 ASCII 字符 "A" - "Z"。
-
对于所有常用书写系统按键,都会产生有效的可打印字符。
常用书写系统按键 是所有 键盘布局均包含的按键,如 [UIEvents-Code] 规范的 图 13 蓝色部分。
3. 键盘事件
3.1. layoutchange 事件
当用户代理检测到当前键盘布局已更改时,必须触发 layoutchange 事件。布局变化的具体时机取决于底层平台,通常会在用户主动操作(如选择新布局)时触发,或因切换到偏好特定布局的应用间接触发。
请注意以下几点:
-
修改可用布局集合不应触发layoutchange事件,除非修改导致当前布局发生变更。
-
添加或移除物理键盘一般不会引发事件,除非系统具备根据物理键盘变化切换当前布局的能力。但要注意事件的真正触发是布局变化,而非物理键盘的添加或移除。
-
只要当前布局发生更改,无论最高优先级的 ASCII 布局是否变化,都必须触发该事件。
如果用户代理在非前台应用时发生键盘布局更改,当其重新获得焦点时 MUST 触发 layoutchange 事件。
navigator.keyboard.addEventListener("layoutchange", function() {
// 更新用户键盘映射设置
updateGameControlSettingsPage();
});
3.1.1. 触发 layoutchange 事件
要触发 "layoutchange" 事件,需在用户代理的 keyboard
属性(Navigator
对象)上,派发名为 layoutchange 的事件。
4. 集成
4.1. 权限策略
本规范定义了一个控制 getLayoutMap() 方法是否在 Keyboard
接口上暴露的特性。
该特性的名称为 "keyboard-map"。
该特性的默认允许列表为 "self"。
5. 移动设备注意事项
由于该 API 以键盘为核心,且移动设备通常没有物理键盘,因此该 API 一般不会在移动设备上实现或支持。
但若移动设备允许物理键盘连接,也可选择支持该 API。在这种情况下,它们可以返回适合平台的一部分书写系统按键。
对于需要用户手动配置物理键盘布局(且没有合理默认项)的移动平台,也可通过返回一个内容为空的布局映射支持此 API。
6. 安全注意事项
本 API 返回静态数据,不会改变系统状态,因此没有特殊的安全风险。
7. 隐私注意事项
如同所有返回设备当前状态信息的 API 一样,提供此 API 后,可能会被用于扩大用户的“指纹”特征,从而增加用户被识别的风险。
通过只返回最高优先级支持 ASCII 的键盘布局的信息(而不是活动布局),可以降低该信息被用于指纹识别的价值,因为用户更容易获得相同映射。
注意以下几种可能利用布局信息识别个体的情形:
-
使用非常见 ASCII 布局(如 Dvorak 或 Colemak)的用户
-
所用 ASCII 布局与所处地区默认布局不符的用户。例如,美国用户启用 UK 或法语布局。
如果没有此 API,也可以尝试类似的指纹识别,但难度更高,因为需用户实际输入字符后分析生成的 KeyboardEvent。
7.1. 隐私缓解措施
作为用户第一道防线,本规范要求该 API 只能在安全上下文中,且只能由当前活动顶层浏览上下文调用,或通过策略控制特性授予访问权限。
担心键盘映射信息隐私风险的用户代理,还可以考虑如下缓解措施:
-
每次站点请求使用该 API 时提示用户请求授权。
-
始终返回“标准”映射——但注意标准映射本身也需随地区变化。例如如果浏览器隐私模式下总返回美式 QWERTY 映射,实际上对英国等地区用户反而更易被识别(因当地大部分用户并非使用 US 布局)。
7.2. 隐私模式
如果用户代理提供“隐身”或“隐私模式”,该 API 表现应与普通模式一致。这是因为不存在通用的中性返回值能确保用户隐私。
用户代理可以允许用户自行设定隐私模式下返回的值,但应注意须避免让用户产生安全错觉,因为该值在用户离开本地区时需及时调整。
8. 致谢
感谢以下人员的讨论促成了本提案的产生:
Hadley Beeman(W3C TAG), Joe Downing(Google), Masayuki Nakano(Mozilla), Julien Wajsberg(Mozilla)
9. 术语表
- 扫描码
-
键盘硬件分配给每个按键以便独立识别的值。 参见 https://en.wikipedia.org/wiki/Scancode 获取更多信息。