MS-IMEを使って、変換文字列がすでにある状態で、新しい文字を入力すると、変換済みの文字列が確定され、新しい未確定文字列として文字が入力されるのですが、TSFモードでは、この最初に入力した文字ごと確定され、未確定文字列にも含まれるため、文字がダブってしまう、というバグです。
このバグの原因は、Bug-org 1050703の修正によるregressionでした。Bug-org 1050703では、キャレット位置が変化しない場合、compositionstart
イベントを送信する前に、選択範囲の調整を行わないように修正しましたが、この際に、誤って、未確定文字列の一部のみが確定される、今回のようなケースの際には、常に、選択範囲の調整を行っていないのが原因でした。
ひとまず、regressionは、即、修正しましたが、そのままでは、「あい」と入力し、変換後、「う」と入力すると、「あいう」と確定され、「う」が選択された状態で、未確定文字列の編集が始まり、未確定の「う」で上書きする、という動作になっていました。
これでは、「う」を確定後にUndoすると、「あいう」から「あいう」になり、次のUndoで全てがキャンセルされます。これは明らかに、ユーザの感覚とは異なるものですので、未確定文字列の一部が確定される際には、一度、未確定のままキープされる文節の文字列を削除した上で確定し、その後、未確定文字列を復元するように修正しています。これにより、「あいう」→「あい」→「」というトランザクションが記録されるようになっています。
TSFでは、TIPは、マウスのイベントを受け取りたい場合、ITextStore
に、マウスイベントのリスナを登録する必要があります。このバグはそれに、対応しようというバグです。
まず、イベントリスナのインターフェースである、ITfMouseSink
を受け入れるための、ITfMouseTrackerACP
インターフェースを単純に、nsTextStore
に実装しました。
ただし、実際の登録・削除処理は若干、面倒なことに、任意の個数のITfMouseSink
を同時にインストール可能です。これは、MSDNでは名言されていませんが、ITfMouseTrackerACP::AdviseSink()
で登録時にDWORD
値のCookieを返し、ITfMouseTrackerACP::UnadviseSink()
を呼び出す時には、そのCookie値のみで削除するITfMouseSink
を指定しようとしている形になっていることから、DWORDで表現できる個数程度は登録可能である必要があるということが伺えます。現に、他のオープンソースソフトウェアのソースコードを調査しましたが、やはり、複数のITfMouseSink
を同時にインストールできるようになっていました。
nsTextStore::MouseTacker
クラスを新設し、これをnsTArray
で配列として管理し、各インスタンスには、Cookie値、ITfMouseSink
の実装へのポインタ、マウスイベントを受け取りたい、文字列の範囲を保存しています。現在、Cookie値は配列のインデックスと同値ですが、将来的に、フットプリントが問題になるようなら、配列から削除しても処理が破綻しないように、二重管理する形にしています。
次に、マウスのボタンが押されたときと離された時に、その座標に文字があるかどうか調査し、無ければ無視、あれば、その文字のどの位置でイベントが発生したのかをITfMouseSink::OnMouseEvent()
に通知しなくてはいけません。
これまでの実装(IMM等)では、widgetから、NS_QUERY_CONTENT_CHARACTER_AT_POINT
イベントを送信し、カーソル位置の情報を取得していましたが、このままでは、現在開発が進んでいる、e10s(Firefoxのマルチプロセス化)には対応できません。なぜなら、ネイティブイベントは、chromeを管理する、親プロセスが受け取りますが、contentは子プロセスにあります。子プロセスの情報を取得するには、同期通信で情報を取得する必要がありますが、子プロセスが処理落ちやハングアップしている時に、chromeプロセスが巻き添えを食わないように、これは禁止されています。
そこで、全く逆のアプローチをとることにしました。content側には現在、フォーカスを持ったエディタの状態を監視し、nsIWidget::NotifyIME()
で様々なことを通知している、IMEContentObserver
が居ます。エディタ上の文字でマウスのボタンが押されたとき、もしくは、話された時に、ここから、新しい通知を同期通信で出せば、widget側でIMEにマウスイベントを通知できますし、IMEContentObserver
は、そのイベントがIMEによって消費されたかどうかを確認することもできます。
どのような通知が行われているのかは、nsIWidget::IMENotification::mMouseButtonEventData
を参照してください。
このアプローチにより、各プラットフォームのIMEイベントハンドラごとに、似たような処理を書かなくて済むようになった上に、e10sにも対応可能なので、かなり、成功した修正になったと思っています。