Skip to content

Instantly share code, notes, and snippets.

@william-ml-leslie
Last active August 29, 2015 14:25
Show Gist options
  • Save william-ml-leslie/16823ab9d5f2d6fd4e52 to your computer and use it in GitHub Desktop.
Save william-ml-leslie/16823ab9d5f2d6fd4e52 to your computer and use it in GitHub Desktop.
A Better DOM Keyboard API

A better DOM keyboard API

Author: William ML Leslie <william.leslie.ttg@gmail.com>
Date: $Date 2015-07-27 15:21:08 +1000 (Mon, 27 Jul 2015) $
Copyright: This document has been placed in the public domain.

The current DOM keyboard API has a lot of flexibility, but is lacking in a few important ways. Some of those ways have been partially addressed by libraries. I would like to take a look at some of the problems I've run into with this API and how we might address them.

To Do

  • How handler resolution proceeds
  • How it handles key prefixes
  • Revocation and substitution
  • Documentation discovery
  • Interaction with the legacy API

The DOM Keyboard API

The DOM defines events for keyup and keydown, as well as a number of events for composing text using an external editor. These events implement the bubbling phase, enabling parent elements to handle keyboard events if their children permit. They do not implement, as far as I can tell, the capture phase, which means that children can hide the event from their parents.

This API is quite flexible. For example, it doesn't embed the fact that the shift key is only a modifier, so you can use shift or control keys as buttons, often useful for implementing games.

Better Keyboard Bindings

My concerns about this API come primarily from the perspective of accessibility. People have trouble using a mouse often end up having to deal with the site's keyboard interface. Often, that keyboard interface conflicts with bindings in the IME or UA, which prevents the user from exercising this functionality.

It would be better if, instead of fairly opaque event handlers, keyboard handlers were declared in a way that made it easy to attach event names and even descriptions of what the command does. This would make it possible to display commands in a menu.

It would also enable a UA to provide site-specific overrides for a given command without having the user write custom javascript, handy for those sites that insist on vim-style movement keys when you're an emacs user.

Securable UI interaction

The default 'bubble up' behaviour of keyboard bindings is both confusing for the user and problematic from a security perspective. For example, it is possible for iframes and embedded plugins to steal keyboard input just by getting focus, without the consent of the parent page. So, a better UI would make it possible to prevent some bindings from being overridden without user interaction.

An API where bindings are specified per-feature would enable a UI to offer inspection of the delegation chain.

Secure Delegation

Delegation of security concepts is important when building a UI, especially as web pages incorporate content from multiple origins and multiple runtimes. Consider what happens outside the page:

  • A web page must not be able to bind the keys that allow you to switch tabs.
  • In a multi-process model like that of Google Chrome, a tab must not bind keys to operate the application menu or activate fullscreen.
  • In a layered model like Mozilla Firefox and other XUL-style browsers, the page must not be able to override the browser's own UI.
  • The browser must not be able to override the window management keys, such as Alt-Tab which is used on MS Windows and many X11 window managers for task switching.
  • The window manager must not be able to override TCB-implemented safeguards, such as Ctrl-Alt-Delete on Microsoft Windows or the Magic SysReq key on many unixen.

In practice, much of the above is not enforced. Sometimes this can provide useful functionality, such as remote terminal access applications where Alt-Tab should switch tasks on the remote machine rather than the local machine. This proposed API makes it possible for applications to register their interest in already bound keys, and then makes it possible for the UA to describe, enforce, and supply any overrides.

Importantly, it means that rather than an application asserting itself that it will bind or grab certain keys, there should be a trusted interface for enabling and disabling such overrides. This will go a long way to preventing the confusion that occurs when a window (such as a virtual machine window) grabs the keyboard or mouse.

User Overrides and Preference Persistence

There are a number of things that a UA should allow the user to configure, both for each command-set and globally:

  • Add, remove, or modify a keybinding.
  • Allow a keybinding to succeed, even if bound in the parent.
  • Select documentation language.

If the UA enables preferences such as bookmarks to roam with the user, keybinding preferences should roam where possible.

In order to support persistent preferences, a user must be able to identify a command-set or even individual commands. The page origin and command name can be used as a first approximation, but command-set authors can supply additional identification for additional flexibility. Each command-set should contain an URI that identifies that specific set.

Additionally, individual commands can use a URI much like a namespace. This is useful for common commands, such as for site-wide search, fullscreen, or rich text editor functions. Typically, users want common commands to behave the same across many websites, so making it possible for a website to declare the intent of a command in this way enhances the usability of the site.

Some thoughts on API design

The API should make it possible to declare several sets of commands that can be active at any one time, as well as the ability to close over data when activating. It should make it easy to document methods and to provide default keybindings. It should also make it possible to defer loading of documentation.

Proposed API 1: Policy gives user convenience

A caller-created command_set makes it possible to do POLA

function component_configure_command_set() {
  var command_set = new CommandSet()
  command_set.on('input_character', '\w', handle_word_char);
  command_set.on('search', 'C-f', open_search_box);
  command_set.on('indent', 'Tab', indent_more);
  return command_set;
}

var command_set = component_configure_command_set();
keyboard.install('name', command_set, definition_url);
keyboard.remove('name');

Proposed API 2: User-Provided Objects

I'm not sure if this is really idiomatic JS or DOM style, but I think this is what I prefer.

var command_set_definition = {
  '$_name' : 'some_commands',
  '$_url' : 'definition_location',
  'open_search_box' : {
    'bindings': ['C-f'],
    // todo: i18n for documentation!
    'documentation' : 'optional command documentation (can use url instead)'
  }
}

keyboard.defineCommandSet(command_set_definition);

function MyCommandSet(ctxt) {
  this.ctxt = ctxt;
}

MyCommandSet.prototype = {
  name : 'some_commands',
  handle_word_char : function handle_word_char() { },
  open_search_box : function open_search_box() {}
}

var revoke = keyboard.addCommandSet(new MyCommandSet());
revoke();

Proposed API 3: Here's your keyboard object

function component_configure_command_set(command_set) {
  command_set.on('input_character', '\w', handle_word_char);
  command_set.on('search', 'C-f', open_search_box);
  command_set.on('indent', 'Tab', indent_more);
}

var [subkeyboard, revoke] = keyboard.subKeyboard();

component_configure(subkeyboard);
revoke();

References

The closest thing I know of a 'nice' API to this sort of thing is `jQuery Hotkeys`_.

_jQuery Hotkeys: https://github.com/jeresig/jquery.hotkeys

$('input.foo').bind('keyup', '$', function() {
  this.value = this.value.replace('$', 'EUR');
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment