この日記はMozillaのプロダクトへの貢献者としての私の成果を中心に、気になったバグやWeb界隈の話題について書いていますが、 断り書きがある場合を除き、いかなる団体のオフィシャルな見解ではありません。あくまでも個人的なものです。 Mozilla Foundation、Mozilla Corporation、及び関連企業の公式情報ではないことに注意してください。

現在、XHTML 1.0 (もどき)から、HTML5なコンテンツに修正中です。古い日記は修正が完了していませんので表示が崩れます。 順次、修正していく予定ですのでしばらくお待ちください。

もずはっく日記(2015年12月)

2015年12月30日

Bug-org 1213589 [TSF] Converting Hangul to Hanja with MS Korean IME converts unexpected range of fixed string 初回投稿日時: 2015年12月30日13時24分18秒
カテゴリ: IME Mac Mozilla Core Mozilla45 TSF Windows バグ修正
固定リンク: id=2015123000
SNS: (list)

TSFモードがデフォルトで有効になってから、韓国向けMS-IMEを、韓国のリッチテキストエディタを利用したいくつかのWebサイトで漢字や記号への変換が期待通りの範囲を行ってくれなくなったというバグです。一ヶ月以上、これにかかりっきりに近い状態の、実に難産なバグ修正でした。

韓国向けMS-IMEでは、Hanjaキー(Hanjaは漢字のこと)を押すと、既に入力済みの文字を変換しようとします。IMMの場合、未確定文字列のみが対象となっていたので、問題が発生することはありませんでしたが、TSFの場合、未確定文字列が変換対象を確定済みのテキストも含めて、最高で直近の改行文字の直後まで遡って探すようになっていました。報告してくれた例によると、안녕하세요(改行)안という文字列の最後でHanajaキーを押した場合、最後のを変換したいのに、Geckoのリッチテキストエディタ上では안녕が変換対象になってしまうサイトが多々あるとのことでした。

まず、Geckoのリッチテキストエディタの挙動を確認してみると、Enterキーを押したときに挿入されるものがエディタの内容によって異なることが分かりました。例えば、<div contenteditable></div>というエディタだと、<br>要素が挿入されます。それに対して、<div contenteditable><p></p></div>というエディタで、<p>要素内にキャレットがある場合、Enterキーを押すと、そこで<p>要素を分割します(HTMLのソースコードで言うなら、</p><p>を挿入する形)。この、後者の場合が問題でした。

現在、IMEのリクエストに対して返すプレーンテキストはmozilla::ContentEventHandlerで生成されています。この際に、<br>要素は\nもしくは\r\nに変換されますが、他の要素の境界には何も挿入されません。そのため、上記の例が<p>안녕하세요</p><p>안</p>だった場合、生成されるプレーンテキストは안녕하세요안になってしまっていたのです。

そこで、このバグでは、mozilla::ContentEventHandlerの設計を大きく変更し、一部の例外の要素を除き、要素の開始タグの位置にあたる部分に改行文字を挿入して返すようにしました。この変更に伴い、<img>要素等でテキストが分割されている場合にも改行文字が挿入されることで単語の境界が明確になるようになっています。

ContentEventHandlerのこの機能は、Mac OS X 10.10以前では辞書引きの際にコンテンツを返すのに使われていますので、今までよりも期待通りの範囲が辞書引きされるように改善されています(OS X 10.11 El Capitanでは新しいAPIに応答しないといけなくなっているものの、そこが未実装なため辞書引きの機能自体が動作しません)。

時間がとれ次第、閉じタグの位置にも改行文字を挿入するように修正しようと考えていますが、韓国向けMS-IMEで問題が発生するにはかなりあり得ないHTML片になっていないといけないので、優先度は低いです。

Bug-org 1179632 [e10s][TSF] Remaining composition in child process causes hitting assertion aCompositionEvent->message == (2200) in child process 初回投稿日時: 2015年12月30日13時47分04秒
最終更新日時: 2015年12月30日13時47分47秒
カテゴリ: e10s IME Mozilla Core Mozilla45 TSF Windows バグ修正
固定リンク: id=2015123001
SNS: (list)

e10sモードで、コンテンツ内のテキストエディタに未確定文字列がある際に、ウインドウを×ボタンで閉じるとクラッシュするというバグです。

このバグの原因は、×ボタンでウインドウを閉じた場合、TabParentと閉じようとしているネイティブウインドウをラッピングしているnsIWidgetのインスタンスの関連付けが先に失われてしまい、子プロセス内から親プロセスのIMEにアクセスしようとしても失敗し、それによって発生する矛盾した動作を検出して、MOZ_CRASH()を呼び出していたことでした。

まず、子プロセスから親プロセスのIMEにアクセスする際に、プロセス間通信を利用して、親プロセスのネイティブのIMEコンテキストを同期的に取得しようとする所に問題があることが分かります。そもそも、この無駄なプロセス間通信自体を無くしてしまい、子プロセスの方で最新の未確定文字列を発生させているネイティブのIMEコンテキストを識別するIDをキャッシュしておく方がよさそうです。そこで、今回の修正の準備として、nsIWidget::GetInputContext()からネイティブのIMEコンテキストのIDを削除し、代わりに、WidgetCompositionEventがこれを運ぶように修正しました。

そして、プロセス間通信を行わないnsIWidget::GetNativeIMEContext()を新設し、PuppetWidgetは親プロセスから来たWidgetCompositionEventを発火する際にキャッシュしておいたネイティブIMEのコンテキストの値をこのメソッドで返すようになっています。

これらの修正により、XP部分から見れば、未確定文字列がある場合は、必ず、そのnsIWidgetインスタンスに紐付いているネイティブのIMEコンテキストが返ってくる状態になっていますので、今までのコードが確実に動作するようになっています。

なお、自動的にネイティブIMEコンテキストを初期化するためにnsIWidget::GetNativeData(NS_NATIVE_IME_CONTEXT)という手段も用意されていますが、PuppetWidgetに対してこれを呼び出すと(安全に)クラッシュするようになっているので混同しないように注意してください。

2015年12月31日

Bug-org 1234120 [IMM] Either Google Japanese Input on Win7 or Japanist 2013 doesn't show suggest window at correct position after previous composition string is committed 初回投稿日時: 2015年12月31日00時08分50秒
最終更新日時: 2015年12月31日00時09分30秒
カテゴリ: IME Mozilla Core Mozilla44 Mozilla45 Mozilla46 TSF Windows バグ修正
固定リンク: id=2015123100
SNS: (list)

Firefox 42以降、TSFモードで、IMMのIMEを利用すると、文字を確定後に新しく未確定文字列を入力し、変換しても、適切な位置に候補ウインドウが表示されないというバグです。

TSF-awareなアプリケーションであっても、従来のIMMのIMEをサポートするには、従来のアプリケーションと同様にIMMのメッセージをハンドリングし、IMM-IMEが期待するAPIを呼び出して候補ウインドウ位置を設定する必要があります。この、候補ウインドウ位置を指定するコードは、現在の選択範囲を基準に動作していることが多いのですが、Bug-org 1184449の修正によって、IMMHandlerはその選択範囲をIMEContentObserverから送信される新しい選択範囲の情報をキャッシュし続けて、負荷を減らすようになりました。ところが、TSFモードが有効な場合、IMEContentObserverに対して、TSFTextStoreが必要とする通知の情報だけを渡していました。このため、IMEが原因で発生した選択範囲の変更を受け取れていませんでした。

今回の修正では、このような矛盾を解消するために、TSFモードが有効かつ、IMMモードのサポートも行っている場合は、どちらか一方でも必要としている通知を要求するようにし、それぞれが不要な通知は無視するように修正しています(どちらかだけを設定した場合、ユーザがIMM-IMEと、TSFのTIPとでスイッチした場合にIMEContentObserverにそれを通知、再設定する手段が無いため、対応しきれない)。

また、この修正により別のregressionも発生が露見し、候補ウインドウ位置がそれだけでは期待通りの位置に表示されなくなっていました。

その原因はBug-org 555642の修正で、IMMHandlerは、IMEの指定するキャレット位置が変換ターゲットの文節内にある場合に、キャレットを非表示にするためにeCompositionChangeイベントで送信しなくなったため、IMMHandlerのキャッシュしているキャレット位置と、実際にGeckoが非表示で設定しているキャレット位置とにズレが生じ、スクリーン上のキャレット位置に候補ウインドウを表示させようとするとGeckoの非表示のキャレットの位置に表示されていました。

こちらのバグは、eCompositionChangeイベントの発火時にキャッシュしていたIMEの指定したキャレットのオフセットを破棄することによって解決しています(常に指定したオフセットの文字の矩形を問い合わせてそこに候補ウインドウを配置するようになるため)。

Bug-org 1234422 [TSF] Google Japanese Input sometimes fails to update its candidate window at converting the composition string 初回投稿日時: 2015年12月31日00時40分57秒
カテゴリ: IME Mozilla Core Mozilla44 Mozilla45 Mozilla46 TSF Windows バグ修正
固定リンク: id=2015123101
SNS: (list)

Windows 8以降ではGoogle日本語入力はTSFTIPとしてインストールされますが、Firefox 43以降のTSFモードでは変換候補ウインドウが出ている状態で変換キー(スペースキー)を押しても未確定文字列は更新されても、候補ウインドウ内の表示が更新されなかったり、候補ウインドウ自体が表示されないことがあるというバグです。

このバグは、Bug-org 1204519の修正で削除した、Google日本語入力向けのハックが別の未発見だった問題を洗い出してしまったというものでした。いわゆる、単純なregressionではありません。

TSFにはドキュメントロックという概念があります。TIPが未確定文字列を挿入・変更する際には、ドキュメントをロックし、ロック中はアプリ側でそのドキュメントの内容が変更されることは無いことになっています。しかし、ブラウザで単純にTSFを実装してしまうと、ドキュメントのロック中にcompositionupdateイベント等を発火することになり、Javascriptによって何らかの変更が行われる可能性があります。Geckoはこれを防ぐために、ドキュメントがロックされている間は発火すべきイベントをキューに保存し、アンロック時にまとめて発火することで対応しています。

しかし、この挙動を実現するためには、ドキュメントロック中に来る、未確定文字列の矩形の問い合わせに対して即、回答することはできません。未確定文字列をイベントを使ってエディタに送信するのがドキュメントがアンロックされてからだからです。TSFではこれに対応するために、TS_E_NOLAYOUTというエラーコードが用意されていて、これを返した場合、TIPは、レイアウトの計算が終了後に必ず呼び出さなくてはいけないITextStoreACPSink::OnLayoutChange()の呼び出しを待ち、再度、レイアウト情報の取得をリトライできるようになっています。

しかし、問題はこのITextStoreACPSink::OnLayoutChange()の呼び出しタイミングです。これまでGeckoは、ロックされた状態の最後にこれを呼び出していました。しかし、よくよくログを調べてみると、TSFからOnLayoutChange()の呼び出し中にドキュメントのロックのリクエストがネストされて呼び出されていました。しかし、もちろん、そのドキュメントロックには必ず失敗します。

最初はこれが原因かと思い、ドキュメントがアンロックされた直後にOnLayoutChange()を呼び出すパッチを書いてみました。しかし、TSFからドキュメントロックの要求が来るものの、そのロック中には何もしなかったり、選択位置だけを確認して終了し、肝心のTIPには通知が行っていないようでした。

そこで苦肉の策として、PostMessage()を利用して、ITextStoreACP::RequestLock()が完全に終了後、次のイベントループ処理でOnLayoutChange()を呼び出してみると、ようやくGoogle日本語入力がレイアウトの再取得を行う様になりました。ひとまず、この修正がNightlyには既に入っています。

ただし、PostMessage()を利用したハックでは、WM_KEYDOWNメッセージ等の入力メッセージが優先されてしまい、期待通りに機能しない可能性があります。そこで、WM_KEYDOWNをTSFに送信した最中にこのハックが動作した場合には、その直後に(次のメッセージを処理する前に)、このハックを走らせるべきかを検証中です。それが終わり次第、BetaとAuroraでも修正の予定です。

ちなみにMS-IMEやATOKで同様の問題が発生しないのは、TSFTextStoreのログで確認する限り、未確定文字列の編集のためのドキュメントロック中にはITextStoreACP::GetTextExt()を候補ウインドウのためには呼び出さず、ITextStoreACP::RequestLock()の後に能動的にITextStoreACP::GetTextExt()のためのロックを再度要求してきて処理しているためです。

個人的にはGoogle日本語入力もこのような動作に修正した方がアプリ側の微妙な挙動の変化で「相性問題」が出なくて良いのではないかと思います(もっとも、MSDNのTSFのドキュメントの内容からすると、TSFがOnLayoutChange()の呼び出しを確実にTIPに後から伝えるのがあるべき姿だとは思うのですが……)。