2014年9月25日
Bug-org 1053048のテストケースの<textarea>
要素でEnterキーをTSFモードで押すと、長時間起動していたPCでは、ランダムにクラッシュするというバグを見つけました。
クラッシュ自体は、OnLockGranted()
を呼び出している間に、TSF内部でクラッシュしていたので、ある意味、TSFのバグと言えそうです。
問題のテストケースでは、Enterキーを押した時に、<textarea>
要素がリサイズされるため、一旦、フォーカスを失い、また、フォーカスを再度得る、という動作になるのですが、OnLockGranted()
を呼び出し中にフォーカスを失うことで、nsTextStore::Destroy()
が呼び出され、nsTextStore
が生成・保持していたTSFのオブジェクトの参照をやめた際に、実際に、それらのオブジェクト自体が解放されているようでした。
つまり、TSF内部で直接参照しているこれらのうちのいずれか、もしくは全てが、TSF内部でstrong referenceではなく、weak referenceで管理されていることを意味します。つまり、OnLockGranged()
を呼び出している間は、ITextStoreACP
インターフェースの実装側、つまり、アプリは、自信が生成したオブジェクトを手放してはいけない、ということになります。
これを解決するために、このバグでは、根本的な挙動に修正を入れました。
これまでは、nsTextStore
は、プロセス起動時にひとつだけ生成され、エディタがフォーカスを持っている間のみ、必要なTSFのオブジェクトを生成して保持し、エディタがフォーカスを失った際に、それらを解放していました。ですが、この疑似シングルトンのような挙動では複雑なケース、例えばドキュメントがロックされている最中にフォーカスが複数回移動するようなケースがあると、破綻しそうです。
そこで、エディタがフォーカスを得る度に、nsTextStore
を生成し、sEnabledTextStore
で掴んでおくようにし、フォーカスを失った際には、sEnabledTextStore
がそれまでのnsTextStore
のインスタンスを手離し、参照カウントがそのままゼロになったら完全に破棄されるようにしました。これで、メンバの複雑な管理には悩まされずに済みます。
続いて、OnLockGranted()
を呼び出す前に、nsTextStore
自身のインスタンスをスタック上のnsRefPtr
で掴んでおき、sEnabledTextStore
が手放しても、参照カウントが絶対にゼロにならないようにし、OnLockGranted()
の呼び出し中は、各種インスタンスが解放されないようにしました。
そして、sEnabledTextStore
がクリアされる直前には今まで通り、nsTextStore::Destroy()
を呼び出しますが、Destroy()
は、ドキュメントをロック中には呼び出された場合には、これを表すフラグを立てるだけにし、実際にメンバの解放は行わないようになりました。その後、RequestLock()
の処理が全て終わってから(ドキュメントのロックが解除されてから)、このフラグが立っている場合には、再度Destroy()
を呼び出し、クリーンナップを行うようにしました。
まとめると、nsTextStore
は、エディタがフォーカスを得た際に生成され、エディタがフォーカスを失う際に解放されますが、RequestLock()
が呼び出されている最中にフォーカスを失った場合のみ、直ちに解放されず、処理が完了後に自動的に解放されます。
Firefoxの検索ボックスで、MS-IMEを利用していると、時々発生するバグです。
Windows 8.1上で、MS-IMEで候補ウインドウを開いた状態で、スペースキーで順に、変換候補を切り替えていくと、希に、候補ウインドウがディスプレイの左上に表示されてしまうことがあるというバグがあることに気付きました。
早速ログをとってみると、NS_QUERY_CARET_RECT
がランダムに失敗している様に見えました。その原因について、色々と考えてみましたが、全く、納得できる答えが発見できず、久々にデバッガに頼りました。
デバッガで失敗した箇所を探してみると、nsRange
でセキュリティチェックに引っかかり、コンテンツへのアクセスに拒否されているケースがあることが分かりました。何故、そのような事が発生するのか、詳しいことは分かりませんが、スタック上には、Javascriptで実装された、autocompleteのタイマーからの呼び出しと思われるメソッドの呼び出しがあり、それによって、TSFに通知が飛び、TSFからnsTextStore
へ文字位置の問い合わせが来ていました。つまり、TSFからのリクエストが、content権限で処理されていたことが原因な様です。
ひとまず、解決策として、nsRange
に新設されていた、内部処理用のメソッドをContentEventHandler
からは利用することで、セキュリティチェックを回避するようにしました。ContentEventHandler
自身はJavascriptから呼び出して利用するには、chrome権限が必要ですので、セキュリティ的なチェックがそもそも必要ないためです。
Bug-org 826657の修正により、フォーカスを持ったエディタ内の文字上でマウスボタンが押されるか、離された時、content側から、widgetにその通知を出せるようになりました。
このバグでは、IMMモードのマウスイベントのハンドリングを、この通知を利用して再実装しました。これにより、実装がより単純になり、e10sへの対応も可能になりました。
nsTextStore
には、プロセス起動中にTSFのオブジェクトをずっと参照し続ける、staticなメンバが多数あります。今までは、これらの参照カウントを、マクロを活用して操作していましたが、これを分かりやすいコードに書き換えられるStaticRefPtr
が新たに作成されているので、全てこれで置き換えよう、というバグです。
ついでに、sTsfFooBar
という冗長な名前だったメンバは、sFooBar
とリネームされました。
Bug-org 826657の修正により、フォーカスを持ったエディタ内の文字がクリックされた際に、widgetはその通知を受け取ることができるようになり、IMEがそのイベントを消費した場合には、content側ではそのイベントを無視するようになりました。
この通知をe10sに対応させよう、というのがこのバグです。
単純に、プロセス間で、同期通信で通知を、子から親に通知し、その結果(消費されたか否か)を返すようにしています。
MS-IMEで、変換中に新たに文字を追加入力すると、変換している部分が確定され、追加の文字が未確定文字列として挿入されます。しかし、MS-IMEは非常にトリッキーにこれを実現しています。まず最初に、未確定文字列に新しい文字を追加し、変換済みの文節のみを部分的に確定します。そして、新しい文字だけ未確定のまま、その状態が継続します。この複雑な動作がGeckoではうまく処理できていなかったのをBug-org 1057192で修正したのですが、その修正が中途半端で、未確定文字列の後ろに確定済みの文字が既にある場合は、追加入力した一文字が、キャレット直後の一文字を上書きしてしまう、というバグが残ってしまっていました。
部分確定を行う際に、まず、最新のコンテンツと選択範囲を保存しておき、未確定部分を一旦、コンテンツから取り除き、通常通りに確定。その後でコンテンツと選択範囲を復元し、compositionstart
を生成する、という前回の修正の仕方にこのバグの原因がありました。
nsTextStore
は、TSFのドキュメントロックを実現するために、コンテンツや選択範囲変更のリクエストを即座にエディタに反映させるのではなく、キャッシュしておいたコンテンツと選択範囲を、TSFからのリクエストに応じて変更しつつ、それに必要な後でエディタに反映される際に必要なDOMイベントをpending actionとして、配列に保存し、ロック解除時にDOMイベントを連続して発火するという方法をとっています。
このため、上記の処理の流れでは、pending actionからcompositionstart
イベントを生成する際には、復元したはずの、新しい未確定文字列をエディタ側に送っていないのに、未確定文字列が存在しているはずの、その部分を選択するようにエディタにイベントを発火し、その部分を上書きする形で未確定文字列を生成しようとしていました。これが、新しい未確定文字列と同数の確定済みの文字が上書きされてしまう原因です。
新しい未確定文字列はcompositionstart
イベントの後に続くtext
イベントで自動的に送信・挿入されるので、compositionstart
イベントの発火時には、選択範囲を変更して上書きするのではなく、部分確定後のキャレット位置にそのまま、未確定文字列が挿入されるように修正しました。
今回の一件で、キャッシュをトリッキーにいじっても、それがpending actionの解消時に結果として反映されるわけではないという、当たり前の教訓が得られました。
TestJSONWriter
という、コンパイルコードテストが追加されていたのですが、日本語版のWindowsでこれをビルドしようとすると、コンパイルエラーが出るというバグです。
エラーの発生した行を見てみると、非ASCII文字が含まれていて、cppファイルは、BMP無しのUTF-8形式で保存されていました。
調べて見ると、VisualStudioは、BOM無しのファイルはUTF-8として読み込まない仕様のようなので、日本語環境ではShift-JISとして読み込み、たまたま、"
がエスケープされてしまっている様でした。
仕方ないので、非ASCII文字を全てエスケープシーケンスで書き直し、VisualStudioでも、OSのロケールを選ばず、ビルドできるようにしました。
2014年9月30日
document
のルートノードを、Javascriptを使って、<textarea>
要素にし、さらに、その子要素に、<input type="text">
要素を追加し、これにフォーカスをあわせると、デバッグビルドでは異常を検知して、IMEContentObserver
内でクラッシュする、というバグです。
検知した異常というのは、フォーカスノードから算出した、編集可能なルートノードと、そのエディタが一致しない、というものでした。
IMEContentObserver::Init()
内でIMEStateManager::GetRootEditableNode()
を呼び出し、フォーカスノードの親が編集可能なら、さらに祖先が編集可能か調査し、編集可能な最後の祖先を探します。これにより、IMEContentObserver::Init()
はこのケースでは<input type="text">
要素が欲しかったのですが、<textarea>
要素が返され、さらに、このような異常な状況下では、<textarea>
要素は自身のエディタを生成していませんでした。
これだけなら、非常にマイナーかつ、実際には起こりえないようなバグですが、このバグの原因からすると、<body contenteditable><input type="text"></body>
の様な場合にも、<input type="text">
要素がフォーカスを持っていても、算出される編集可能なルート要素は、<body>
要素、ということになります。
そのため、IMEContentObserver
はこのようなケースでは、誤った要素を監視していることになり、これはデリケートなTSFモードでは色々と嫌なシナリオが思い浮かびます。
ひとまず、このバグでは、要素を祖先方向に辿って、編集可能か調べていく際に、そのノードが独立したセレクションを管理しているかどうかを調査し、該当した場合にはそれをルート要素として返すように、IMEStateManager::GetRootEditableNode()
を修正しました。
独立したセレクションは現在、<input>
、<textarea>
、<select>
要素のみが持ち得ていて、<select>
要素は常に編集不能と判定されるので、論理的にはまずい対応ですが、とりあえずは動く形になっています。
TextComposition
に、IMEの未確定文字列を強制確定、もしくは強制キャンセルさせるAPIを追加し、TextComposition
自身が同期的にこれを実行することを保証すべき、というバグです。
現在、これらのリクエストは、ネイティブのAPIを利用して、IMEにリクエストし、それがどのように実行されるかは各OSやIMEの挙動に依存する、という形になっています。このため、iBusのように、非同期で未確定文字列を操作されてしまうと、Gecko内部でAPIを呼び出した側は、まだ、未確定文字列が残っているかどうかを意識して処理を行わなければいけません。
これはもちろん、Gecko本体のみならず、Webアプリにも、OSやIME依存のバグが産まれやすいことを意味していて、非常によくありません。また、e10sでは確実に非同期での確定になってしまいますので、e10s対応のためにもどうにかしなければいけない話です。
今回の修正では、nsIWidget::NotifyIME()
を呼び出し、その呼び出し直後に確定やキャンセルが実行されていない場合にはDOMイベントを発火し、これをエミュレートし、その後にやってくる非同期で実行されたネイティブイベントは無視するようにしました。
また、Linuxのように、明示的に確定や破棄といったネイティブAPIが存在しない環境のために、それぞれのリクエストで期待した確定文字列が来ていない場合には、確定文字列を期待される文字列で上書きする機能も入れています(ただし、全角スペースをプレースホルダーとして未確定文字列に挿入する中国語のIMEでは、非同期確定の場合には期待通りに動作しませんが……)。
この修正により、nsGtkIMModule
は、同期確定・キャンセルをエミュレートするコードが削除されたので、処理が単純化しています。強制確定に絡んだバグがもしあったら、それも同時に修正されているかもしれません。