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

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

もずはっく日記(2015年2月)

2015年2月21日

Bug-org 1039006 Enable the preference for CSS Ruby (layout.css.ruby.enabled) by default 初回投稿日時: 2015年02月21日17時32分31秒
カテゴリ: CSS Firefox Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022100
SNS: (list)

CSS3のruby moduleをデフォルトで有効にしようというバグですが、ついに、修正されました。CSS3 rubyは以前から、Xidorn Quanさんがアジア系の言語のレイアウトの改善の一環として、せっせと作業されていましたが、ついに大物が実を結んだというところです。このまま何も大きな問題が発生しなければ、Firefox 38から有効になります。

では、いくつか、実際にどうなっているか確認してみましょう。

.ruby {
  display: ruby;
  border: solid green 1px;
  padding: 1px;
}
.rubyBase {
  display: ruby-base;
  border: dotted aqua 1px;
  white-space: nowrap;
  padding: 1px;
}
.rubyText {
  display: ruby-text;
  border: dotted red 1px;
  white-space: nowrap;
  padding: 1px;
}

以下のサンプルではこのようにスタイル指定を行っています。

<p>
  前テキスト
  <span class="ruby">
    ルビの子ノード
    <span class="rubyText">
      ルビテキストの子ノード
    </span>
  </span>
  後テキスト
</p>
前テキスト ルビの子ノード ルビテキストの子ノード 後テキスト

まずは、<ruby>要素にルビベースとなるテキストを直接書いて、<rt>要素を記述した場合に相当するケースです。ルビボックス内のルビベースと、ルビテキストは、出現順にペアリングされていきますが、この場合、ルビテキストの前に明示的なルビベースがありませんので、このテキストノードの周りに、ルビベースと、ルビベースコンテナの匿名ボックスが生成される形になっているのだと思います。ですので、期待通りに、ルビの子ノードに、ルビテキストの子ノードがルビテキストとして振られている形になるのが正解です。

ちなみに、このテストでは、ルビテキストにfont-sizeを指定していませんので、同じフォントサイズで表示されなくてはいけません。また、ルビテキストは、仕様書ではruby annotationと呼ばれていますが、カタカナにするとアレなので、この記事内では、ルビテキストという単語を使い続けます。

<p>
  前テキスト
  <span class="ruby">
    <span class="rubyBase">
      ルビベースの子ノード
    </span>
    <span class="rubyText">
      ルビテキストの子ノード
    </span>
  </span>
  後テキスト
</p>

前テキスト ルビベースの子ノード ルビテキストの子ノード 後テキスト

この例では、XHTMLに存在し、HTML5ではもめていた、<rb>要素にあたるものを明示した場合のサンプルです。当然、ルビベースにルビテキストが振られているのが正解です。

<p>
  前テキスト
  <span class="ruby">
    <span class="rubyText">
      ルビテキストの子ノード
    </span>
    ルビの子ノード
  </span>
  後テキスト
</p>
前テキスト ルビテキストの子ノード ルビの子ノード 後テキスト

続いて、ルビテキストを先に記述した場合です。ルビベースと、ルビテキストのペアリングに関する仕様によると、必ず、ルビベースが先に存在し、ルビテキストがそれに続きます。CSSの理想からすると、ボックスの順序に意味を持たせるのはあり得ませんが、簡素で使いやすい仕様の形を考えると、致し方ないことだろうとは思います(深くは考えてみたことありませんが)。

このため、ルビテキストの前に何もない場合、2.3.1. Segment Pairing and Annotation Levelsによると、ルビテキストの直前に空のルビベースが存在し、次のルビベースとなっている、ルビの子ノードは、ルビテキストを持たない形になっているのが正解です。

<p>
  前テキスト
  <span class="ruby">
    <span class="rubyBase">
      ルビベースの子ノード
    </span>
    ルビの子ノード
  </span>
  後テキスト
</p>
前テキスト ルビベースの子ノード ルビの子ノード 後テキスト

この例では逆に、ルビテキストを明示していませんが、ルビ子ノードを明示しています。しかし、このケースについては、ペアリングにおいては特に触れられていません。ルビの子ノードは、そのまま、もうひとつのルビベースとなっているかのように、Geckoはレンダリングします。

<p>
  前テキスト
  <span class="ruby">
    <span class="rubyBase">
      ルビベースの子ノード
    </span>
    ルビの子ノード
    <span class="rubyText">
      ルビテキストの子ノード
    </span>
  </span>
  後テキスト
</p>
前テキスト ルビベースの子ノード ルビの子ノード ルビテキストの子ノード 後テキスト

では、先の例のルビの子ノードの後ろに、ルビテキストを追加するとどうなるでしょうか? その結果、Geckoでは、ルビテキストの子ノードは、ルビベースの子ノードに振られる形になっています。

<p>
  前テキスト
  <span class="ruby">
    <span class="rubyBase">
      ルビベースの子ノード
    </span>
    ルビの子ノード
    <span class="rubyText">
      ルビテキスト1の子ノード
    </span>
    <span class="rubyText">
      ルビテキスト2の子ノード
    </span>
  </span>
  後テキスト
</p>
前テキスト ルビベースの子ノード ルビの子ノード ルビテキスト1の子ノード ルビテキスト2の子ノード 後テキスト

今度は、二つ目のルビテキストを追加してみました。この例では、二つ目のルビテキストの子ノードが、ルビノードの子ノードに振られています。ということは、ルビの子ノードはルビテキストとの位置関係にかかわらず、ルビベースとして処理されている様です。

<p>
  前テキスト
  <span class="ruby">
      <span class="rubyBase">
        ルビベース1の子ノード
      </span>
      <span class="rubyBase">
        ルビベース2の子ノード
      </span>
      <span class="rubyText">
        ルビテキスト1の子ノード
      </span>
      <span class="rubyText">
        ルビテキスト2の子ノード
      </span>
  </span>
  後テキスト
</p>
前テキスト ルビベース1の子ノード ルビベース2の子ノード ルビテキスト1の子ノード ルビテキスト2の子ノード 後テキスト

先の例で、ルビテキスト二つに先行するテキストノードを二つとも、ルビベースと定義しても、当然、同じ様にペアリングされます。

<p>
  前テキスト
  <span class="ruby">
      <span class="rubyBase">
        ルビベース1の子ノード
      </span>
      <span class="rubyText">
        ルビテキスト1の子ノード
      </span>
      <span class="rubyBase">
        ルビベース2の子ノード
      </span>
      <span class="rubyText">
        ルビテキスト2の子ノード
      </span>
  </span>
  後テキスト
</p>
前テキスト ルビベース1の子ノード ルビテキスト1の子ノード ルビベース2の子ノード ルビテキスト2の子ノード 後テキスト

HTML5仕様の様に、交互にルビベースと、ルビテキストを配置しても、当然、期待通りにレンダリングされます。

<p>
  前テキスト
  <span class="ruby">
      <span class="rubyBase">
        ルビベースの子ノード
      </span>
      <span class="rubyText">
        ルビテキスト1の子ノード
      </span>
      <span class="rubyText">
        ルビテキスト2の子ノード
      </span>
  </span>
  後テキスト
</p>
前テキスト ルビベースの子ノード ルビテキスト1の子ノード ルビテキスト2の子ノード 後テキスト

ルビテキストが多い場合、余ったルビテキストは、匿名の空のルビベースとペアリングされますので、二つ目のルビテキストは何もルビベースが存在しないところに振られることになります。

コンテナまで検証しだすと、キリが無さそうなので、とりあえずこのあたりで。また、機会があれば、ネストしたケースや、改行、ルビベースや、ルビテキスト中にテキストノード以外が混在している場合について検証してみたいと思います。

Bug-org 1055665 implement vertical positioning of CSS ruby, including ruby-position property (other than the 'inter-character' value) 初回投稿日時: 2015年02月21日18時27分48秒
カテゴリ: CSS Mozilla Core Mozilla37 バグ修正
固定リンク: id=2015022101
SNS: (list)

CSS3のruby-positionについても、overunderについては実装が終わってる模様。早速、テスト。

<span style="display: ruby;">
  <span style="display: ruby-base;">ルビベース</span>
  <span style="display: ruby-text-container; ruby-position: under;">
    <span style="display: ruby-text;">ルビテキスト</span>
  </span>
</span>
ルビベース ルビテキスト

仕様書によると、ruby-positionは、ルビテキストコンテナに指定しないといけないらしい。この例だと、ルビテキストはルビベースの下側に表示されるのが正しい。

<span style="display: ruby;">
  <span style="display: ruby-base;">ルビベース</span>
  <span style="display: ruby-text; ruby-position: under;">ルビテキスト</span>
</span>
ルビベース ルビテキスト

それに対して、この例では、ルビテキストがルビベースの上にあるのが正解。

Bug-org 1055676 implement CSS 'ruby-align' property 初回投稿日時: 2015年02月21日19時05分43秒
カテゴリ: CSS Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022102
SNS: (list)

ルビテキストの配置をコントロールする、ruby-alignも実装されているみたいなので、早速、実験。

前のテキスト
<span style="display: ruby; ruby-align: start;">
  <span style="display: ruby-base;">長いルビベース</span>
  <span style="display: ruby-text;">3文字</span>
</span>
後ろのテキスト
前のテキスト 長いルビベース 3文字 後ろのテキスト

日本語なら、短いルビテキストが左寄せになってるのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: center;">
  <span style="display: ruby-base;">長いルビベース</span>
  <span style="display: ruby-text;">3文字</span>
</span>
後ろのテキスト
前のテキスト 長いルビベース 3文字 後ろのテキスト

短いルビテキストが中央寄せになってるのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: space-between;">
  <span style="display: ruby-base;">長いルビベース</span>
  <span style="display: ruby-text;">3文字</span>
</span>
後ろのテキスト
前のテキスト 長いルビベース 3文字 後ろのテキスト

短いルビテキストがtext-align: justify;のように、均等割で配置されているのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: space-around;">
  <span style="display: ruby-base;">長いルビベース</span>
  <span style="display: ruby-text;">3文字</span>
</span>
後ろのテキスト
前のテキスト 長いルビベース 日本語 後ろのテキスト

日本語のような文字の場合、短いルビが、両端に余裕をもちつつ、間にスペースがあるのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: space-around;">
  <span style="display: ruby-base;">長いルビベース</span>
  <span style="display: ruby-text;">AB CD</span>
</span>
後ろのテキスト
前のテキスト 長いルビベース AB CD 後ろのテキスト

英語のような文字間で改行が発生しない文字の場合、短いルビが、両端に余裕をもちつつ、単語間にだけスペースを持つのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: start;">
  <span style="display: ruby-base;">ベース</span>
  <span style="display: ruby-text;">長いルビテキスト</span>
</span>
後ろのテキスト
前のテキスト ベース 長いルビテキスト 後ろのテキスト

日本語なら、短いルビベースが左寄せになってるのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: center;">
  <span style="display: ruby-base;">ベース</span>
  <span style="display: ruby-text;">長いルビテキスト</span>
</span>
後ろのテキスト
前のテキスト ベース 長いルビテキスト 後ろのテキスト

短いルビベースが中央寄せになってるのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: space-between;">
  <span style="display: ruby-base;">ベース</span>
  <span style="display: ruby-text;">長いルビテキスト</span>
</span>
後ろのテキスト
前のテキスト ベース 長いルビテキスト 後ろのテキスト

短いルビベースがtext-align: justify;のように、均等割で配置されているのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: space-around;">
  <span style="display: ruby-base;">ベース</span>
  <span style="display: ruby-text;">長いルビテキスト</span>
</span>
後ろのテキスト
前のテキスト ベース 長いルビテキスト 後ろのテキスト

日本語のような文字の場合、短いルビベースが、両端に余裕をもちつつ、間にスペースがあるのが正解です。

前のテキスト
<span style="display: ruby; ruby-align: space-around;">
  <span style="display: ruby-base;">AB CD</span>
  <span style="display: ruby-text;">長いルビテキスト</span>
</span>
後ろのテキスト
前のテキスト AB CD 長いルビテキスト 後ろのテキスト

英語のような文字間で改行が発生しない文字の場合、短いルビベースが、両端に余裕をもちつつ、単語間にだけスペースを持つのが正解です。

Bug-org 1020139 Support new KeyboardEvent.code value for Sun keyboard 初回投稿日時: 2015年02月21日20時41分01秒
カテゴリ: Android Events GTK Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022103
SNS: (list)

KeyboardEvent.codeで、Sunのキーボードにある特殊なキーもサポートしようというバグです。従来のD3Eでは定義しているキーが足りなかったため、これ用に定義を追加してもらうのに手間取り、約半年かかって、ようやく修正となりました。

KeyboardEvent.code value mapping table
Key LabelKeyboardEvent.code valueUSB HID name
Stop"BrowserStop"Stop
Again"Again"Again
Props"Props"Menu
Undo"Undo"Undo
Front"Select"Select
Copy"Copy"Copy
Open"Open"Execute
Paste"Paste"Paste
Find"Find"Find
Cut"Cut"Cut

これらのキーは、OS側の制限により、LinuxとAndroid上のGeckoでのみ、判別が可能です。

また、マッピングにはそれぞれ、ハードウェアキーコードと、スキャンコードから行っていますので、Stopと、マルチメディアキーボードにあるBrowserStopキーとは区別が付きません(これはブラウザにとっても区別付かないので仕方ないですが)。

ちなみに、何故、このバグにこだわって、仕様の拡張までお願いしたのかというと、Bugzillaに寄せられるバグ報告では、今でも、Sunのキーボードを利用している人というのが居ることが分かっているためです。そういったユーザが企業単位であるとしたら、その企業内のイントラネット上で必要になるかもしれませんので、対応を行いました。

ちなみに、仕様まわりや、レビュー周りの関係者全員、Sunのキーボードを持っていませんでしたので、その需要が少ないことには疑いの余地はありませんが。

Bug-org 1126673 Enable KeyboardEvent.code in default settings of release build 初回投稿日時: 2015年02月21日21時07分16秒
最終更新日時: 2015年02月21日21時14分44秒
カテゴリ: Events Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022104
SNS: (list)

Sunキーボードの問題が解決し、特に互換性等で問題になる可能性のある話も今のところ無いので、リリースビルドでもようやく、KeyboardEvent.codeを有効化しました。

重ね重ね警告しておきますが、KeyboardEvent.codeは、キーボード上の物理キーの位置を確認するためのものです。このため、ユーザが本当にPCのキーボードで操作している時にしか区別が付きませんし、WindowsやMacでは、OSがサポートしていないキーに関しては、値が空文字列、もしくはキーイベント自体が発生しない、ということになります。以下に、違いをまとめておきます。

属性値の意味モディファイアキーの状態に依存OSのキーボードレイアウト設定に依存
KeyboardEvent.key ユーザがそのキーを押したことで期待している効果。テキストを入力するキーの場合、 入力される文字がそのまま入っており、それ以外の場合は、キー固有の名前が入る。 するする
KeyboardEvent.code ユーザが押した物理キーのID。ソフトウェアキーボードを含む、 他のアプリから生成されたキーイベントの場合、空文字列のこともある(生成元に依存)。 しない(ただし、ハードウェアレベルのモディファイアキー、Fnキー等は例外)しない
KeyboardEvent.keyCode ユーザが押したキーを示す意味の無い数値。 元々はIEがWindowsの仮想キーコードをそのまま設定していたものなので、 ブラウザによって値の解釈や定義が異なる。特に、非Windows環境での互換性は皆無。 テキスト入力キーでは非依存、それ以外のキーは依存する
KeyboardEvent.charCode ユーザが押したキーで入力される文字のUnicode値(UTF-16?)KeyboardEvent.keyCodeに比べると互換性が高い。 するする

現在、KeyboardEventの実装はGeckoが最も最新仕様に準拠していて、実装している機能も最も多いので、これをリファレンスに研究してもらえれれば良いかと思います(他のブラウザが互換性の高い実装が可能なように、詳細な技術情報はMDNで公開し、W3CのワーキンググループのMicrosoftとGoogleの方々も把握しています)。

KeyboardEvent.keyがほとんどの処理ではユーザの期待する結果を表しています。ただし、これを利用する際には、仕様の変化に簡単に対応できるようにするために、コーディングに一手間かけることをお勧めします

それに対して、KeyboardEvent.codeは、例えばゲームで、WSADのキーを、上下左右の十字キーの変わりとして用いる場合に、ユーザが選択しているキーボードレイアウトに関係無く処理できるのが特徴です。QWERTYでも、AZERTYでも、Dvorakでも、同じキーからは同じ値が取得可能なわけです。キーの位置に意味がある操作の処理にのみ、有用でしょう。

2015年2月27日

Bug-org 1129406 window_nsITextInputProcessor.xul, line 917: TypeError: textareaInFrame is null 初回投稿日時: 2015年02月27日01時20分48秒
カテゴリ: Events Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022700
SNS: (list)

nsITextInputProcessorの自動テストのバグです。

作成時に、DOMContentLoadedイベントのリスナを、loadイベントのリスナに書き換えた際、ひとつだけ、removeEventListener()の引数を変更し忘れていたため、複数のloadイベントが実行され、予期しないタイミングで変数を参照して、エラーになっていました。

Bug-org 1131026 nsITextInputProcessor.init(ForTests) should be renamed to nsITextInputProcessor.beginInputTransaction(ForInit) 初回投稿日時: 2015年02月27日01時24分37秒
カテゴリ: Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022701
SNS: (list)

nsITextInputProcessorに、KeyboardEventの生成機能も統合するのであれば、それを利用する際にもいちいち、init()、もしくは、initForTests()を呼び出す必要があるというのは、名前からして分かり難いということで、それぞれをbeginInputTransaction()と、beginInputTransactionForTests()に名前を変更しました。

Bug-org 1133629 crash in mozilla::IMEContentObserver::OnMouseButtonEvent(nsPresContext*, mozilla::WidgetMouseEvent*) 初回投稿日時: 2015年02月27日01時37分29秒
カテゴリ: Mozilla Core Mozilla37 Mozilla38 TSF Windows バグ修正
固定リンク: id=2015022702
SNS: (list)

Firefox 35での、top crashの第16位のバグです。

これは、IMEへ、マウスボタンのイベントが発生したことを通知するXPレベルのコードで発生しているのですが、当初はなんでこんなマイナーな操作で、16位という高頻度での発生なのか分かりませんでした。しかし、よくよく考えてみると、これは、未確定文字列が無くても動作する部分でクラッシュしているので、かなり深刻なことに気付きました。

実際に再現するテストケースを作る余裕も無かったので、スタックトレースから推測してみると、マウスボタンイベントの発生位置にある文字の情報をIMEContentObserverが取得する際に、ContentEventHandlerにクエリを投げた際に、最新のレイアウト情報が必要なため、ペンディングになっているリフローが全て実行されます。この際に、フォーカスを持ったエディタが非表示になったりして、エディタがフォーカスを失い、IMEContentObserver自身が破棄されると、その直後にメンバにアクセスしたタイミングで、クラッシュすることが分かりました。

そこで、リスクを最小限に抑えるために、IMEContentObserver::OnMouseButtonEvent()の内部で、ContentEventHandlerを利用する前に、kungFuDeathGripIMEContentObserverrefCountがゼロにならないようにして、破棄されるのを防ぎ、その後、IMEContentObserverが破棄されていないか確認するように修正しています。

ただ、Firefox 36のリリースまで1週間を切っていたため、残念ながら36にはこの修正は入っていません。

もし、特定のサイトでマウスをクリックした瞬間によく落ちる方が居たら、この修正が入ってる、37 Betaをインストールすれば、このクラッシュバグを回避することができます。

Bug-org 1119609 event.key from nsIDOMWindowUtils.sendKeyEvent() is 'undefined' 初回投稿日時: 2015年02月27日03時25分43秒
カテゴリ: Events Firefox OS Mozilla Core Mozilla38 バグ修正
固定リンク: id=2015022703
SNS: (list)

nsIDOMWindowUtils.sendKeyEvent()を利用してKeyboardEventを生成しても、KeyboardEvent.key等、D3Eで追加された新機能の一部に対応していないというバグです。

そもそもの原因は、このメソッドが自動テスト用に設計されたため、最小のコストで実装するために、当時のKeyboardEventの生成に必要な情報を全て引数で個別に設定できるようにしていることでした。その後、フラグをオプションの引数として追加することで、KeyboardEvent.location等にはだましだまし対応していましたが、KeyboardEvent.keyや、KeyboardEvent.codeの対応には、どうしても引数の追加が必要になります。しかし、そうすると、アドオンとの互換性が失われるため、好ましくありません。そして、今後も仕様側で属性が追加される度に引数を増やしていくのも現実的とは言えません。

ではその解決策をどうすれば良いのかと考えてみると、D3Eのイベントコンストラクタのように、Javascriptから任意のオブジェクトを受け取って、そこに保存されてる任意の情報からイベントを初期化していく、ということが考えられます。しかし、この方法は、XPCOM的には少し面倒です。そこで、直接的に、KeyboardEventそのものを引数として受け取れば良いという結論に至りました。これなら、単純に、nsIDOMKeyEventとしてオブジェクトを受け取れるためです。

幸い、Bug-org 930893の修正により、KeyboardEventは、Javascriptから、簡単にインスタンスを生成することができます。ですので、この方法がAPIの提供側、ユーザ側双方にとってベストな方法と言えます。

ですが、ここで一度、互換性を捨てて新しいAPIを用意するのであれば、他の懸念事項も解決しておこうと考えました。

一つ目の懸念事項は、keypressイベントの扱いです。このイベントは、D3Eでもレガシーイベントのひとつと定義され、具体的な仕様を定めず、各ブラウザがこれまでのバージョンとの互換を維持続けられるように配慮されています。ですが、nsIDOMWindowUtils.sendKeyEvent()は、「DOMイベントを生成すること」が目的のメソッドです。つまり、APIのユーザは、どのようなあり得ない順序・タイミングでのDOMイベントの生成も可能です。しかし、これはもちろん、Webアプリ作者の方にとってはたまったものではありません。

そこで視点を変えてみました。Geckoがネイティブイベントをハンドリングしている時、何をハンドリングしているのか、それは、キーが押された、もしくは離された、この二点のタイミングを元に、適切にDOMイベントを生成しているのです。つまり、アドオンやFirefox OSからも、この二点だけを通知してもらい、Gecko側で必要なイベントを適切に生成すれば、Webアプリから見た場合に、差異は無くなります。これは、自然な自動テストを書く上でも大切なことと言えます。

このことから、keydown()keyup()の二つ目のメソッドが存在すれば、良いことが分かります。

そして二つ目の懸念事項は、KeyboardEventが、どのタイミングでも生成できてしまう、という点です。

具体的には、GeckoはIMEの未確定文字列が存在する際には、KeyboardEventを一切生成しませんが、nsIDOMWindowUtils.sendKeyEvent()では、生成可能です。

Geckoの現在の動作は、最新のD3Eでも好ましい動作とされています。しかし、他のブラウザは、keydownkeyupは未確定文字列が存在していても生成していて、この差によって、期待通りに機能しないサイトというのが実際に報告されています。ですので、Gecko側が、今後、仕様よりも他のブラウザの動作にあわせる可能性もゼロではないのです。少し話が逸れましたが、要するに、Gecko側の動作にあわせて、アドオンやFirefox OS側も修正が必要になるような設計というのは現実的ではありません。キーが押された、離されたという事実を全てAPIで通知してもらい、必要な時だけGeckoがDOMイベントを生成するという形の方がより良い設計であることは誰の目にも明らかでしょう。

これを解決するためには、IMEの状態を知る必要があります。であれば、nsITextInputProcessorに、keydown()keyup()を追加してしまうのが理にかなっています。

大枠のアイデアはこれで決まりました。しかし、実際に実装しようとすると、いくつかの問題が出てきました。

まず、現在のD3E仕様では、AltMetaControlShift以外のモディファイアキーの状態を、KeyboardEventを生成する際に指定できません。また、モディファイアキーの状態を直接指定できてるのは不自然です。例えば、連続する文字入力のキーが、別々のモディファイアキーの状態である可能性があります。これは、モディファイアキーのイベントを監視しているWebアプリがあると、そういったアプリ上では期待通りに処理されない可能性があります。

こういった問題を解決するために、nsITextInputProcessor.keydown()nsITextInputProcessor.keyup()は、引数のKeyboardEventのモディファイアキーの状態を無視するようにしました。その代わりに、モディファイアキーのイベントを通知される度に、インスタンス内でその状態を記録し、KeyboardEventの初期化時には、その保持している情報のみを利用するようにしました。これにより、実際のキーボードと同じ手順を踏まなくては、モディファイアキーの状態を反映したKeyboardEventが生成できないようになっています。

もうひとつ別の問題点があります。それは、nsITextInputProcessor.keydown()は、自動的にkeypressイベントを生成するということです。これは例えば、Aキーが、未確定文字列を生成する際に、

TIP.keydown(new KeyboardEvent({ key: "a", code: "KeyA", KeyboardEvent.DOM_VK_A }));
TIP.setPendingComposition("あ");
TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE);
TIP.flushPendingComposition();

というコードを書くと、keydownkeypresscompositionstartという順でイベントが発生します。こうなると、<input>要素等は、"aあ"という入力結果になってしまいます。

また、未確定文字列が変更される度に、keydown()keyup()を呼び出すのも手間ですし、本来不要なvirtual callが多数発生するため、あまり好ましくありません。

そこで、既存のnsITextInputProcessor.startComposition()nsITextInputProcessor.flushPendingComposition()nsITextInputProcessor.commitComposition()nsITextInputProcessor.cancelComposition()それぞれに、オプションの引数として、KeyboardEventを渡せるようにしました。つまり、各、未確定文字列への変更の原因となったキーの情報を提供してもらえれれば、後はうまくやりますよ、ということです。

上記の例は、

TIP.setPendingComposition("あ");
TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE);
TIP.flushPendingComposition(new KeyboardEvent({ key: "a", code: "KeyA", KeyboardEvent.DOM_VK_A }));

という形で書けます。この結果、keydowncompositionstartの順でイベントが発生し、keypressイベントは発生しませんので、<input>要素等の内容は期待通りに、"あ"のみとなります。

このように、キーに起きた「事実」を全て通知してもらいさえすれば、OSからネイティブキーのイベントを受け取ってDOMイベントを生成したときと全く同じ条件で、アドオンやFirefox OSのキーイベントも生成されるようになりました。残る問題は、既存のアドオンやFirefox OSのキーボードの対応の問題ですが、このあたりは私にはどうしようもありません。残念ながら、生成するアドオンやキーボードに依存して、D3Eに対応したイベントが生成されるか否かが決定するという形になります。正直なところ、これは好ましい話とは思っていません。しかし、このような、仮想も含めたデバイスにWebアプリが依存する状況がまたひとつ産まれたというのは、Webアプリがネイティブアプリにまた一歩近づいたということでもあり、本当は好ましいことなのかもしれません。

ちなみに、Firefox OSでは、もう一層、CompositionManagerというAPI層があり、ここの仕様を修正するまでは、各キーボードやIMEがD3Eに対応することはできません。ここは、台湾のチームにがんばってもらおうかと勝手に思ってます。

なお、このAPIのより詳しい情報は、既にMDNで公開しています