2014年10月30日
現在、compositionupdate
の内部イベントである、NS_COMPOSITION_UPDATE
イベントは、各OS用のwidget
から発火していますが、同じロジックで発火している上、XPレベルでは、TextComposition
クラスで管理している未確定文字列の情報をwidget
側と二重管理するのは勿体ないということで、NS_COMPOSITION_UPDATE
を、TextComposition
で発火するようにしよう、というバグです。
この修正により、widget
が発火する内部イベントは、NS_COMPOSITION_START
、NS_COMPOSITION_END
、NS_TEXT_TEXT
イベントの三種類のみになりました。TextComposition
イベントは、NS_TEXT_TEXT
イベントが発火されると、PresShell
から、IMEStateManager::DispatchCompositionEvent()
経由で、DOMツリーにイベントを発火前に受け取り、この際に、未確定文字列に変化がある場合にのみ、NS_COMPOSITION_UPDATE
イベントを発火するようになりました。
これにより、一部のwidget
では未確定文字列を保存しておかなくてよくなりました。メモリ容量は未確定文字列の長さからして、問題にはなりませんが、メモリのフラグメンテーションについては多少、懸念が無くなりました。また、NS_COMPOSITION_UPDATE
の発火後の各種オブジェクトの生存確認をXPレベルで確実に行えるので、安全性が高まっています。そして、XP化により、全てのプラットフォームで確実に同じ条件で発火するようになりました。今までは、Android版のみ、未確定文字列に変化がなくても、compositionupdate
イベントが発火されていたように思います(実際には未確認)。
最後に、e10sモードでは、compositionupdate
を発火するにあたり、widget
から発火して、子プロセスがフォーカスを持つ場合には、プロセス間通信が必要でしたが、これが不要になったため、未確定文字列操作時のパフォーマンスがかなり改善しています。
nsIDOMWindowUtils.sendCompositionEvent()
に、compositionupdate
を渡しても、このメソッドは何もしないように変更されています(互換性のため、例外も発生しません)。nsICompositionStringSynthesizer.dispatchEvent()
を呼び出すと、未確定文字列の変更が発生した場合にのみ、自動的にcompositionupdate
イベントも発火されます。
compositionupdate
は、未確定文字列が変化する前に発火するイベントということに、一応、なっていますので、これは、エディタの未確定文字列を書き換えるためには現在使っていません(というか、内部処理では使っていません)。内部処理では、WidgetTextEvent
を、NS_TEXT_TEXT
イベントとして発火し、text
という独自DOMイベントとして発火したものを利用しています。
しかし、この、独自のイベント型であるWidgetTextEvent
が存在し続けると、各種引数は、共通のスーパークラスである、WidgetGUIEvent
を使わなくてはいけなかったり、WidgetEvent::mClass
メンバの値を何度もチェックする必要があり、コードがスッキリとしない原因になっていました。
このバグの修正により、NS_TEXT_TEXT
イベントは、NS_COMPOSITION_CHANGE
イベントに変更され、WidgetTextEvent
クラスは単純に廃止、代わりに、WidgetCompositionEvent
に、WidgetTextEvent
のみが持っていた未確定文字列の文節情報を持たせています。独自DOMイベントもUIEvent
から、CompositionEvent
に変化していますので、若干の非互換が発生していますが、これが問題になることは実際のケースでは存在しないと思います(CompositionEvent.data
で未確定文字列にはアクセス可能に変更になっています)。
これにより、未確定文字列を取り扱うメソッドで、引数にWidgetGUIEvent
をとっていたメソッドは、(見つけられた限り)全て、WidgetCompositionEvent
に修正し、メソッドの利用方法をより明確にしています。
自動テストを記述する際には、EventUtils.synthesizeText()
が、EventUtils.synthesizeCompositionChange()
に名前が変更されているので注意してください。
クラッシュのスタックからは何が起きているのかはっきりとしませんでしたが、クラスへのポインタがnullptr
の場合に、そのメソッドを呼び出して、そのままクラッシュせずに妙なことになっていました。Bug-org 1065835の修正によるregressionです。
単純にnullチェックを挟むことで対応しています。元々はその必要が無いはずの場所でしたが、特殊な条件下ではnullになることが判明した訳ですので、単なる見落としとも言えますね。
TSFモードで、IMMのIME (ATOK 2010以前やJapanist、Windows 7かVistaでGoogle日本語入力)を利用していると、候補ウインドウがキャレット位置に正しく表示されず、ウインドウの左上に表示されたり、全く表示されなかったりする、というバグです。Bug-org 975383の修正によるregressionです。
IMMのIMEは、TSFモードであっても、ITextStoreACP
を利用せず、IMMのメッセージとAPIを利用して、従来通りにハンドリングしなくてはいけません。ですので、nsIWidget::NotifyIME(NOTIFY_IME_OF_COMPOSITION_UPDATE)
が呼び出された場合、ImmSetCandidateWindow()
APIでウインドウ位置をTSFモードであっても指定しておく必要があります。しかし、これまではTSFモードでは無い場合、NOTIFY_IME_OF_COMPOSITION_UPDATE
を無視していました。
では、なぜ、今までは問題なかったのか、ということになるのですが、Bug-org 975383の修正で、NS_COMPOSITION_UPDATE
の発火コードをnsIMM32Handler
から削除した際、誤ってImmSetCandidateWindow()
を呼び出していたコードを正しく削除してしまったのが、このバグが表面化した理由でした。つまり、元々バグっていたのですが、バグにより、過剰な呼び出しがあったので、それに救われていたものの、その過剰な呼び出しが削除されたということです。
今回、IMMとTSFのどちらに処理を任せるか判断しているIMEHandler
クラスの処理を見直して、TSFモードでIMM IMEが有効かどうかをきちんと確認するようにしました。これにより、TSFモードでIMM IMEを利用している場合にのみ、未確定文字列があってもKeyboardEvent
が発生するバグがありましたが、このバグも修正されています。
designMode
のエディタで、未確定文字列を入力中に、<body>
要素の外側、つまり、<html>
要素の部分をクリックすると、GTK版でIMEがiBusの場合、未確定文字列が確定されず、キャンセルされてしまう、というバグです。Bug-org 1065835の修正によるregressionです。
Bug-org 1065835の修正では、未確定文字列の確定のリクエストがあった場合、IMEの挙動に関わらず、直前の未確定文字列をそのまま確定するように修正しました。これに伴い、もし、XPレベルで確定のリクエストを出さなかった場合、各widget
がIMEに確定のリクエストを出した場合は、そのIMEの挙動に依存するようになっています。最近のiBusに関しては、gtk_im_context_reset()
を呼び出した場合、非同期で未確定文字列をキャンセルするという挙動になっていますので、このようなバグが発生しました。
具体的に、どこでキャンセル処理が発生していたのかは確認していませんが、<html>
要素をクリックした場合、<body>
要素内でクリックしたのとは違って、XPレベルでの確定リクエストが発生していないことに問題があります。そこで、nsHTMLEditorEventListener::MouseDown()
を調査してみると、contenteditable
で生成されたHTMLエディタのために、ハンドリングすべきmousedown
イベントかどうかを、イベントのターゲットが、エディタのルート要素かその子孫であるかどうかを確認していました。ここで問題なのが、エディタのルート要素の定義です。nsHTMLEditor
では、ルート要素は、contenteditable
属性で生成されている場合は、その、editing hostになりますが、designMode
を"on"
に設定して生成された場合は、<body>
要素になります。このため、<html>
要素をクリックした場合は、エディタ外でのmousedown
イベントであると判定される結果になってしまっていました。
今回の修正では、Auroraでの修正も必要だったので、ルート要素の定義は変更せず、単純に、mousedown
イベントを処理すべきかどうかの判断を、nsEditor::IsAcceptableInputEvent()
で確認するようにしました。このメソッドは、もともと、この用途での利用を想定したものなので、期待通りに判断できますし、実績もあります。
これにより、designMode
で生成されたHTMLエディタでは、どこでmousedown
イベントが発生しても、強制確定のリクエストがXPレベルで発生するようになりました。
未確定文字列があるエディタ以外をクリックして、フォーカス移動により、未確定文字列を強制確定させた場合、Linux GTK版で、iBusを利用していると、次にどのエディタでもIMEの未確定文字列が入力されず、それを一度確定なりキャンセルすると、通常の状態に戻る、というバグです。Bug-org 1065835の修正によるregressionです。
例によってiBusの非同期での未確定文字列のキャンセル処理がバグの原因になっています。
Bug-org 1065835の修正で、非同期で確定、もしくはキャンセルが行われるIMEを利用していた場合、TextComposition
は自動的に、確定、もしくはキャンセルをDOMイベントを生成して、エミュレーションするようになりました。この際に、TextComposition
のインスタンスは、まだ、破棄されないように修正されました。待機して、その後に非同期で発生するはずのIMEから来るWidgetCompositionEvent
を無視し、DOMツリーでは発火しないようにしなくてはいけないからです。
フォーカス移動で強制確定が行われた場合かつ、IMEが有効なエディタが続けてフォーカスを得ていない場合、同期でnsIWidget::SetInputContext()
が呼び出され、nsGtkIMModule::GetContext()
の戻り値が、パスワードフィールド用のGtkIMContext
か、IMEを利用しないための、ダミーコンテキストに変わってしまっていることがあります。その後、IMEが非同期で確定、もしくはキャンセルのためのシグナルをnsGtkIMModule
に送信しても、その時点でのGetContext()
の戻り値とは異なるコンテキストであるという理由で無視してしまっていました。これにより、TextComposition
が期待する、NS_COMPOSITION_END
イベントが発火されず、これを待ち続ける状態が続き、新しい未確定文字列が表示されなくなっていました。そして、それの確定、もしくはキャンセル時に発生するNS_COMPOSITION_END
イベントを受け取ることで、ようやく破棄されて、新しい未確定文字列を処理可能な状態に戻っていた、という訳です。
この修正では、シグナルを受信した場合に渡されたGtkIMContext
が、Gecko自身が生成して管理しているもののうちのどれかと同じであるかを確認する形に変更しました。これにより、フォーカス移動でnsGtkIMContext::GetContext()
が異なる値を返してきてもシグナルを処理するようにしています。また、シグナルを処理するメソッドの一部は、GtkIMContext
を引数で受け取るようにし、GetContext()
を利用しないようにしています。
Auroraでも修正しないといけないので、最低限の変更にしてありますが、追々、ほとんどのシグナル処理のメソッドが、GtkIMContext
を引数で受け取るように修正する予定です。
GTKのIMEでは、南アジアの言語でキャレット周囲の文字を含めて、未確定文字列を生成する場合や、日本語の再変換の実装のために、キャレット前後の任意の文字を削除するシグナルが存在しています。これを処理する場合、アプリ側は未確定文字列を含まない、元の文字列でその削除される範囲を決めなくてはいけません。また、nsEditor
は、未確定文字列がある状態での、未確定文字列外の文字の編集をサポートしていません。このため、文字列を削除するシグナルを、未確定文字列が存在する際に受け取った場合、まずは、確定してから、文字を削除し、再度、未確定文字列を復元する必要があります。しかし、その実装であるnsGtkIMModule::DeleteText()
では、未確定文字列の文節状態を含んだまま、NS_COMPOSITION_END
を発火し、復元時には逆に、文節状態を含まないまま、NS_COMPOSITION_CHANGE
イベントを発火していました。これは、NS_COMPOSITION_CHANGE
イベントを発火するメソッドが、bool
の引数で文節情報を含めるかどうかを決めるという設計だったため、呼び出し側のDeleteText()
が単純に、true
とfalse
を指定しなくてはいけないところ、それぞれ、逆を指定してしまっていました。
ちなみに、これは、nsGtkIMModule
のリファクタリングの作業中にコード上で発見したバグで、実際にこれが問題になっている環境は無いのかもしれません。かなり前から存在しているバグで、発生したら目立つにも関わらず、バグ報告が今まで無いので。