この日記はMozillaのプロダクトへの貢献者としての私の成果を中心に、気になったバグやWeb界隈の話題について書いていますが、 断り書きがある場合を除き、いかなる団体のオフィシャルな見解ではありません。あくまでも個人的なものです。 Mozilla Foundation、Mozilla Corporation、及び関連企業の公式情報ではないことに注意してください。

現在、XHTML 1.0 (もどき)から、HTML5なコンテンツに修正中です。古い日記は修正が完了していませんので表示が崩れます。 順次、修正していく予定ですのでしばらくお待ちください。

もずはっく日記(2014年4月)

2014年4月16日

Bug-org 930893 Implement D3E KeyboardEvent constructor
初回投稿日時: 2014年04月16日00時48分07秒
最終更新日時: 2014年04月25日16時27分06秒
カテゴリ: Events Javascript Mozilla Core Mozilla31 バグ修正
SNS: (list)

D3Eで定義された、KeyboardEventのコンストラクタを実装しよう、というバグです。

このバグの修正により、コンストラクタを利用して、全ての属性を初期化しつつ、untrustedイベントを生成することができます。

var keydown =
  new KeyboardEvent("keydown", { bubbles: true, cancelable: true,
                                 view: window, detail: 0,
                                 keyCode: 65, charCode: 0, which: 65,
                                 key: "a", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD };
var keypress =
  new KeyboardEvent("keypress", { bubbles: true, cancelable: true,
                                  view: window, detail: 0,
                                  keyCode: 0, charCode: 97, which: 97,
                                  key: "a", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD };
var keyup =
  new KeyboardEvent("keyup", { bubbles: true, cancelable: true,
                               view: window, detail: 0,
                               keyCode: 65, charCode: 0, which: 65,
                               key: "a", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD };

例えば、この例では、Geckoのaキーを押した時と同じイベントを生成しています。これを利用して、エディタ等を更新することはできませんが、Webアプリのイベントハンドラの自動テストには使いやすいかと思います。

なお、あわせて、このバグの修正時に、D3Eで定義されている、KeyboardEvent.initKeyboardEvent()も一応、実装しておきました。これの実装義務はもはや無いので、あくまでも、IE向けのコードとの互換性のためでしかありません。ですが、このメソッドはブラウザ毎に仕様が異なっているので要注意です。ちなみに、Geckoには昔から、KeyEvent.initKeyEvent()というメソッドも存在していますので、あわせて紹介しておきましょう。

ブラウザ実装しているメソッド
DOM Level 3 Events
void initKeyboardEvent(DOMString typeArg,
                       boolean canBubbleArg,
                       boolean cancelableArg,
                       window? viewArg,
                       long detailArg,
                       DOMString keyArg,
                       unsigned long locationArg,
                       DOMString modifiersListArg,
                       boolean repeatArg)
Gecko
void initKeyEvent(DOMString typeArg,
                  boolean canBubbleArg,
                  boolean cancelableArg,
                  window? viewArg,
                  boolean ctrlKeyArg,
                  boolean altKeyArg,
                  boolean shiftKeyArg,
                  boolean metaKeyArg,
                  unsigned long keyCodeArg,
                  unsigned long charCodeArg)
void initKeyboardEvent(DOMString typeArg,
                       boolean canBubbleArg,
                       boolean cancelableArg,
                       window? viewArg,
                       long detailArg,
                       DOMString keyArg,
                       unsigned long locationArg,
                       DOMString modifiersListArg,
                       boolean repeatArg)
IE
void initKeyboardEvent(DOMString typeArg,
                       boolean canBubbleArg,
                       boolean cancelableArg,
                       window? viewArg,
                       long detailArg,
                       DOMString keyArg,
                       unsigned long locationArg,
                       DOMString modifiersListArg,
                       boolean repeatArg,
                       DOMString localeArg)
Blink
void initKeyboardEvent(DOMString typeArg,
                       boolean canBubbleArg,
                       boolean cancelableArg,
                       window? viewArg,
                       DOMString keyIdentifierArg,
                       boolean ctrlKeyArg,
                       boolean altKeyArg,
                       boolean shiftKeyArg,
                       boolean metaKeyArg,
                       boolean altGraphKeyArg)
WebKit

このように、互換性は最悪な状況です。IEは、D3Eにかつて存在していた、locale属性を初期化する引数が、現在の標準仕様の最後に追加されている形になっています。この引数を指定した場合でも、Geckoでは、エラーなく動くことは確認済みで、自動テストにも含めていますので、IE向けのコードはGeckoでそのまま動きます。ただし、Geckoはlocale属性をサポートしていませんので、この引数は無視されます。

Blink/WebKitでは、過去の仕様案に従っているのか、現在からは考えられないような引数構成になっています。detail属性は初期化できない上、key属性ではなく、独自のkeyIdentifier属性を初期化する引数があり、さらに、AltGrキーの状態を指定する引数が最後にあります。

幸い、Blinkでは、コンストラクタをサポートしていることを確認していますので、コンストラクタが存在しない場合に、try-catchを利用して、フォールバックしていく以外に、スマートなやり方はなさそうです。

最後に、keyCodecharCodewhich属性の値について、注意があるので書いておきます。

これらのレガシー属性は、Blinkのコンストラクタを利用しても初期化できないことは確認しています。つまり、今のところ、これらの属性値を初期化できるのは、Geckoのみということになります。

ですが、Geckoにも少し、癖があります。まず、コンストラクタで初期化した場合、初期化時に指定した値がそのままセットされます。未指定時には初期値の0になります。

それに対し、initKeyEventで初期化した場合、keyCode属性は、イベントのtype属性値が、keydownもしくはkeyupでない場合、無視され、0がセットされます。ただし、keypressの場合は、charCodeがゼロの場合のみ、無視されません。

同様に、initKeyEventで初期化した場合、charCode属性は、イベントのtype属性値が、keypress以外の場合、無視され、0がセットされます。

initKeyEventで初期化した場合、which属性値は直接初期化できませんが、keyCode属性値、もしくは、charCode属性値と同じものがセットされます。type属性値が、keydown、もしくは、keyupの場合は、keyCode属性値がセットされ、keypressの場合は、keyCode値が、DOM_VK_RETURN (0x0D)、もしくは、DOM_VK_BACK_SPACE (0x08)の場合は、keyCode属性値がセットされ、それ以外の場合は、charCode属性値がセットされます。type属性値がこれらのいずれでもない場合は、常に0となります。

initKeyEvent()で初期化した場合の挙動は、trustedイベントの挙動と全く同じものなので、この様に、複雑なものとなっています。今回の修正で単純化も考えましたが、そもそも、このようなレガシーなメソッドを利用しているコードの互換性を壊すのも意味が無いことなので、コンストラクタでの初期化時のみ、その動作を単純化しています。

KeyboardEvent.initKeyboardEvent()の実装は取りやめになりました。IEのものと互換性が無いことが分かったため、feature detectionを完全に壊してしまうことがその理由です。GeckoとIEでは、コンストラクタを利用し、WebKitとBlinkでは、非標準のinitKeyboardEvent()を利用するようにしてください。

関連するかもしれないエントリ

bug-org 930893を含むエントリ