Friday, September 6, 2013

Again, I Cannot Generate Keyboard Events in Dart


Sigh.

The latest Dart release has completely broken the build for the ICE Code Editor. On the plus side, I believe that these are useful, breaking changes that have been mentioned on the mailing list. On the negative side, these are more changes for which I need to account in the next edition of Dart for Hipsters.

They don't make it easy on a fella.

In the build there are a few warnings along the lines of:
Removing disallowed attribute <INPUT style="margin: -10px 4px;"> 
Removing disallowed attribute <INPUT style="margin: -10px 4px;"> 
Removing disallowed attribute <INPUT style="margin: -10px 4px;"> 
...
I believe those are related to some of the HTML sanitizing that has recently been introduced into the language. I am eager to dive into that and this seems a good excuse. Since I have no idea why I am styling with a style attribute, the fix would also seem pretty easy.

Too easy.

The more pressing issue for me involves keyboard event failures:
NoSuchMethodError : method not found: 'KeyboardEvent'
Receiver: Type: class 'KeyboardEvent'
Arguments: ["keyup", keyIdentifier: "U+0031"]
CONSOLE MESSAGE: Stack Trace:
#0      NoSuchMethodError._throwNew (dart:core-patch/errors_patch.dart:99:5)
#1      typeIn (package:ctrl_alt_foo/helpers.dart:10:22)
#2      createProject (native_DOMImplementation.dart:255:17)
What makes this more pressing is that (1) it is preventing the build from running and (2) I have no idea how to fix it. When triaging bugs, the unknown (2) always wins. And in this case, it is the more serious of tonight's problems because of (1). In other words, HTML sanitizing is going to have to wait.

After a bit of digging, I find that the latest version of Dart has changed the constructor signature for KeyboardEvent. The keyIdentifier named parameter has been removed. I relied on that. Heavily.

I relied on it so heavily that I extracted test helpers and keyboard shortcuts out into a library that ICE (and other Dart code) uses. The stack trace above originates from that library—ctrl-alt-foo. In there, I generate all kinds of keyboard events with the keyIdentifier parameter:
  var last_char = new String.fromCharCode(text.runes.last);
  document.activeElement.dispatchEvent(
    new KeyboardEvent(
      'keyup',
      keyIdentifier: keyIdentifierFor(last_char)
    )
  );
Thankfully, the change that removed keyIdentifier also indicates that the KeyEvent class is finally getting some love. The KeyboardEvent constructor—the one that no longer supports supplying character data—includes this note in the documentation:
For programmatically creating keyboard events with specific key value contents, see the custom Event KeyEvent.
Pefect!

To try this out, I swap ICE's dependency on ctrl-alt-foo to a local path instead of a version from Dart Pub (the central package repository for Dart):
name: ice_code_editor
# ...
dependencies:
  crypto: ">=0.6.19 <0.6.20"
  js: ">=0.0.24 <0.0.25"
  unittest: ">=0.6.15+3 <0.6.16"
  ctrl_alt_foo: 
    path: /home/chris/repos/ctrl-alt-foo
  json: ">=0.6.9 <0.6.10"
But now that I am looking at the updated documentation for KeyEvent, I notice that it does not look very updated. Well, a little updated, but only to limit the constructor to a single argument, which is KeyboardEvent. Since I cannot add character data to a KeyboardEvent, any instance of that class that is passed to KeyEvent will lack character data. And I see no way to add character data.

The keyCode and charCode properties are read-only. If try to set them:
  var last_char = new String.fromCharCode(text.runes.last);
  document.activeElement.dispatchEvent(
      new KeyEvent(new KeyboardEvent('keyup'))
        ..keyCode = keyIdentifierFor(last_char)
  );
}
Then I get method-not-found errors:
NoSuchMethodError : method not found: 'keyCode='
Receiver: Instance of 'KeyEvent'
Arguments: ["U+0031"] 
I even try several variations of extending and implementing the KeyboardEvent and KeyEvent classes. But if there is a way to get the keyIdentifier set in the native DOM implementation, it is beyond me.

The closest that I get is overriding the _shadowCode private variable with my own value:
class KeyEventX extends KeyEvent {
  int _shadowKeyCode;

  KeyEventX(keyboard_event, identifier): super(keyboard_event) {
    print('keyCode: ${identifier.codeUnits.first}');
    _shadowKeyCode = identifier.codeUnits.first;
  }
}
But that only yields a very impressive Internal Dartium exception:
keyCode: 49 undefined:1
Uncaught Error: InvalidStateError: Internal Dartium Exception undefined:1
Stack Trace:
#0      Node.dispatchEvent (file:///mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/out/Release/gen/blink/bindings/dart/dart/html/Node.dart:215:120)
#1      typeIn (package:ctrl_alt_foo/helpers.dart:19:39)
#2      createProject (file:///home/chris/repos/ice-code-editor/test/helpers.dart:11:9)
#3      full_tests.<anonymous closure>.<anonymous closure>.<anonymous closure> (file:///home/chris/repos/ice-code-editor/test/full_test.dart:101:30)
...
At this point, I think that I have to admit that I am out of luck. The keyIdentifier parameter was far from perfect, but it was sufficient to legitimately test keyboard events in a more-or-less cross-browser fashion. I can take some solace in the fact that it seems the Dart folks (who I love and respect very much) are actively working on this code. I can only hope that I will be able to generate keyboard events with character data soon (pretty please).

In the meantime, I need to decide how to proceed with ICE. The ctrl-alt-foo library is a complete loss. Not only are the testing helpers broken, but the event bindings are broken as well (they rely on keyIdentifer for cross-browser compatibility and testability). I can probably remove every instance of keyIdentifier from ctrl-alt-foo, which falls back on the still supported charCode/keyCode. But without my tests, I have no way to know for sure that everything is still working.

I was hoping to get a new release of ICE pushed out this weekend which included all my fancy new keyboard shortcuts as well as last night's JavaScript <script> load order fix. That just got a lot harder, but it may still be possible. I still have the old version of Dart which includes keyIdentifier support. I will restore that, verify that my tests still pass and the the code still passes analysis. With that, I will compile and push to ice-beta. Once that is done, I will mark all of my keyboard tests as skip_test to get the build passing. I will also work through ctrl-alt-foo to remove keyIdentifier support.

Bleh.


Day #866

No comments:

Post a Comment