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に割り当てられていますが、これが入力される以下のように処理が行われます。
- Control+DeleteのキーイベントでGeckoの
[NSResponder keyDown:]
が呼び出される。
- Geckoが
keydown
イベントを発行する。
- Geckoが
keypress
イベントを発行する。(バグ)
- Geckoが
[NSResponder interpretKeyEvents:]
を呼び出して、ATOKにキーイベントを渡す。(たぶん)ATOKは次のキーイベントを登録し、一度Geckoに処理を戻す。
- ATOKが登録した、確定済みの文字を消すためのDeleteキーイベントが
[NSResponder keyDown:]
に渡される。
- Geckoが
keydown
イベントを発行する。
- Geckoが
keypress
イベントを発行する。(バグ)
- Geckoのエディタが
keypress
イベントをハンドリングし、文字を削除する。
- Geckoが
[NSResponder interpretKeyEvents:]
を呼び出してATOKに処理を渡す。
- ATOK(もしくはシステム?)が
deleteBackward:
コマンドを発行する。
- Geckoは
[NSResponder doCommandBySelector:]
で、deleteBackward:
コマンドをシステムに渡さず消費する。(別バグ)
- #5から#11が取り消される文字数分繰り返される。
- ATOKが登録した、もう一つのダミーのDeleteキーイベントで
[NSResponder keyDown:]
が呼び出される。
- Geckoが
keydown
イベントを発行する。(好ましくないものの、どうしようもないバグ)
- Geckoが
keypress
イベントを発行する。(バグ)
- Geckoのエディタが
keypress
イベントをハンドリングし、文字を削除する。(つまり一文字余分に消える)
- Geckoが
[NSResponder interpretKeyEvents:]
を呼び出してATOKに処理を渡す。
- ATOKは
deleteBackward:
コマンドを発行せず、代わりに取り消された文字列で未確定文字列を挿入してくる。
このように、一文字、余分に確定済みの文字が消された状態で再度変換が始まるという形になってしまっていました。
今回のバグでは[NSResponder interpretKeyEvents:]
の呼び出し後にkeypress
イベントを発行するように修正を行ったので次のような挙動に変わっています。
- Control+DeleteのキーイベントでGeckoの
[NSResponder keyDown:]
が呼び出される。
- Geckoが
keydown
イベントを発行する。
- Geckoが
[NSResponder interpretKeyEvents:]
を呼び出して、ATOKにキーイベントを渡す。(たぶん)ATOKは次のキーイベントを登録し、一度Geckoに処理を戻す。
- Geckoが
keypress
イベントを発行する。(タイミング修正)
- ATOKが登録した、確定済みの文字を消すためのDeleteキーイベントが
[NSResponder keyDown:]
に渡される。
- Geckoが
keydown
イベントを発行する。
- Geckoが
[NSResponder interpretKeyEvents:]
を呼び出してATOKに処理を渡す。
- ATOK(もしくはシステム?)が
deleteBackward:
コマンドを発行する。
- Geckoが
keypress
イベントを発行する。(ただし、コマンドの内容は意識していないバグは残る)
- Geckoのエディタが
keypress
イベントをハンドリングし、文字を削除する。
- #5から#10が取り消される文字数分繰り返される。
- ATOKが登録した、もう一つのダミーのDeleteキーイベントで
[NSResponder keyDown:]
が呼び出される。
- Geckoが
keydown
イベントを発行する。(好ましくないものの、どうしようもないバグ)
- Geckoが
[NSResponder interpretKeyEvents:]
を呼び出してATOKに処理を渡す。
- ATOKは
deleteBackward:
コマンドを発行せず、代わりに取り消された文字列で未確定文字列を挿入してくる。
[NSResponder interpretKeyEvents:]
からGeckoに戻ってくる。
- GeckoはIMEの未確定文字列があるので、
keypress
イベントの発行を行わない。(D3E仕様通り)
と、このように、見た目ではATOKの確定アンドゥのバグが修正された状態に改善しています。ただし、処理リストを見ると分かるように、たまたま動くようになったという状況です。
そして、大きな問題があります。何故、今までGeckoのコードがこのように不可解な処理順序でkeypress
イベントを発行していたのか?という点です。
CVSの履歴を追いかけていくと、このように修正されたのは、ログで名前を見るまで存在すら忘れていたChimera用の開発ブランチからのマージで、詳細は全く分かりません。
ですが、本来のCocoaのキーイベント処理から察するに、[NSResponder interpretKeyEvents:]
を呼び出した際に、そのキーイベントがOS設定のキーバインディングのどれにもヒットしなかった場合に(Webアプリケーションが利用するかもしれないキーイベントで)ビープ音が(先に)鳴ることが問題だったのではないかと思われます。しかし、現在、GeckoではWebKitと同様にCocoaの隠しAPI(_wantsKeyDownForEvent:
)を利用したからか、不要な(もしくは必要なものも含めて)ビープ音が鳴らなくなっています。
この推測が当たっているのか、特に目立つ問題は今のところ見つかっていませんが、非常にリスキーな変更だったので、mozilla 10の初期であるこの時期に修正を入れました。
もし、修正困難なregressionが出た場合には早急にバックアウトする必要がありますので、是非、色々なキー入力をテスタの方にはテストしてもらいたいです。また、些細な変化であっても、昨日のNightlyからキーイベントの挙動が変わっている場合には報告をよろしくお願いします。
modestにこの修正に関わるテストに関する記事をポストしましたので、あわせて参照してください。