Wednesday, August 7, 2013

Keyboard Event Support Remains Rough in Dart


It does not matter the language, I hate keyboard events in the browser.

To date, I have been unsuccessful in testing keyboard events in Dart—mostly because it is very hard, if not impossible, to simulate keyboard events in Dart. And, of course, JavaScript DOM events stink to high heaven: the character is in the keyCode property for keyup and keydown events, but it is the uppercase version of the character no matter what, and the character is in the charCode property for keypress events, but both keyCode and charCode are deprecated for key which is deprecated for which which is not implemented anywhere. And of course none of it works in all browsers.

Anyhow…

Dart is supposed to normalize bizarre behaviors across browsers, but I am still using the same raw values that JavaScript uses. In the ICE Code Editor project, I handle keyboard shortcuts by querying the $dom_keyIdentifier property:
    _keyDownSubscription = document.onKeyDown.listen((e) {
      if (!e.ctrlKey) return;

      switch(e.$dom_keyIdentifier) {
        case 'n':
        case 'U+004E':
          new NewProjectDialog(this).open();
          e.preventDefault();
          break;
        case 'o':
        case 'U+004F':
          new OpenDialog(this).open();
          e.preventDefault();
          break;
        case 'h':
        case 'U+0048':
          if (e.shiftKey) toggleCode();
          e.preventDefault();
          break;
      }
    });
Don't even get me started on keyIdentifier, but it seems to have the best support in the various browsers that I am targeting and has limited testability in Dart. But let's face it, that kind of stinks.

As I have been mucking with events in Dartover the past few nights, I came across the KeyEvent class. This class is supposed to normalize the wackiness of cross-browser key events. I give it a try with the KeyboardEventStream.onKeyDown() static method, which ought to give me a stream of KeyEvent objects:
    _keyDownSubscription = KeyboardEventStream.onKeyDown(document).listen((e) {
      if (!e.ctrlKey) return;
      switch(e.keyCode) {
        // ...
      }
    });
When I try that out in the browser, I get:
Exception: Unsupported operation: keyIdentifier is unsupported. /mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/dart/tools/dom/src/dartium_KeyEvent.dart:40
KeyEvent.$dom_keyIdentifier /mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/dart/tools/dom/src/dartium_KeyEvent.dart:40
KeyEvent._shadowKeyIdentifier /mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/dart/tools/dom/src/dartium_KeyEvent.dart:36
_KeyboardEventHandler.processKeyPress /mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/dart/tools/dom/src/KeyboardEventStream.dart:138
processKeyDown /mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/dart/tools/dom/src/KeyboardEventStream.dart:121
Sigh. A quick search reveals that a bug has already been reported for this (KeyEvent objects with the control key active). Well, that is unfortunate. It seems that I am stuck with $dom_keyIdentifier until the M6 release of Dart (at least).

Before calling it a night, I would like to catalog the behavior of $dom_keyIdentfier across clients.

In Dart unittests, I can create keyboard events with data using the KeyboardEvent class:
  document.activeElement.dispatchEvent(
    new KeyboardEvent(
      'keydown',
      keyIdentifier: char,
      ctrlKey: true
    )
  );
When char is "n" the value of $dom_keyIdentifier is also "n".

When I try pressing Ctrl+n in Dartium, $dom_keyIdentifier returns the rather absurd string of "U+004E". Note that is the string "U", the string "+", the string "0", etc. and not a unicode integer. I have little room to complain since I am using a raw DOM getter. Well, I supposed I am entitled to complain a little bit since it is such an incredible pain to create keyboard events with data in Dart, but I won't.

When I compile to JavaScript, Chrome also returns the string "U+004E".

Unfortunately, Firefox returns null for this value.

Similarly, Internet Explorer also returns null for $dom_keyIdentifier.

In all browsers, the keyCode value is set properly (decimal 78 for N), but I have no way to set the keyCode property in the KeyboardEvent constructor or via a setter on the resultant object. The keyCode value is final—intended only for the native browser event to set.

For posterity:
Env$dom_keyIdentifer for “N”keyCode for “N”
unittestn0
Dartium"U+004E"78
Chrome"U+004E"78
Firefoxnull78
Internet Explorernull78

In other words, I am left with two choices: make it work across all browser via the keyCode property or leave it testable (albeit poorly) with $dom_keyIdentifier and only support Chrome.

I think for ICE, it makes the most sense to leave it testable and to support Chrome only. The main reason is that I value testing that highly. If I cannot test it, I cannot guarantee that future changes will not introduce regressions. Also, once KeyEvent improves, I have a drop-in place for the code to go and tests will ensure that it works. The second reason that I stick with Chrome-only is that ICE's primary use is for programmers reading 3D Game Programming for Kids. Even though ICE will work in all browsers, the book requires Chrome for easier support, better WebGL support, and so that I can have readers check the Chrome console for errors.

Still, all I want for Christmas is better keyboard event support and testability in Dart. Please?


Day #836

No comments:

Post a Comment