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

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

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

2011年10月6日

Bug-org 685073 Manage nested key events for IME 初回投稿日時: 2011年10月06日18時45分10秒
カテゴリ: Mozilla Core Mozilla10 バグ修正
固定リンク: id=2011100600
SNS: (list)

Cocoaのキーイベントは、[NSResponder keyDown:]が呼ばれて、その時に[super interpretKeyEvents:]を呼び出すとIMEにキーイベントを引き渡すことができますが、原理上、interpretsKeyEventsを呼び出し中にIMEはkeyDownを再度呼び出すことが可能です。そのような挙動を実際に確認していませんが、もし発生するとGecko内部の処理が破綻するので処理中のキーイベントを配列で管理するように修正しています。

なお、もしキーイベントがこのように入れ子になった場合、元のキーイベントに対するkeypressイベントは既にpreventDefault()が呼び出された状態で送信されます。ですので、defaultPreventedをきちんと確認しているWebアプリケーションではそういったケースでも適切に処理することができます。

実際にキーイベントがどのように発生するかというと、Aキーを押した時に、Bキーを押したイベントがIMEによって擬似的に生成された場合、

  1. Aキーのkeydownイベント
  2. Bキーのkeydownイベント
  3. Bキーのkeypressイベント
  4. Aキーのkeypressイベント (defaultPreventedtrueを返す)
  5. Aキーのkeyupイベント

となります。Bキーのkeyupイベントが来るかどうかはIMEの実装次第ですが、期待すべきではありません(基本的には物理キーであっても、keyupイベントが発行されるかは期待できない)。

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 バグ修正
固定リンク: id=2011100700
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にこの修正に関わるテストに関する記事をポストしましたので、あわせて参照してください。

2011年10月12日

Bug-org 680320 After upgrading to Thunderbird 6, Japanese signature text attached from a file turned into garbage characters 初回投稿日時: 2011年10月12日17時38分48秒
最終更新日時: 2011年10月12日18時21分14秒
カテゴリ: Thunderbird バグ却下
固定リンク: id=2011101200
SNS: (list)

Mac版のThunderbirdで署名のファイルをShift_JISで保存している場合、メール作成時に署名が文字化けする、というバグです。

mozilla-centralでは、レガシーなAPIの整理が逐一行われていますが、修正されたAPIのうちのひとつで、comm-centralのTbでのみ他では使っていないパラメータを使っていたため、mozilla-centralでの修正時に気付かなかったというのが原因です。追記: API自体の修正ではなく、API内部の処理の単純化が原因でした。

Macは唯一、ユーザが日常的に使うエンコーディングとシステムのデフォルトエンコーディング(ファイルパスやクリップボード等々)が食い違うプラットフォームです。この前者のエンコーディングを取得する方法が結果的には無くなってしまいました。

修正するには同様のAPIを追加するか、ローカライズ時に各言語版ごとにひとつ追加のエンコーディングを指定できるようにするか、といった、そこそこ大きな修正が必要です。ですが既にTbには署名のエディタが付いていますし、Tb6、Tb7と2バージョンにわたって発生している問題なので、問題の出た全てのユーザは既にエディタで作り直したのではないかと思われます。そういった理由からこのバグの修正自体をあきらめることになりました。

なお、システムデフォルトのエンコーディングであるUTF-8で作成された署名ファイルは引き続き利用可能です。

2011年10月31日

Bug-org 694913 crash nsIMM32Handler::OnMouseEvent 初回投稿日時: 2011年10月31日22時25分46秒
カテゴリ: Mozilla Core Mozilla10 バグ修正
固定リンク: id=2011103100
SNS: (list)

実際に発生する要因が全く分からないクラッシュバグです。

クラッシュの直接の原因は、クリックされた場所にある未確定文字列中の文字の幅がゼロだった場合にゼロ除算エラーが出る、という単純かつ安全なものだったので修正は行いました。ですが、それなりにクラッシュした人が居るようなので、狙えば再現しやすいパターンがありそうなものなのですが、未確定文字列中に幅ゼロの文字が(特に日本語のIMEで)発生し、なおかつそこにヒットする位置をクリックしてしまう、という状況がよく分かりません。中国語のIMEでもクラッシュした痕跡がいくつかありましたが、こちらはおそらく未確定文字列をGeckoが自前で描画していないのに、無理矢理クエリに行っておかしな値が返ってきてるのかな、という気もしますが……