e10sを有効にしていると、contentプロセス内のエディタにフォーカスを移し、その後、chromeプロセス内のエディタにフォーカスを移動させてもcontentプロセス内のエディタに未確定文字列が表示されるというバグです。
このバグは目には見えないだけで、Windows固有のもので、TSFモードだけではなく、IMMモードでも問題がありました。
問題の根本的な原因は、IMEStateManager
がマルチプロセスに全く対応させられておらず、TabParent
で疑似的にWidgetCompositionEvent
等、IMEのハンドリングに必要なイベントをアクティブなタブへ送信していたことにあります。そこで、このバグではIMEStateManager
のマルチプロセス対応を行いました。
実際に何が起きてWindowsでのみ、この問題が出ているのかという理由ですが、それは、Windowsでのみ全てのウインドウがアクティブではなくなっても、未確定文字列を強制確定せず、次にウインドウがアクティブになった場合に未確定文字列の編集を続行できるようにしているためです。
何故、Windowsだけこのような挙動になっているかというと、Windowsではプロセスがビジー状態に陥った場合、Windows自身がウインドウを白っぽく描画し、タイトルバーに「応答無し」と表示を行います。この時、アプリはウインドウが一時的にディアクティブになったかのようにイベントをOSから受け取ります。ですので、この時に強制確定してしまうと、システム全体でCPUやディスクアクセスがビジー状態の場合に、まともにIMEが利用できなくなってしまいます。これを避けるため、意図的にこのような動作になっているのです。
では、IMEStateManager
がこの仕様が原因できちんと動かないのかというと、フォーカスが子プロセスから親プロセスに移動した時、子プロセス側のIMEStateManager
はウインドウがディアクティブになった場合と同じ通知を受けとっているためです。このため、実際にはフォーカスが親プロセスの別のエディタに移動した場合にも、子プロセスのIMEContentObserver
が破棄されないため、IMEにフォーカスを失ったという通知が送信されません。しかし一方では、親プロセスのIMEStateManager
は、IMEContentObserver
を生成し、IMEにフォーカスを得たという通知を出しますので、IMEからすると、複数のエディタが同時にフォーカスを持っているという異常な事態が発生していました。
また、それ以外にも、プロセスをまたいだフォーカス移動では様々な問題が起きていて、例えば、子プロセスのエディタがフォーカスを持っているときに、親プロセスのメニューを開いてもIMEが有効なままというバグもありました。
今回の修正ではまず、IMEStateManager
がアクティブなTabParent
を自身で管理するようにしました。nsIContent
がTabParent
を持っているのかどうなのかは、TabParent::GetFrom()
で取得できますので、これまでのコードに少し変更を入れるだけで簡単に変更できました。
次に、TabParent
のプロセスがフォーカスを失う時(親プロセスがフォーカスを得るときと、他の子プロセスがフォーカスを得る時)に、フォーカスを持っていた子プロセスに、フォーカスを失ったという通知を出すようにしました。これにより、ウインドウ全体がディアクティブになったと思い込んでいる子プロセス側のIMEStateManager
がIMEContentObserver
を適切に破棄することができます。また、フォーカスを持っていたnsPresContext
とnsIContent
を忘れることで、今までの処理と整合性がとれます。
最後に、メニューが開かれたときと閉じられたときに発生する疑似フォーカス移動を、フォーカスを実際に持っている子プロセスに通知するようにしました。これで、メニューを開き、一時的に全てのキーイベントを処理する親プロセス側のIMEStateManager
と、IMEを一時的に無効化する必要がある子プロセス側のIMEStateManager
で状態を同期できるため、従来のコードそのままでメニューの開閉にも対応することができました。
これまでのIMEStateManager
のコードを読んだことがある人には特に違和感が無いような修正になっていますので、我ながらなかなかうまく修正できたなと思ってます。
ちなみにこの一連のデバッグとパッチ作成は、カナダからの帰国時に太平洋上で行ったものです。いや、どうでもいいことですが。