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 インターフェース
現行エンジン1つのみ。
Opera55+Edge79+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android69+Android WebView69+Samsung Internet10.0+Opera Mobile48+
[Exposed =Window ]interface {KeyboardLayoutMap readonly maplike <DOMString ,DOMString >; };
KeyboardLayoutMap
は、code
値からkey
値へのマッピングを提供する読み取り専用コレクションです。
2.3. Keyboard インターフェース
現行エンジン1つのみ。
Opera55+Edge79+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome 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()
現行エンジン1つのみ。
Opera56+Edge79+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android69+Android WebView69+Samsung Internet10.0+Opera Mobile48+
getLayoutMap()が呼ばれると、ユーザーエージェントは次を実行します:
-
p を新しい
Promiseとする。 -
もし thisの 関連するグローバルオブジェクトの 関連付けられたドキュメント が 使用することを許可されていない ポリシー制御機能 名称 "keyboard-map"
-
p を返す
-
以下のステップを並列で実行:
-
map を新たな空の
KeyboardLayoutMapとする。 -
Writing System Keys テーブルの "KeyboardEvent code" 列 の各codeについて:
-
layout を、最も優先度が高いASCII対応キーボードレイアウトまたは対応レイアウトが無い場合は最優先レイアウトとする。
-
codeがlayoutで有効なキーでなければ続行
-
key を、layout で修飾なしでcodeのキーを押した時に生成される
key値とする。 -
もしkeyがデッドキーであれば
-
keyにデッドキーと対応する独立した文字(「デッドキーの単独等価」テーブル参照)をセット
-
-
<code, key>からマップエントリeを作成
-
eをmapに追加
-
-
pをmapで解決
-
-
p を返す。
ユーザーエージェントはlayout変更時にキャッシュを破棄・更新する限り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対応キーボードレイアウトとは、次の条件を満たすものです。
-
修飾キーを使わずに "A" から "Z" までの基本ASCII文字すべてを出力できること。
-
すべての共通書記体系キーで有効な印字可能文字を出力できること。
共通書記体系キーとは、Figure 13([UIEvents-Code]仕様)で青色で示されている、全キーボードレイアウトに共通するキーです。
3. キーボードイベント
3.1. layoutchangeイベント
ユーザーエージェントがキーボードレイアウトの変更を検知した場合、必ずlayoutchangeイベントを発火しなければなりません。レイアウト変更のタイミングは基盤プラットフォームによって異なりますが、多くの場合はユーザーが新しいレイアウトを選択した時や、特定レイアウトが設定されたアプリケーションへ切り替える時など、直接・間接的トリガーによって発動します。
以下に注意してください:
-
利用可能なレイアウトの変更は、現在のレイアウトを変更しない限りlayoutchangeイベントを発火してはいけません。
-
物理キーボードの追加や取り外し自体でイベントを発火してはいけません。ただし、それによってシステムがレイアウト変更を行う場合、その時にイベントが発火します。つまり、実際にレイアウトが変わった時にのみイベントを発火しなければなりません。
-
現在のレイアウトが変わるたび、たとえ最優先ASCII対応レイアウトが同じでも、イベントは必ず発火されなければなりません。
ユーザーエージェントが前面アプリケーションでない間にキーボードレイアウトが変更された場合、フォーカスを再取得したタイミングで必ずlayoutchangeイベントを発火しなければなりません。
navigator.keyboard.addEventListener("layoutchange", function() {
// ユーザーのキーボードマップ設定を更新
updateGameControlSettingsPage();
});
3.1.1. layoutchangeイベントを発火
"layoutchange"イベントを発火するには、イベントを発火し、イベント名layoutchange、ユーザーエージェントのkeyboard
属性(Navigator
オブジェクト上)で発火します。
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を使うたびにユーザー確認プロンプトを出す
-
常に「標準」マッピングを返す。ただし標準値自体が地域ごとに異なるため、例えば「プライバシーモード」として常にUS-QWERTYを返すと、むしろUKユーザーの特定性が高まる場合があることに留意する
7.2. プライバシーモード
ユーザーエージェントが「シークレット」や「プライバシーモード」を提供する場合も、このAPIの挙動は通常と同じであるべきです。 なぜなら、ユーザーのプライバシーを保証できる普遍的な中立値が存在しないためです。
このモード時に返す値の選択肢をユーザーに委ねても構いませんが、居住地域外へ移動する時は値の更新が必要なことなど、ユーザーが誤った安全感を抱かないよう注意が必要です。
8. 謝辞
本提案の策定につながる議論をしてくださった皆さまに感謝いたします。
Hadley Beeman (W3C TAG), Joe Downing (Google), Masayuki Nakano (Mozilla), Julien Wajsberg (Mozilla)
9. 用語集
- スキャンコード
-
各キーを一意に識別するためにキーボードハードウェアが割り当てる値。 詳細は https://en.wikipedia.org/wiki/Scancode を参照。