【JavaScript】フルスクリーンから抜け出せなくなる未来

フルスクリーンでゲームなどをプレイしているときに、うっかりESCを押してしまってフルスクリーンから脱出してしまって興がそがれた、なんて経験はありませんか？

フルスクリーンモードでは、`ESC`をキャプチャすることが[できません](https://stackoverflow.com/questions/25126106/capture-esc-event-when-exiting-full-screen-mode)。
また`Ctrl+W`や`Windows+D`といったOSによって制御されるショートカットは、そもそもJavaScriptから操作することが[できません](https://stackoverflow.com/questions/21695682/is-it-possible-to-catch-ctrlw-shortcut-and-prevent-tab-closing)。

ということで、そのあたりのキーボード入力を制御できるようにしようというAPIがWICGに提出されています。
以下は該当のAPI、[Keyboard Lock](https://wicg.github.io/keyboard-lock/)の紹介です。

現在のステータスは`Draft Community Group Report`ですが、これ[どの勧告レベル](https://www.w3.org/Consortium/Process/)なのかよくわかりません。
`Working Draft`の一種でいいんですかね。
いずれにせよ、まだ実装はされていません。


# Keyboard Lock

## 1 Introduction

リッチでインタラクティブなWebサイト、ゲーム、リモートデスクトップ、ストリーミングなどは没入感を高めるためにフルスクリーンで提供する必要があります。
これを実現するために、各サイトではフルスクリーン時にはナビゲーションやメニューを制御するため、特別なキーボードショートカットを使用できるようにする必要があります。
例としては`Escape`・`Alt+Tab`・``Cmd+` ``・`Ctrl+N`などです。

デフォルトでは、これらのキーはOSによってキャプチャされるため、Webアプリケーションから使用することはできません。
Keyboard Lock APIは、全てのキーをWebサイトからキャプチャして使用することを可能にします。


## 2 Keyboard Lock API

## 2.1 Navigator Interface

```js
partial interface Navigator {
  [SecureContext, SameObject] readonly attribute Keyboard keyboard;
};
```

## 2.1.1 keyboard

keyboard属性は、Keyboardの[Navigator](https://html.spec.whatwg.org/multipage/system-state.html#navigator)オブジェクトを返さなければなりません。

## 2.2 Keyboard Interface

```js
[SecureContext, Exposed=Window]
interface Keyboard : EventTarget {
  Promise<undefined> lock(optional sequence<DOMString> keyCodes = []);
  undefined unlock();
};
```

Keyboardオブジェクトは、キーボードロックが有効であればtrueを返すキーボードロック属性を持ちます。
デフォルトはfalseです。

Keyboardオブジェクトは、キャプチャするキーコードの集合を持ちます。
これは、[UIEvents-Code](https://www.w3.org/TR/uievents-code/)で定義された有効なキーコード属性のうち、制御可能なキーを示すDOMStringの集合です。
デフォルトは空集合です。
キーボードロックを有効にすると、キャプチャ対象のキーが登録されます。

:::note info
Keyboardインターフェースは[EventTarget](https://dom.spec.whatwg.org/#eventtarget)を継承しています。
これは[layoutchange](https://wicg.github.io/keyboard-map/#layoutchange-event)のようなキーボードを制御するイベントを処理する必要があるためです。
:::

## 2.2.1 lock()

`lock()`は、キーボードロックを取得するPromise返します。

:::note info
`lock()`が複数回呼ばれた場合は、最後の要求だけが有効になります。
最初の`lock()`が解決しないうちに2回目の`lock()`が呼ばれた場合、最初の`lock()`はAbortErrorを出して終了します。
:::

:::note warn
例1
全てのキーをキャプチャする場合は、単に引数なしで呼び出します。

navigator.keyboard.lock();
:::


:::note warn
例2
WASDの4キーをキャプチャする場合は、該当のキーコードを引数に渡します。

navigator.keyboard.lock(["KeyW", "KeyA", "KeyS", "KeyD"]);

これは、修飾の有無に関わらずキーをキャプチャします。
すなわち、`KeyW`を指定すると`W`・`Shift+W`・`Ctrl+W`・`Control+Shift+W`などの組み合わせがキャプチャされるということです。
:::

:::note warn
例3
必ずしもあらゆる組み合わせがキャプチャ可能であるとは限りません。
例として`Delete`を挙げると、`Shift+Delete`・`Control+Delete`・`Shift+Control+Delete`などはキャプチャできますが、Windows環境においては`Control+Alt+Delete`をキャプチャすることはできないかもしれません。
:::

## 2.2.2 unlock()

`unlock()`が呼ばれた場合、キーボードロックを解除し、ハンドラを登録解除しなければならない。


## 3 Handling Keyboard Key Presses

## 3.1 System Key Press Handler

System Key Pressハンドラは、プラットフォームごとに固有の、キーをフィルタリングするためのハンドラです。
キーボードロックは、通常はブラウザで使用できない`Cmd`や`Alt`といったキー操作を提供することを目的としているため、ほとんどのプラットフォームでは固有のハンドラを設定することが必要となります。

System Key Pressハンドラは、ユーザエージェントがキーボード入力を処理する前に入力を処理しなければなりません。


## 3.1.1 Registering

ユーザエージェントは、プラットフォーム固有の手順に従って、プラットフォームがキーを処理する前に割り込んで入力を取得するSystem Key Pressハンドラを登録する必要があります。
登録の正確なプロセスはプラットフォームによって異なりますが、例としてWindowsでは[LowLevelKeyboardProc](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx)、MACでは[QuartzEventServices](https://developer.apple.com/reference/coregraphics/1658572-quartz_event_services)、X Windowでは[GrabKeyboard](https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#requests:GrabKeyboard)等となります。


## 3.1.2 Unregistering

System Key Pressハンドラを解除するには、プラットフォーム固有の手順に従って削除する必要があります。
登録と同様に、解除の方法もプラットフォームによって異なります。

## 3.2 Handling Keyboard Events

System Key Pressハンドラが登録されている場合、ユーザエージェントはユーザのキーダウンに対して以下の処理を行います。
・現在フォーカスされている領域に[fullscreen要素](https://fullscreen.spec.whatwg.org/#fullscreen-element)がnullでない場合は、isFullscreenをtrueにする。
たとえばシステムダイアログがフォーカスされている場合は、fullscreen要素はありません。
・isFullscreenがtrueであり、キーボードロックが有効であれば、以下のステップを行う。
・キー入力をキーイベントkeyEventに登録する。
・押されたきーがEscapeである場合、押し続ければフルスクリーンから脱出できるメッセージを表示する。オプショナル。
・Escapeが2秒保持されたら、フルスクリーンを終了し、キーボードハンドラを終了する。
・それ以外のキーボードロックしたキーであれば、keyEventを処理に渡す。プラットフォームには渡さない。
・キーボードロックしたキーでなければ、キーボードショートカットアクションを通常どおり処理する。

:::note warn
本APIは、必ずしも全てのキーの組み合わせについて入力の上書きを保証するものではない。
たとえばWindowsの`Ctrl+Alt+Del`のように、アプリケーションからは操作することのできない入力も存在する可能性がある。
:::


## 4 Integration With Other Web Platform APIs

他の類似APIとの統合

[Fullscreen](https://fullscreen.spec.whatwg.org/)と[PointerLock](https://www.w3.org/TR/pointerlock/)は、それぞれWebページがユーザエクスペリエンスの一部(スクリーンとマウスカーソル)を一時的に制御することを可能にするAPIです。
これらは悪用が懸念されるため、ユーザの信頼できる脱出機能を提供しています。
デフォルトではEscapeキーが脱出機能に割り当てられており、それは本APIでキャプチャできるキーのひとつです。


## 4.1 Special Considerations with the Escape Key

Escapeキーの扱い。

`lock()`の対象にEscapeキーが含まれる場合、ユーザエージェントは要求に応じてUXの変更を行う必要があるかもしれません。
たとえばこれまでフルスクリーンになったときに『フルスクリーンを終了するにはESCを押す』とメッセージを表示していたとしたら、キーボードロック時にはそのメッセージを『フルスクリーンを終了するにはESCを押し続ける』と変更する必要があります。

フルスクリーンになったあとでキーボードロックを有効にする場合、ユーザはフルスクリーンを終了するメッセージを2回見せられることになります。
従ってフルスクリーンにする前にロックを取得することをお勧めします。

```js
navigator.keyboard.lock();
document.documentElement.requestFullscreen();
```

フルスクリーンを終了するときは、逆の順番で終了するとよいでしょう。

```js
document.exitFullscreen();
navigator.keyboard.unlock();
```

一般的に、Escapeキーは必要な場合にのみロックに含めるべきです。
またEscapeキーをロックする場合は、ユーザが現状を終了できるという通常の動作を維持することが推奨されます。


## 4.2 Fullscreen Considerations

フルスクリーンの考慮事項。

最近のユーザエージェントは、2種類のフルスクリーンモードを持っています。
JavaScriptの[Fullscreen API](https://fullscreen.spec.whatwg.org/)から起動されるものと、ユーザがキーボードショートカットから起動するものです。
ユーザ起動のフルスクリーンは、ショートカットとしてよく使われるキーから`F11フルスクリーン`と呼ばれます。

F11フルスクリーンと、JavaScriptフルスクリーンは挙動が異なります。
F11フルスクリーンからは、フルスクリーンを起動したキーと同じキーで脱出することができますが、この場合`exitFullscreen()`関数は反応しません。
またJavaScriptフルスクリーンで発生するfullscreenイベントも、F11フルスクリーンでは発生しません。

これら動作の違い、さらにF11フルスクリーンのショートカットキーはF11と決まっているわけではないので、キーボードロックAPIはJavaScriptフルスクリーンでのみ有効になります。
F11フルスクリーンについては、キーボードロックは動作しません。


## 5 Pointer Lock Considerations

Pointer Lockの考慮事項。

[PointerLock](https://www.w3.org/TR/pointerlock/)の動作には変更がありません。

フルスクリーンでなく、Pointer Lockが有効な場合は、キーボードロックを有効にすることはできません。

フルスクリーン状態では、Pointer Lockとキーボードロックが共に有効で、さらにキーボードロックの対象にEscapeキーが入っていた場合にのみ、UXの文言の変更が行われます。
それ以外の場合は動作は変わりません。


## 6 Mobile Device Considerations

モバイルデバイスへの考慮事項。

このAPIはキーボードに特化したAPIであり、モバイルデバイスには物理キーが存在しないため、このAPIはサポートされません。

ただし、物理キーボードを接続したときに本APIをサポートする選択を取ることもできます。


## 7 Security Considerations

セキュリティの考慮事項。

このAPIの懸念事項のひとつは、全てのキーのキーボードロックを取得したうえで、[Fullscreen](https://fullscreen.spec.whatwg.org/)と[PointerLock](https://www.w3.org/TR/pointerlock/)を組み合わせることでユーザがWebページから抜け出すことを阻止する可能性です。

これを防ぐため、ユーザエージェントはAPIによって全てのキーがロックされたとしても、ユーザがキーボードロックから抜ける方法を提供しなければならない(MUST)。

この仕様では、Escapeキーを2秒以上押し続けることでキーボードロックから抜けることができます。
ユーザエージェントは、さらに別の方法を提供することもできます。

## 8 Privacy Considerations

プライバシーに関する考慮事項はありません。



# 感想

Keyboard LockにESCの操作を奪われている場合、フルスクリーンから抜け出すには『ESCを2秒間押し続ける』必要がある。

こんなもん誰がどう考えても100%絶対確実に、『このPCを乗っ取りました。解除してほしくば〇〇に振り込め』に使われる。

特に、脱出するにはボタンを押し続けるというUIが最悪です。
今でもフルスクリーンにしたときには脱出方法のメッセージが数秒表示されますが、あんなの似たような文字をたくさん出すだけでほぼ視認できなくなります。
それに『ESCを押す』と『ESCを押し続ける』なんて間違い探しレベルの違いだからたとえ意識して読んでいたとしても気付きづらいし、そして意識して読んでいる人なんてほぼいません。
また私なら、一度フルスクリーン化に成功してしまえば、今度はキーを触った瞬間に画面を崩したり叫び声を出したりして、キーボードを押させない・手を離させるように仕向けます。
これで一般人はもう手も足も出せなくなるでしょう。
出せるのは電源ボタンくらいです。

当然[懸念を声に出している人もいます](https://github.com/WICG/keyboard-lock/issues/47)が、当事者からの反応は、なんと完全無視。
この後も当RFCにはcommitがあり、これより後のissueには[反応している](https://github.com/WICG/keyboard-lock/issues/58)ので、気付かなかったわけではなく意図的なスルーです。
この時点で開発者がどのような姿勢を持っているのか丸わかりですね。

ESCをキャプチャ対象外にするなどの対応を行えば評価は変わるかもしれませんが、現在の仕様としては『最悪』以外の何物でもありません。

そしてもちろん言うまでもなく、このAPIの提唱者はGoogleです。