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

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

もずはっく日記(2011年10月)

2011年10月7日

Bug-org 477291 Should not send keypress event before calling interpretKeyEvents
初回投稿日時: 2011年10月07日13時13分42秒
最終更新日時: 2011年10月07日16時02分24秒
カテゴリ: Mozilla Core Mozilla10 バグ修正
SNS: (list)

Mac版のGeckoは[NSResponder interpretKeyEvents:]を呼ぶ前にkeypressイベントを発行すべきではない、というバグです。先のバグ修正の解説にもあったように、アプリケーションに対してまずは[NSResponder keyDown:]にキーイベントが渡され、これを[NSResponder interpretKeyEvents:]経由でIMEに処理を渡す、というのがCocoaのキーイベントの基本的な処理です。逆に言うなら、[NSResponder interpretKeyEvents:]を呼び出してみなければ、そのキーイベントがIMEによって利用されるのか否かが分かりません。ですので、その使途不明なキーイベントを先にkeypressイベントを発行することでWebアプリケーションや、Geckoのエディタ等がそのキーイベントを処理してしまうと、IMEに渡るべきキーイベントが渡らなくなったり、アプリとIMEで二重に処理されてしまうことになります。Geckoは何故か文字入力を行わないキーと、Controlキーが押されている場合にはkeypressイベントを先に発行する、ということを意図的に行っていました。

これが一番問題になっていたのが、ATOKの確定アンドゥです。ATOKの確定アンドゥはControl+Deleteに割り当てられていますが、これが入力される以下のように処理が行われます。

  1. Control+DeleteのキーイベントでGeckoの[NSResponder keyDown:]が呼び出される。
  2. Geckoがkeydownイベントを発行する。
  3. Geckoがkeypressイベントを発行する。(バグ)
  4. Geckoが[NSResponder interpretKeyEvents:]を呼び出して、ATOKにキーイベントを渡す。(たぶん)ATOKは次のキーイベントを登録し、一度Geckoに処理を戻す。
  5. ATOKが登録した、確定済みの文字を消すためのDeleteキーイベントが[NSResponder keyDown:]に渡される。
  6. Geckoがkeydownイベントを発行する。
  7. Geckoがkeypressイベントを発行する。(バグ)
  8. Geckoのエディタがkeypressイベントをハンドリングし、文字を削除する。
  9. Geckoが[NSResponder interpretKeyEvents:]を呼び出してATOKに処理を渡す。
  10. ATOK(もしくはシステム?)がdeleteBackward:コマンドを発行する。
  11. Geckoは[NSResponder doCommandBySelector:]で、deleteBackward:コマンドをシステムに渡さず消費する。(別バグ)
  12. #5から#11が取り消される文字数分繰り返される。
  13. ATOKが登録した、もう一つのダミーのDeleteキーイベントで[NSResponder keyDown:]が呼び出される。
  14. Geckoがkeydownイベントを発行する。(好ましくないものの、どうしようもないバグ)
  15. Geckoがkeypressイベントを発行する。(バグ)
  16. Geckoのエディタがkeypressイベントをハンドリングし、文字を削除する。(つまり一文字余分に消える)
  17. Geckoが[NSResponder interpretKeyEvents:]を呼び出してATOKに処理を渡す。
  18. ATOKはdeleteBackward:コマンドを発行せず、代わりに取り消された文字列で未確定文字列を挿入してくる。

このように、一文字、余分に確定済みの文字が消された状態で再度変換が始まるという形になってしまっていました。

今回のバグでは[NSResponder interpretKeyEvents:]の呼び出し後にkeypressイベントを発行するように修正を行ったので次のような挙動に変わっています。

  1. Control+DeleteのキーイベントでGeckoの[NSResponder keyDown:]が呼び出される。
  2. Geckoがkeydownイベントを発行する。
  3. Geckoが[NSResponder interpretKeyEvents:]を呼び出して、ATOKにキーイベントを渡す。(たぶん)ATOKは次のキーイベントを登録し、一度Geckoに処理を戻す。
  4. Geckoがkeypressイベントを発行する。(タイミング修正)
  5. ATOKが登録した、確定済みの文字を消すためのDeleteキーイベントが[NSResponder keyDown:]に渡される。
  6. Geckoがkeydownイベントを発行する。
  7. Geckoが[NSResponder interpretKeyEvents:]を呼び出してATOKに処理を渡す。
  8. ATOK(もしくはシステム?)がdeleteBackward:コマンドを発行する。
  9. Geckoがkeypressイベントを発行する。(ただし、コマンドの内容は意識していないバグは残る)
  10. Geckoのエディタがkeypressイベントをハンドリングし、文字を削除する。
  11. #5から#10が取り消される文字数分繰り返される。
  12. ATOKが登録した、もう一つのダミーのDeleteキーイベントで[NSResponder keyDown:]が呼び出される。
  13. Geckoがkeydownイベントを発行する。(好ましくないものの、どうしようもないバグ)
  14. Geckoが[NSResponder interpretKeyEvents:]を呼び出してATOKに処理を渡す。
  15. ATOKはdeleteBackward:コマンドを発行せず、代わりに取り消された文字列で未確定文字列を挿入してくる。
  16. [NSResponder interpretKeyEvents:]からGeckoに戻ってくる。
  17. GeckoはIMEの未確定文字列があるので、keypressイベントの発行を行わない。(D3E仕様通り)

と、このように、見た目ではATOKの確定アンドゥのバグが修正された状態に改善しています。ただし、処理リストを見ると分かるように、たまたま動くようになったという状況です。

そして、大きな問題があります。何故、今までGeckoのコードがこのように不可解な処理順序でkeypressイベントを発行していたのか?という点です。

CVSの履歴を追いかけていくと、このように修正されたのは、ログで名前を見るまで存在すら忘れていたChimera用の開発ブランチからのマージで、詳細は全く分かりません。

ですが、本来のCocoaのキーイベント処理から察するに、[NSResponder interpretKeyEvents:]を呼び出した際に、そのキーイベントがOS設定のキーバインディングのどれにもヒットしなかった場合に(Webアプリケーションが利用するかもしれないキーイベントで)ビープ音が(先に)鳴ることが問題だったのではないかと思われます。しかし、現在、GeckoではWebKitと同様にCocoaの隠しAPI(_wantsKeyDownForEvent:)を利用したからか、不要な(もしくは必要なものも含めて)ビープ音が鳴らなくなっています。

この推測が当たっているのか、特に目立つ問題は今のところ見つかっていませんが、非常にリスキーな変更だったので、mozilla 10の初期であるこの時期に修正を入れました。

もし、修正困難なregressionが出た場合には早急にバックアウトする必要がありますので、是非、色々なキー入力をテスタの方にはテストしてもらいたいです。また、些細な変化であっても、昨日のNightlyからキーイベントの挙動が変わっている場合には報告をよろしくお願いします。

modestにこの修正に関わるテストに関する記事をポストしましたので、あわせて参照してください。

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

bug-org 477291を含むエントリ