1. Introduction
Richly interactive web sites, games and remote desktop/application streaming experiences want to provide an immersive, full screen experience. To accomplish this, sites need access to special keys and keyboard shortcuts while they are in full screen mode so that they can be used for navigation, menus or gaming functionality. Some examples of the keys that may be required are Escape, Alt+Tab, Cmd+`, and Ctrl+N.
By default, these keys are not available to the web application because they are captured by the browser or the underlying operating system. The Keyboard Lock API enables websites to capture and use all available keys allowed by the OS.
2. Keyboard Lock API
2.1. Navigator Interface
partial interface Navigator { [SecureContext ,SameObject ]readonly attribute Keyboard ; };
keyboard
2.2. Keyboard Interface
[SecureContext ,Exposed =Window ]interface :
Keyboard EventTarget {Promise <undefined >(
lock optional sequence <DOMString >= []);
keyCodes undefined (); };
unlock
The keyboard object has enable keyboard lock, which is a boolean that is set to true when Keyboard Lock is enabled. By default, this is set to false.
The keyboard object has reserved key codes, which is a set of DOMStrings, each of which is a valid key code attribute value as defined in [UIEvents-Code]. By default this set is empty (which would capture all keys if enable keyboard lock was enabled).
The keyboard object has a keyboard lock task queue which is initialized to the result of starting a new parallel queue.
Note: The Keyboard
interface inherits from EventTarget
because
it needs to be able to handle some keyboard-releated events like layoutchange
.
2.2.1. lock()
When lock()
is called, the user agent must
run the following steps:
-
Let p be a new Promise.
-
If not currently executing in the currently active top-level browsing context, then
-
Reject p with an "
InvalidStateError
"DOMException
.
-
-
Enqueue the following steps to the keyboard lock task queue:
-
Reset reserved key codes to be an empty set.
-
If the optional
keyCodes
argument is present, run the following substeps:-
For each string key in
keyCodes
:-
If key is not a valid key code attribute value, then
-
Set enable keyboard lock to be false.
-
Reject p with an "
InvalidAccessError
"DOMException
.
-
-
Append key to reserved key codes.
-
-
-
If enable keyboard lock is currently false, run the following substeps:
-
Optionally, register a system key press handler.
-
Set enable keyboard lock to be true.
-
-
If there is a pending
lock()
task in the keyboard lock task queue, then-
Set enable keyboard lock to be false.
-
Reject the p with an "
AbortError
" DOMException.
-
-
Resolve p.
-
-
Return p.
Note: If lock()
is called multiple times without an
intervening call to unlock()
, then only the keyCodes
specified in the last request call will be in effect.
If a second call to lock()
is made before the first one has finished,
then the first one will be rejected with "AbortError
".
lock()
with
a list that contains the key code attribute value for each of these keys:
navigator.keyboard.lock(["KeyW", "KeyA", "KeyS", "KeyD"]);
This will capture these keys regardless of which modifiers are used with the key press. Assuming a standard US QWERTY layout, registering KeyW will ensure that "W", Shift+"W", Control+"W", Control+Shift+"W", and all other key modifier combinations with "W" will be sent to the app. Similarly for KeyA, KeyS and KeyD.
navigator.keyboard.lock(["Delete"]);
While this will make most Delete key presses available (e.g., Shift+Delete, Control+Delete, Shift+Control+Delete), on Windows it will not make available the “secure attention sequence” Control+Alt+Delete.
2.2.2. unlock()
When unlock()
is called, the user agent must
run the following steps:
-
Enqueue the following steps to the keyboard lock task queue:
-
If enable keyboard lock is true, then run the following substeps:
-
Set enable keyboard lock to be false.
-
Reset reserved key codes to be an empty sequence.
-
Note: When a document is closed, the user agent MUST implicitly call unlock()
so that the [system key press handler=] (if any) is
unregistered.
3. Handling Keyboard Key Presses
3.1. System Key Press Handler
A system key press handler is an platform-specific handler that can be used to filter keys at the platform level. Since Keyboard Lock feature is intended to provide access to key presses that are not normally made available to the browser (for example, Cmd/Alt-Tab), most platforms will require a special handler to be set up.The system key press handler must have the following properties:
-
It must process key presses before any user agent keyboard shortcuts are handled.
-
Wherever possible, it should process key presses before any system keyboard shortcuts are processed.
3.1.1. Registering
To register a system key press handler, the user agent will need to follow the platform-specific steps to add a low-level hook that will be called whenever the platform begins to process a new key press.
The exact process for adding a system key press handler varies from platform to platform. For examples of how to register a low-level hook to process key presses on common platforms, see [LowLevelKeyboardProc] for Windows, [QuartzEventServices] for Mac OS X and [GrabKeyboard] for X Windows.
Note: If the user agent already has a key press handler registered for another purpose, then it can optionally extend that handler to support the Keyboard Lock feature (assuming it meets the requirements mentioned above).
3.1.2. Unregistering
To unregister the system key press handler, the user agent will need to follow the platform-specific steps to remove the (previously added) low-level hook for processing new key press.
As with registering system key press handlers, the process for unregistering system key press handlers is also platform-specific. See the references listed in § 3.1.1 Registering for more details and examples.
3.2. Handling Keyboard Events
In response to the user pressing a key, if a system key press handler has been registered, it should run the following steps:
-
Let isFullscreen be set to true if the fullscreen element of the currently focused area of a top-level browsing context is non-null (see [Fullscreen]).
Note: The fullscreen element would not have focus, for example, if there was a system dialog being displayed with focus.
-
If isFullscreen and enable keyboard lock are all set to true, then run the following substeps:
-
Let keyEvent be the key event for the new key press.
-
Let code be the value of the
code
attribute of keyEvent. -
If reserved key codes is empty or if code is listed in reserved key codes, then run the following substeps:
-
If code is equal to "Escape", then run the following substeps:
-
Optionally overlay a message on the screen telling the user that they can Hold the Escape key to exit from fullscreen.
-
If the key is held for 2 seconds, then exit from the keyboard handler and pass the key on to the user agent for normal processing (which will exit fullscreen (and pointer lock, if active)).
-
-
Dispatch keyEvent directly to the fullsceen document or element, bypassing any normal user agent processing.
-
-
Else, handle the key event as it normally would be handled, either by dispatching a key event or performing the usual keyboard shortcut action.
-
Note: This API operates on a "best effort" basis. It is not required that a conforming implementation be able to override the OS default behaviour for every possible key combination. Specifically, most platforms have a “secure attention sequence” (e.g., Ctrl-Alt-Del on Windows) that applications cannot override; this specification does not supersede that.
Note: When implementing this API, user agents should take care not to change the order in which keyboard events are dispatched to the page. Keys that are included in the set of reserved key codes must be dispatched in the same relative order that they would have been sent had they not been included in the set.
4. Integration With Other Web Platform APIs
[Fullscreen] and [PointerLock] are APIs that allow the page to temporarily take control of part of the user’s experience (screen and mouse cursor, respectively). Because of the concern for abuse of these features, they provide an "escape" or "unlock" gesture that the user can rely on to exit those features. By default, this gesture is pressing the Escape key, which is one of the keys that can be captured by this API.
4.1. Special Considerations with the Escape Key
Because of the special actions associated with the Escape key, when the lock()
request includes the Escape key, the user agent may need to make additional
changes to the UX to account for the changed behavior.
For example, if the user agent shows a user message "Press ESC to exit fullscreen" when Javascript-initiated fullscreen is activated, then that message will need to be updated when keyboard lock is in effect to read "Press and hold ESC to exit fullscreen".
If keyboard lock is activated after fullscreen is already in effect, then the user
my see multiple messages about how to exit fullscreen.
For this reason, we recommend that developers call lock()
before they enter
fullscreen:
navigator.keyboard.lock(); document.documentElement.requestFullscreen();
A similar concern with multiple user messages exists when exiting keyboard lock and fullscreen, so it is recommended to call them in the reverse order:
document.exitFullscreen(); navigator.keyboard.unlock();
In general, developers should only include the Escape key in the set of locked keys if they actually have need for that key. And it is recommended that, if the Escape key is locked, the developer should maintain its primary meaning of allowing the user to exit their current state.
4.2. Fullscreen Considerations
There are two different types of fullscreen available in modern user agents: JavaScript-initiated fullscreen (via the [Fullscreen] API) and user-initiated fullscreen (when the user enters fullscreen using a keyboard shortcut). The user-initiated fullscreen is often referred to as "F11" fullscreen since that is a common key shortcut used to enter and exit fullscreen mode.
F11 fullscreen and JavaScript (JS) fullscreen do not behave the same way. When a user enters F11 fullscreen, they can only exit it via the same keyboard shortcut that they used to enter it -- the exitFullscreen() function will not work in this case. In addition, fullscreen events that are normally fired for JS fullscreen are not sent for F11 fullscreen.
Because of these differences (and because there is no standard shortcut for F11 fullscreen), the Keyboard Lock API is only valid when the a JavaScript-initiated fullscreen is active. During F11 fullscreen, no Keyboard Lock processing of keyboard events will take place.
5. Pointer Lock Considerations
Other than the UX changes noted earlier, there are no changes to the operation of Pointer Lock.
When Pointer Lock is enabled outside of fullscreen, then Keyboard Lock cannot be enabled.
When Pointer Lock, Keyboard Lock and Fullscreen are all enabled, then the behavior is unchanged unless Keyboard Lock includes the Escape key. In that case, the only chages are to the UX (as noted above).
6. Mobile Device Considerations
Since this is a keyboard-focused API and mobile devices do not commonly have physical keyboards, this API will not typically be present or supported on mobile devices.
However, mobile devices may choose to support this API if it makes sense to do so when a physical keyboard is connected.
7. Security Considerations
One concern with this API is that it could be used to grab all of the keys and (in conjunction with the [Fullscreen] and [PointerLock] API) prevent the user from exiting the web page.
To prevent this, the user agent MUST provide a way for the user to exit from keyboard lock even if all of the keys are requested by the API.
This specification requires support for allowing a long (more than 2 second) Escape key press to trigger an exit from Keyboard Lock. In addition, user agents may choose to also provide alternate ways to exit Keyboard Lock.
8. Privacy Considerations
Not applicable. This API does not use or reveal any personal information about the current user.
9. Acknowledgements
Thanks to the following people for the discussions that lead to the creation of this proposal:
Jon Dahlke (Google), Joe Downing (Google)