GmailのリッチテキストエディタにTabでフォーカスを移動するとIMEが無効になったままになるというバグです。
HTMLエディタのフォーカス処理がぐちゃぐちゃだったことが原因でした。非常に多くのバグがコード上では連携していたので一気に修正しています。
まず、Gmailのメール作成画面ではdesignMode
を利用していますが、このdesignMode
はドキュメントを特殊なモードに変更してしまうというもので、一部の例外を除き、designMode
上の要素ノードはフォーカスをセットされません。ドキュメントノード自体がフォーカスを得るのみ、という特殊な仕様になっています。
nsFocusManager
はフォーカス移動時にnsIMEStateManager
(ISM)にこれを通知し、ISMが新しいフォーカスに基づいてIMEの状態を設定します。ですが、designMode
の特殊な事情によりフォーカス移動時にfocus
イベントが発生しないため、ISM的には最後のblur
イベント以降、何もフォーカスを持っていない状態が継続されていた、という状況になっていました。
つまり、ISMに常に通知を送るだけで修正できそうですが、この辺のコードは非常に複雑でこれだけを修正すると他でregressionが出ました。この前段階の処理にも問題があったということです。
少し話をHTMLエディタから離れて、フォーカスの仕様について考えてみましょう。nsFocusManager
の内部処理ではひとつ例外的に、本来はフォーカスを得られないものの、内部処理ではフォーカスを得る要素が存在します。それはドキュメントのルート要素です。ルート要素がフォーカスを得られないとTabによって選択するフレームを変更し、キーボードでスクロールする、ということができなくなってしまいます。ただし、本当にフォーカス可能な要素という訳ではないので、focus
イベントやblur
イベントは生成しません。この条件判定時に、nsFocusManager
はルート要素であるかどうかだけをチェックしていました。
そう、nsFocusManager
はルート要素はフォーカス可能で編集可能であることはない、という前提のもとに実装されてしまっていたのです。そこでfocus
イベントを編集不能かdesignMode
により編集可能なルート要素には送らない、という本来の形に変更しました。これにより、ISMは正しく機能し、フォーカス処理にも問題はなくなりました。ですが、類似サイトをいくつか検証しているとまだうまくいかないケースがありました。それは<html><body contenteditable="true"></body></html>
というケースです。
親からTabによりフォーカスを移動して来た時に編集不能なルート要素(html
要素)が最初にフォーカスを受け取ってしまいます。ですが、ユーザから見たらbody
要素にフォーカスが移動しているべきです。もちろんもう一度Tabを押すとbody
要素にフォーカスが移動するのですが、スクロールバーが無いときには意味が分かりませんし、もともとtextarea
要素と同じでそのような操作ができることをユーザは求めていません。そこで、このケースにおいてはTabによるフォーカス移動ではルート要素を無視するように修正しています。
これでおおむね動作するようになりましたが、Shift+Tabが根本的に動作していなかったことや、body
要素の外でクリックした時にも問題があったので、同時に修正しています。
今回の修正でも修正されていない類似のバグはまだ数多く残っていますが、現在のサイクルでできる限り修正してしまいたいと考えています。