Hatena::Groupjavascript

JavaScriptで遊ぶよ

2011-12-09

CompositionEventの紹介

00:49

JavaScript Advent Calendar 2011オレ標準駅伝10日目です。それにしてもみなさん濃いですね。圧巻はoogattaさんでしょうか。

今日は軽めに、あまり知られてないと思われるDOMのAPIを紹介したいと思います。

DOM3 EventsにはComposition Event Typesというのがあります。

CompositionEventにはcompositionstart, compositionupdate, compositionendの3つのイベントタイプがありまして、これらはIME専用のイベントタイプです。

ChromeとFirefoxとIE9では既に使えました。デモを試してみてください。

f:id:edvakf:20111209062313p:image


こんな感じになるわけです。

IMEを使って最初の一文字を入力しようとするときに出るのがcompositionstartで、キーを打ってるときと変換候補を巡ってるときに出るのがcompositionupdateで、決定のときに出るのがcompositionendです。


何が嬉しいの?

ChromeやIEはIMEで入力中にもkeyupやkeydownが出ますが(昔は違ったような気が…うろ覚え)、FirefoxやOperaなどはCompositionEvent以外には何もイベントを出さないので、compositionupdateが無ければIMEで入力中なのかどうかわかりません。Firefoxではtextというオレ標準イベントが出るらしいです。Operaは東アジア対応が弱いので、CompositionEventが近いうちに入る可能性は低い気がします。

なので、サジェスト系のUIだとタイマーで入力欄のvalueを見てやらないといけません。

CompositionEventがあればもうそんな心配はいりませんね。


それから、↓こういう経験ありますよね。

f:id:edvakf:20111209063950p:image

compositionstartがcancelableになっているのはこれを解決しようとしてのことだと思います。標準IMEをキャンセルしちゃってキー打つごとにサジェスト出したらいいじゃん、って感じです。

しかし、今のところcompositionstartをpreventDefaultできるのはIE9だけでした。MozillaのMDNにはこう書かれています。

Note: This event should handle starting the text composition system, but in Gecko it's the other way around; when the system starts its composition system, Gecko fires this event.

Gecko notes

According to the DOM Level3 specification, compositionstart is cancelable; however, Gecko doesn't currently let you cancel them.

Gecko fires this event when IME starts composition, and some platforms don't have an API for canceling composition once it's begun. In addition, Gecko can't know whether a keyboard event will start composition or not until IME actually starts composition. Because of this, event.preventDefault() doesn't work on compositionstart events in Gecko.

compositionstart - MDN

たぶんChromeも似たような理由でcancelableにしていないんだと思います。

標準のIMEをスタートさせないって、ユーザビリティとしてはどうなのかなとも思いますけどねぇ。


もうOSのIMEなんかに頼らずウェブサイト側がIMEを提供しちゃえばいいんじゃね?みたいなぶっ飛んだ提案をGoogle Chromeチームの人が出してたこともあります。(ウェブサイト側というよりChrome OSやExtensionのことを睨んだものかもしれませんが)

ここから続くスレッドで、こっちに飛んで、最後はここまで続いて、まあ否定的に立ち消えになってます。


CompositionEventの話から逸れました。

Chromeの実装でおかしなところを見つけたのでこれも紹介しておきます。

Some implemenations may populate the data attribute of the compositionstart event with the text currently selected in the document (for editing and replacement); otherwise, the value of the data attribute must be the empty string.

Document Object Model (DOM) Level 3 Events Specification

仕様によると、compositionstartでは.dataが空文字か、またはIMEで入力しようとするときに選択中でこれから置き換えられるはずのテキストにせよと書いてあるんですが、Chromeではcompositionupdateと同じ、つまり最初の文字が入ってます。

その他、他のイベントとの順番や、イベントリスナー中でtextarea.valueを取ったときの値などを見るといろいろ食い違ってるので、やっぱりキーイベントは鬼門だなと思った次第です。


あ、そうそう、DOM3 Eventsのキーイベントでもう一つの大きな目玉といえば、.keyですよね。keyIdentifierを置き換えたあれです。これもIE9ではしっかり実装されてました。ChromeとFirefoxはまだです。あとgetModifierStateというメソッドe.shiftKeyとかの代わりにe.getModifierState('Shfit')とか書けるやつ)もIE9では実装されてました。すごい。

keyIdentifierやkey valueについてはこのブログに昔書いたことがありますので参考までに。


また話が逸れました。

まあそういうことで、今のところはサジェストでタイマー走らせないでいいよってぐらいしか良い所がなさそうなCompositionEventですが、その他の使い道を思いついた人は教えてください。

それでは、Happy Christmas!

nanto_vinanto_vi2011/12/10 01:14> FirefoxやOperaなどはCompositionEvent以外には何もイベントを出さない

一応Mozillaにはtextイベントなるものがあるそうです。
http://tech.kayac.com/archive/firefox-text-event.html

edvakfedvakf2011/12/10 01:26ありがとうございます。デモをアップデートしておきました。

2009-11-14

DOM3 Eventsのキーイベント

08:33

この前ちょこっと疑問点を書きましたが、そのあたりも全部 Web Applications Working Group Teleconference にて話されていました。

気になるところをいくつか書いていきます。


1

キーイベントの key プロパティ (key value と呼びます) は、原則としてそのキーの文字そのものが入ります。

event.key === 'a'

など。


2

Shift が押されている場合はちょっと自信がないですが、例を見る感じだと if (event.key == 'a' && event.shiftKey) と判断するのではなくて、直接 if (event.key == 'A') と判断するようです。

  1. keydown: 'Shift', shiftKey
  2. keydown: 'Q' ('\u0051', Latin Capital Letter Q key), shiftKey
  3. textInput: 'Q'
  4. keyup: 'Q' ('\u0051', Latin Capital Letter Q key), shiftKey
  5. keyup: 'Shift'
Document Object Model (DOM) Level 3 Events Specification

keyIdentifier とは違いますよね、たぶん。

参考 (にはならないけど) → SafariとChromeではkeyIdentifierが使えるので素晴らしすぎます - JavaScriptで遊ぶよ - g:javascript


3

対応する Unicode 文字のないキーの場合は、keyIdentifier と同じようにキーの名前が入ります。

event.key === 'Enter'

など。


4

制御キー等の場合は Unicode エスケープで書かないといけないので注意が必要です。

if (event.key == '\u0008') { // 'Backspace'
  //
} else if (event.key == '\u001B') { // 'Esc'
  //
} else if ('\u0009') { // 'Tab'
  //
}

など。


5

サロゲートペアの問題ですが、それも「文字そのもの」を返すということになったので心配はなくなりました。

event.key == '𣧂'  // == '\uD84E\uDDC2'

実際にこの文字を入力するキーボードは存在するそうです。

DS: maciej wasn't sure that there are keys that result in a non BMP character

... however there appears there are

... characters "tree stub" and "untidy" are such characters

http://www.w3.org/2009/11/02-webapps-minutes.html#item08

サロゲートペアの文字は JS では長さ2ということになるので、押されたキーが Unicode かどうかを判断するのに if (event.key.length == 1) としてはいけません。

それでも if (event.key.length <= 2) と書く人がいるかもしれないので、今 F1 などとなっているファンクションキーの名前を Function1 などにしてはどうかと Hixie さんが言ってましたが、まだどうなるかわかりません。(たぶんならない。Unicode がサロゲート「ペア」の範囲で収まる保証もないし、無意味だと思う)


6

↓ここらへんで話されていましたが、ウムラウトやアクセント記号などの入力補助のための dead key (それ単体では文字を入力しないが、次に押されるキーと組み合わせて1文字になるキー) はどうなるのかよくわかりません。

たぶん制御キー等と同じように

if (event.key == '\u0300') { // 'DeadGrave'
  //
} else if (event.key == '\u0301') { // 'DeadEacute'
  //
}

などと判断することになるのかな。


7

IME を使っている場合は composition イベントが出るようになるので、Google のトップページのようにそのために毎秒100回入力欄をチェックする必要はなくなるかもしれません。

  1. keydown: 's' ('\u0073', Latin Small Letter S key)
  2. compositionstart: ''
  3. keyup: 's' ('\u0073', Latin Small Letter S key)
  4. keydown: 'i' ('\u0069', Latin Small Letter I key)
  5. keyup: 'i' ('\u0069', Latin Small Letter I key)
  6. keydown: 'Convert'
  7. compositionupdate: '詩'
  8. keyup: 'Convert'
  9. keydown: 'Convert'
  10. compositionupdate: '市'
  11. keyup: 'Convert'
  12. keydown: 'Accept'
  13. compositionend: '市'
  14. textInput: '市' ("inputMode": 'DOM_INPUT_METHOD_IME')
http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#keyset-unicode

8

key value では、キーボード上でのキーの位置までは感知しません。Google の坊野さんの質問に対する Doug Schepers さんの回答に詳しいです。

つまり、例えばロシア語のキー配列の場合は q のキーを押したとしても event.key は "й" になるので、event.key == 'q' だけで判断した場合はロシア語配列の人はショートカットが使えないことになります。ページ側は、キーボードショートカットとして使用するキー配列を選んでもらうようにするのが安全だとのことです。


9

最後に、この仕様では JavaScript の正規表現に構文を追加することを求めています。↓一番最後の一節。

With key values and regular expressions, however, authors can support selective and intuitive ranges for key-based input, in a cross-platform manner with advanced internationalization support, such as the following pseudocode:

  if ( event.key == "-" || 
       event.key.match("\p{Sc}") || 
       event.key.match("\p{N}") ) {
    // minus sign, any currency symbol, and numeric characters (regardless of key location)
    ... 
  }
  else if ( event.key.match("\p{L}") ) {
    // alphabetic characters from any language, upper and lower case
    ... 
  }
  else {
    ... 
  } 

In addition, because Unicode categorizes each assigned code point into a group of code points used by a particular human writing system, even more advanced capabilities are possible. For example, an author can match characters from a particular human script (e.g. Tibetan) using a regular expression such as \p{Tibetan}, to filter out other characters, or discover if a code point is in a certain code block (range of code points), using a regular expression like \p{InCyrillic}.

To facilitate this, implementations should support Unicode range detection using regular expressions, in a manner such as the Perl Compatible Regular Expressions (PCRE) [PCRE].

http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#keyset-unicode

例えば \p{L} はラテン文字の意味。


感想

ちょっと納得いかないところもありますが、今までよりはかなりキーイベントが扱いやすくなるんじゃないでしょうか。

key value が返すのは「文字そのもの」なので、仕様書から '\uXXXX' のような表現は無くしてほしいなあ。そもそも '\uXXXX' は ECMAScript では定義されているけど他の言語では定義されていない場合もあるわけだし。(他の言語ではその言語に対応する表現で「文字そのもの」を返すことになる)


KeyboardEvent クラス

仕様を読んでたらまだおもしろそうなのがあった。

  // Introduced in DOM Level 3:
  interface KeyboardEvent : UIEvent {

  // KeyLocationCode
  const unsigned long       DOM_KEY_LOCATION_STANDARD      = 0x00;
  const unsigned long       DOM_KEY_LOCATION_LEFT          = 0x01;
  const unsigned long       DOM_KEY_LOCATION_RIGHT         = 0x02;
  const unsigned long       DOM_KEY_LOCATION_NUMPAD        = 0x03;
  const unsigned long       DOM_KEY_LOCATION_MOBILE        = 0x04;
  const unsigned long       DOM_KEY_LOCATION_JOYSTICK      = 0x05;

  readonly attribute DOMString       key;                   
  readonly attribute unsigned long   location;
  readonly attribute boolean         ctrlKey;
  readonly attribute boolean         shiftKey;
  readonly attribute boolean         altKey;
  readonly attribute boolean         metaKey;
  readonly attribute boolean         repeat;
  boolean            getModifierState(in DOMString keyArg);
  void               initKeyboardEvent(in DOMString typeArg, 
                                     in boolean canBubbleArg, 
                                     in boolean cancelableArg, 
                                     in views::AbstractView viewArg, 
                                     in DOMString keyArg, 
                                     in DOMString characterArg, 
                                     in DOMString nameArg, 
                                     in DOMString unicodeArg, 
                                     in unsigned long locationArg, 
                                     in DOMString modifiersListArg,
                                     in boolean repeat);
  };

10

event.location でキーのおおまかな位置が取得できる。一部のブラウザでは既に keyLocation として使えていたものから名称変更。

Shift と左 Shift を区別したい時とか、テンキーの数字と通常キーボードの数字を区別したい時などに使う。


11

keydown 時などで、「キーを押したまま」だった場合は event.repeated が true となる。押したままなんだったら keydown じゃないだろ、と思うところだけど、互換性的な理由 (?) からそういう場合は keydown → keypress が何度も出るブラウザが多い (Opera 以外はそうだったと思う)。


12

event.getModifierState('Alt') // true or false など。

A modifier key value. Common modifier keys are 'Alt', 'AltGraph', 'CapsLock', 'Control', 'Meta', 'NumLock', 'Scroll', or 'Shift'.

shiftKey みたいなのだど、一般的ではないモディファイアキーに対応できないため。


13

keypress について。

Note: the keypress event is traditionally associated with detecting a character value rather than a physical key, and may not be available on all keys in some configurations.

Warning: the keypress event type is defined in this specification for reference and completeness, but this specification deprecates the use of this event type.

http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#events-keyboard-event-order

keypress は互換性のために残してあるけど、deprecated となったので使うべきではない。

2009-11-04

02:23

疑問が解決したのでちゃんと書きました。→DOM3 Eventsのキーイベント - JavaScriptで遊ぶよ - g:javascript


keyIdentifier が DOM3 Events から無くなることになりました。

新しく event.key というプロパティができて、ほぼ event.keyIdentifier と同じになります。

唯一の大きな変更は、keyIdentifier では U+xxxx だったところが \uxxxx となるらしいのですが、ここのところがよくわかりません。

character value

In the context of key values, a character value is a string representing a single Unicode character, such as a letter or symbol, as a UTF-16 character escape (e.g. '\u0041' for the Latin Capital Letter A key, A.).

http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#glossary-character-value

これはつまり \u0041 と書いてあるけども、event.key === 'A' ということでいいのでしょうか? event.key === '\\u0041' ではなくて。


メーリングリストでも、

I really really don’t see the benefit of that change. It only 
complicates things because it looks similar to a JS-string encoded 
character but is not actually one. I.e., "\u2018" does not equal 
"\\u2018". By introducing a backslash, I can already see people getting 
confused by this, writing:

   if (event.key == "\u2018")

instead of

   if (event.key == "\\u2018")

I know I often forget to double-escape the backslash (especially when I 
write regular expressions in strings :)), and then scratch my head over 
why it doesn’t work.
Re: Changes to DOM3 Events Key Identifiers from Laurens Holst on 2009-10-30 (www-dom@w3.org from October to December 2009)

とか、

> The "\uxxxx" syntax is just reminiscent of a programming language
> but has little to do with strings returned from DOM APIs. I suppose
> you could argue that it's useful in some situations, but my concern
> is more about the existence of 2 things that mean the same
> thing.

"\uxxxx" is not a syntax, it is a Unicode string of the actual  
character. \u introduces the escape sequence for a unicode code point.  
So you can compare it directly to a character.
Re: Changes to DOM3 Events Key Identifiers from Maciej Stachowiak on 2009-10-30 (www-dom@w3.org from October to December 2009)

とか書いてあって、たぶん全員がわかってるわけじゃないと思います。(僕もわかりません)

たぶん Maciej さんが書いてるほう (event.key === 'A') が正しいと思うのですが…

だとすれば、 Backspace や Tab を判定する時はやっぱり、

if (event.key === '\u0008') { // Backspace
  //
} else if (event.key === '\u0009'){ // Tab
  //
}

みたいなふうになるのでしょうか? これだったらコードの可読性は上がらないですよね。

SVG Tiny で勧告されている keyIdentifier との互換性を捨てて作るにしては利点が小さい気がするんですよねぇ。

どうなんでしょう。


ついでなので、

issue のところに書いてありますが、サロゲートペアの問題があります。

つまり、

Unicode code point

A Unicode code point is a unique hexadecimal number signifying a character by its index in the Unicode codespace (or library of characters). In the context of key values, a Unicode code point is expressed as a string in the format "\u" followed by a hexadecimal character index in the range 0000 to 10FFFF, using at least four digits. See also character value.

http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#glossary-unicode-code-point

とあるので、「𣧂」は "\u0239c2" とすることになってしまいます。でも JavaScript では '𣧂' !== "\u0239c2" です。

トラックバック - http://javascript.g.hatena.ne.jp/edvakf/20091104

2009-06-30

SafariとChromeではkeyIdentifierが使えるので素晴らしすぎます

20:47

keyIdentifier は DOM3 Events から削除され、key value というものに置き換わりました。詳しくはこちらを参考に。



以下の内容は古いです。絶対に参考にしないでください。

DOM3 の keyIdentifier という仕様は、現在これほどまでにブラウザ毎、OS 毎でぐちゃぐちゃになっているキーボードイベントを統一するためのもの。


実は Safari と Chrome では既に実装していたそうな。(もっとずっと先の話かと思ってた)

使い方は、keydown か keyup の EventListener において、event.keyIdentifier を見る。

Unicode に対応するキーに関しては U+0009 といった Unicode のコードポイントで表され、それ以外のキーは "Enter" とか "Left" とかになる。


現在のところ、Mac OS X 上で、Chromium の最新ビルドと Safari 4 ではキーイベントに若干の違いがある。Safari では Shift や Control というモディファイアキーもイベントが出るのに、Chromium では出ないとか、Chromium でイベントをキャンセルできる条件が結構ややこしいといった具合に。

そのへんのバグはまだ Alpha なので無視すると、キーとモディファイアに一致するキーがだいたいこんな感じで得られる。(p 関数が定義されているとする)

  window.addEventListener('keydown',function(e){
    p(get_key(e));
    e.preventDefault();
  },false)

  var keyId = {
    "U+0008" : "BackSpace",
    "U+0009" : "Tab",
    "U+0018" : "Cancel",
    "U+001B" : "Esc",
    "U+0020" : "Space",
    "U+0021" : "!",
    "U+0022" : "\"",
    "U+0023" : "#",
    "U+0024" : "$",
    "U+0026" : "&",
    "U+0027" : "'",
    "U+0028" : "(",
    "U+0029" : ")",
    "U+002A" : "*",
    "U+002B" : "+",
    "U+002C" : ",",
    "U+002D" : "-",
    "U+002E" : ".",
    "U+002F" : "/",
    "U+0030" : "0",
    "U+0031" : "1",
    "U+0032" : "2",
    "U+0033" : "3",
    "U+0034" : "4",
    "U+0035" : "5",
    "U+0036" : "6",
    "U+0037" : "7",
    "U+0038" : "8",
    "U+0039" : "9",
    "U+003A" : ":",
    "U+003B" : ";",
    "U+003C" : "<",
    "U+003D" : "=",
    "U+003E" : ">",
    "U+003F" : "?",
    "U+0040" : "@",
    "U+0041" : "a",
    "U+0042" : "b",
    "U+0043" : "c",
    "U+0044" : "d",
    "U+0045" : "e",
    "U+0046" : "f",
    "U+0047" : "g",
    "U+0048" : "h",
    "U+0049" : "i",
    "U+004A" : "j",
    "U+004B" : "k",
    "U+004C" : "l",
    "U+004D" : "m",
    "U+004E" : "n",
    "U+004F" : "o",
    "U+0050" : "p",
    "U+0051" : "q",
    "U+0052" : "r",
    "U+0053" : "s",
    "U+0054" : "t",
    "U+0055" : "u",
    "U+0056" : "v",
    "U+0057" : "w",
    "U+0058" : "x",
    "U+0059" : "y",
    "U+005A" : "z",
    "U+005B" : "[",
    "U+005C" : "\\",
    "U+005D" : "]",
    "U+005E" : "^",
    "U+005F" : "_",
    "U+0060" : "`",
    "U+007B" : "{",
    "U+007C" : "|",
    "U+007D" : "}",
    "U+007F" : "Delete",
    "U+00A1" : "¡",
    "U+0300" : "CombGrave",
    "U+0300" : "CombAcute",
    "U+0302" : "CombCircum",
    "U+0303" : "CombTilde",
    "U+0304" : "CombMacron",
    "U+0306" : "CombBreve",
    "U+0307" : "CombDot",
    "U+0308" : "CombDiaer",
    "U+030A" : "CombRing",
    "U+030B" : "CombDblAcute",
    "U+030C" : "CombCaron",
    "U+0327" : "CombCedilla",
    "U+0328" : "CombOgonek",
    "U+0345" : "CombYpogeg",
    "U+20AC" : "€",
    "U+3099" : "CombVoice",
    "U+309A" : "CombSVoice",
  }
  function get_key(evt){
    var key = keyId[evt.keyIdentifier] || evt.keyIdentifier,
    ctrl = evt.ctrlKey ? 'C-' : '',
    meta = (evt.metaKey || evt.altKey) ? 'M-' : '',
    shift = evt.shiftKey ? 'S-' : '';
    if (/^(Meta|Shift|Control|Alt)$/.test(key)) return key; // safari only
    if (evt.shiftKey){
      if (/^[a-z]$/.test(key)) 
        return ctrl+meta+key.toUpperCase();
      if (/^(Enter|Space|BackSpace|Tab|Esc|Home|End|Left|Right|Up|Down|PageUp|PageDown|F(\d\d?))$/.test(key)) 
        return ctrl+meta+shift+key;
    }
    return ctrl+meta+key;
  }

例えば

i
C-j
C-Space
M-Enter
C-u
C-U
C-M-d
C-M-D
C-S-BackSpace
M-S-Enter

など。

U みたいに、Shift されているかを気にしなくてもいいものは S- を付けず、Enter のように、Shift があるかどうか判断できないものについては付けることにした。


どうやら落とし穴があるっぽい

os0x さんの os0x / ChromeKeyconfig のソースを読んでて知ったのだけど、どうやら Windows では上の通りに keyIdentifier が取得できないっぽい。

そのソースから抜粋。

var winkeys = {
	"U+00BC":",",
	"U+00BE":".",
	"U+00BF":"/",
	"U+00E2":"\\",
	"U+00BB":";",
	"U+00BA":":",
	"U+00DD":"]",
	"U+00C0":"@",
	"U+00DB":"[",
	"U+00BD":"-",
	"U+00DE":"^",
	"U+00DC":"\\"
};
var shiftWinkeys = {
	"U+00BC":"<",
	"U+00BE":">",
	"U+00BF":"?",
	"U+00E2":"_",
	"U+00BB":"+",
	"U+00BA":"*",
	"U+00DD":"}",
	"U+00C0":"`",
	"U+00DB":"{",
	"U+00BD":"=",
	"U+00DE":"~",
	"U+00DC":"|",
	"U+0031":"!",
	"U+0032":"\"",
	"U+0033":"#",
	"U+0034":"$",
	"U+0035":"%",
	"U+0036":"&",
	"U+0037":"'",
	"U+0038":"(",
	"U+0039":")"
};

マジキモいんですけどー…

本来 keyIdentifierShift が押されているかどうかなんて気にしなくても使えるように Unicode またはキー名で識別していたはずなのに。

ただの "U+00BC" だったら "," で、shiftKey が true だったら "<" になるなんて、identifier でもなんでもないじゃん。

めっちゃキー配列依存。"<" を入力するのに Shift を押さなくてもいいキーボードはどうするんだ。

Safari は知らないけど、Chrome の keyIdentifier は残念すぎる、と言っても過言ではなさそう。


仕様もちょっと変更してた

一番上のリンクが消えてて、仕様はこちらに移動してた。


Note: This section is normative.

The list of key identifiers contained in this appendix is not exhaustive and input devices may have to define their own key identifiers. Here is a algorithm to determine which key identifier to use:

  1. Consider the primary function of the key (i.e., without modifiers), taking into consideration the keyboard layout mapping in use, to determine if a corresponding Unicode character exists from which a key identifier may be derived. If multiple Unicode characters exist which correspond to the primary function of the key, the Unicode character with the lowest codepoint must be used.
    1. If the primary function of the key is to generate a character, and that character is in one of the Unicode character categories, then the key identifier shall be a string consisting of just that character. If the primary function of the key is to generate a character in class Ll for which there exists an equivalent, single character in class Lu, the uppercase character should be used instead.
      Is this necessary? Why can't we simply allow lowercase letters as well? If a script author wishes to do a comparison, they can cast the output to upper or lower case as needed. See ISSUE-23.
    2. If the primary function of the key is to generate a character that is not in one of the above general categories, or if the primary function of the key is a function for which there exists a corresponding Unicode character that is not in one of the above general categories, then:
      1. If there exists an appropriate key identifier in the key identifiers set, and that key identifier does not have a Unicode codepoint, that key identifier must be used.
      2. If there exists an appropriate key identifier in the key identifiers set, and that key identifier has a Unicode codepoint, and that key identifier is in one of the Unicode character categories, then the character value itself for that key identifier must be used.
      3. If there is no appropriate key identifier in the key identifiers set, then the key identifier is a string beginning with "U+" and followed by the Unicode codepoint of the character in hexadecimal, using at least four digits. Leading zeroes must be omitted unless they are required to make the codepoint use at least four digits.
  2. For keys with no corresponding Unicode character, a key identifier can be devised. The key identifier should be as human friendly as possible and must not contain whitespace. The identifier must be composed only of characters in the ranges U+0030..U+0039, U+0041..U+005A, or U+0061..U+007A, and must begin with a character in the range U+0041..U+005A.
    Why should we not allow names in other ranges (Chinese, for example)?

Document Object Model (DOM) Level 3 Events Specification