Keyboard Lock

Draft Community Group Report,

This version:
https://wicg.github.io/keyboard-lock/
Issue Tracking:
GitHub
Editors:
(Google)
(Google)

Abstract

This specification defines an API that allows websites to capture keys that are normally reserved by the underlying host operating system. It is intended to be used by web applications that provide a fullscreen immersive experience (like games or remote access apps).

Status of this document

This document is an editor’s draft proposed as a First Public Working Draft.

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

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:

  1. Let p be a new Promise.

  2. If not currently executing in the currently active top-level browsing context, then

    1. Reject p with an "InvalidStateError" DOMException.

  3. Enqueue the following steps to the keyboard lock task queue:

    1. Reset reserved key codes to be an empty set.

    2. If the optional keyCodes argument is present, run the following substeps:

      1. For each string key in keyCodes:

        1. If key is not a valid key code attribute value, then

          1. Set enable keyboard lock to be false.

          2. Reject p with an "InvalidAccessError" DOMException.

        2. Append key to reserved key codes.

    3. If enable keyboard lock is currently false, run the following substeps:

      1. Optionally, register a system key press handler.

      2. Set enable keyboard lock to be true.

    4. If there is a pending lock() task in the keyboard lock task queue, then

      1. Set enable keyboard lock to be false.

      2. Reject the p with an "AbortError" DOMException.

    5. Resolve p.

  4. 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".

To capture all keys, simply call lock() with no arguments:
navigator.keyboard.lock();
To capture the "W", "A", "S", and "D" keys, call 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.

Note that requesting a key is not a guarantee that all modified versions will be made available to the app. As an example, consider Delete
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:

  1. Enqueue the following steps to the keyboard lock task queue:

    1. If enable keyboard lock is true, then run the following substeps:

      1. Unregister the system key press handler.

      2. Set enable keyboard lock to be false.

      3. 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:

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:

  1. 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.

  2. If isFullscreen and enable keyboard lock are all set to true, then run the following substeps:

    1. Let keyEvent be the key event for the new key press.

    2. Let code be the value of the code attribute of keyEvent.

    3. If reserved key codes is empty or if code is listed in reserved key codes, then run the following substeps:

      1. If code is equal to "Escape", then run the following substeps:

        1. Optionally overlay a message on the screen telling the user that they can Hold the Escape key to exit from fullscreen.

        2. 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)).

      2. Dispatch keyEvent directly to the fullsceen document or element, bypassing any normal user agent processing.

    4. 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)

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[Fullscreen]
Philip Jägenstedt. Fullscreen API Standard. Living Standard. URL: https://fullscreen.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[UIEVENTS]
Gary Kacmarcik; Travis Leithead; Doug Schepers. UI Events. 30 May 2019. WD. URL: https://www.w3.org/TR/uievents/
[UIEvents-Code]
Gary Kacmarcik; Travis Leithead. UI Events KeyboardEvent code Values. 1 June 2017. CR. URL: https://www.w3.org/TR/uievents-code/
[WebIDL]
Boris Zbarsky. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

Informative References

[GrabKeyboard]
X11 GrabKeyboard API. URL: https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#requests:GrabKeyboard
[LowLevelKeyboardProc]
LowLevelKeyboardProc documentation on MSDN. URL: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx
[PointerLock]
Vincent Scheib. Pointer Lock. 27 October 2016. REC. URL: https://www.w3.org/TR/pointerlock/
[QuartzEventServices]
Quartz Event Services. URL: https://developer.apple.com/reference/coregraphics/1658572-quartz_event_services

IDL Index

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

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