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

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

もずはっく日記(2014年7月)

2014年7月24日

Bug-org 971393 IME composition string becomes invalid if parent of DOM tree is changed 初回投稿日時: 2014年07月24日05時12分28秒
最終更新日時: 2014年07月24日05時38分49秒
カテゴリ: Mozilla Core Mozilla33 TSF Windows バグ修正
固定リンク: id=2014072400
SNS: (list)

未確定文字列を持った<input>要素や、<textarea>要素等が、親ごとDOMツリーから削除されて、別の要素に付け替えられると、動作がおかしくなる、というバグです。

Bug-org 960866の修正で、IMEが発生させたDOMイベントはすべて、未確定文字列を持っているエディタ上で発生するようになったので、このバグの大部分はそれで修正されていました。しかし、WindowsのTSFモードでは、IMEがその後使えなくなる、という問題が残っていました。

その原因は、DOMイベントが発生するとセキュリティリスクが高い状態にあると、script blockerという内部システムが機能し、PresSheelがイベントのハンドリングを行わないということにありました。この際に、DOMイベントを実際には生成しないWidgetQueryContentEventをエラーでwidgetに返してしまっていたので、TSFが混乱してしまっていたようです。

PresSheelは、script blockerが働いていても、DOMイベントを生成しない内部イベントは処理するように修正することで対処しました。

Bug-org 962920 [TSF] Should commit composition at entering modal state 初回投稿日時: 2014年07月24日05時19分56秒
最終更新日時: 2014年07月24日05時39分11秒
カテゴリ: Mozilla Core Mozilla33 TSF Windows バグ修正
固定リンク: id=2014072401
SNS: (list)

TSFモードで、未確定文字列がある時に、ファイルを開くダイアログ等を表示すると、未確定文字列が確定されず、ダイアログ上で何も入力できない、というバグです。

TSFはスレッド単位で管理されているシステムなので、ブラウザ自体のIMEと、Windowsネイティブのダイアログ上とで、同じコンテキストを共有していることになります。このため、ブラウザ自体で未確定文字列を確定せずに掴んだままだと、ネイティブダイアログ上でどうしようもなくなる、というのがこのバグの原因です。

正攻法ではないのですが、ネイティブのモーダルダイアログが開くと、WM_ENTERIDLEが親ウインドウに送信されますので、これを受け取ったら、強制確定するように修正しました。理想的なアプローチとしては、ネイティブのモーダルダイアログを開くAPIを呼び出す直前で強制確定を行うようにすべきですが、メンテナンス性が悪く、新しいダイアログのサポート時に対応が漏れる可能性がある上、手間がかなりかかることから、このようなアプローチに落ち着いています。

Bug-org 1037330 [TSF] Add new pref intl.tsf.force_enable and make intl.tsf.enable ignored on XP or WinServer 2k3 初回投稿日時: 2014年07月24日05時28分47秒
最終更新日時: 2014年07月24日05時39分32秒
カテゴリ: Mozilla Core Mozilla33 TSF Windows バグ修正
固定リンク: id=2014072402
SNS: (list)

Windows XPでのTSFのサポートにはコストがかかる上、OS自体のサポートも終わっているので、intl.tsf.enabletrueにしていても、Windows XPと、Windows Server 2003上では無視し、一部、まだ利用しているかもしれないタブレットPCユーザ等のために、intl.tsf.force_enabletrueに変更すれば、強制的に有効にできるようにしよう、というバグです。

Windows VistaでTSFのバージョンが上がっているため、それ以前のWindowsでのTSFのサポートには、一部、全く別のコードが必要になります。また、Windows XPの日本語版には、互換をとるのが著しく困難な、ナチュラルインプットというIMEが存在しています。これらの完全サポートを考えると、いつまでたっても、TSFモードを有効にできないので、古いWindowsでのTSFサポートを公式にはサポートしなくすることになりました。

Bug-org 995893 TEST-UNEXPECTED-FAIL | leakcheck | 8 bytes leaked (nsTArray_base) with intl.tsf.enable=true 初回投稿日時: 2014年07月24日05時37分37秒
最終更新日時: 2014年07月24日05時38分11秒
カテゴリ: Mozilla Core Mozilla33 TSF Windows バグ修正
固定リンク: id=2014072403
SNS: (list)

TSFモードを有効にしていると、テストでメモリリークが検出される、というバグです。

nsTextStoreの実装が良くなく、テストスイートのメモリリークの検出時には正確なリーク情報が出ていなかったので、原因の特定に時間がかかりましたが、その辺を修正してみると簡単な話でした。

原因は、nsTextStore自体のリークで、これによって、そのメンバがリークしているのを検出されていたのでした。

nsTextStoreは参照カウンタまわりを、自前のコードで実装しており、コンストラクタで1がセットされるようになっていました。このため、

nsRefPtr<nsTextStore> textStore = new nsTextStore();

というコードでインスタンスを生成した段階で、参照カウンタが、1つ余分に増えており、これによって永遠に開放されることがなくなってしまっていました。

再発防止のため、また、メモリリークを検出しやすくするために、widget/windows/WinUtils.hIUnknown派生クラスの実装時に使えるマクロを作成し、これを利用することで、nsISupportsの派生クラスと同様に扱えるように修正しています。

Bug-org 1038089 Log the behavior of IMEStateManager for debug 初回投稿日時: 2014年07月24日05時48分04秒
カテゴリ: Events Mozilla Core Mozilla33 バグ修正
固定リンク: id=2014072404
SNS: (list)

IMEの状態管理(無効・有効)や、IMEにフォーカスを持ったエディタの内容の変更情報を提供するIMEContentObserverを管理するのが、IMEStateManagerですが、このクラスは主に、nsFocusManagerと共に動作するので、デバッガでのデバッグが非常にやりにくいクラスです。

これのデバッグ効率を高めるために、ログを吐くことができるようにしよう、というバグです。ただし、必要無いので、通常のリリース版ではログは吐けません。ビルド時にac_add_options --enable-logging*.mozconfigファイルで明示してビルドする必要があります。

NSPR_LOG_MODULES=IMEStateManager:1で、各メソッドの呼び出し状況のみをログに残せます。2を指定すると、それに加えて、想定外のエラーが発生した場合のエラーログも残せます。さらに、5を指定すると、より細かい、各メソッドをデバッグできるだけの情報が残せます。

NSPR_LOG_FILEの指定ももちろん必要ですのでご注意ください。

Bug-org 1037346 Unexpected assertion in nsTextStore at running toolkit/content/tests/chrome/test_dialogfocus.xul 初回投稿日時: 2014年07月24日06時04分39秒
カテゴリ: Mozilla Core Mozilla33 TSF Windows バグ修正
固定リンク: id=2014072405
SNS: (list)

TSFモードを有効にして、全テストを走らせてみると、toolkit/content/tests/chrome/test_dialogfocus.xulを実行中に、nsTextStoreがassertionを吐き、オレンジになるというバグです。

問題のテストはダイアログを開き、初期フォーカスが想定通りの要素になっているのかを検査していました。しかし、このテストの奇妙なところは、windowfocusイベントをcaptureフェイズで捕まえ、そのターゲットを調べ、さらに、そのままダイアログを閉じてしまう、という、現実ではちょっとあり得ないものでした。

IMEStateManagerのログで調べてみたところ、ダイアログの<input>要素にフォーカスが移動した時点で、IMEStateManagerが、IMEの状態を有効にしにいくのですが、この際に、まだ、フォーカスを持っている要素をグローバル変数に格納していませんでした。そして、そのまま、IMEStateManager::UpdateIMEState()が呼び出され、あり得ないフォーカス状態を元にIMEContentObserverが作られ、nsTextStoreがフォーカスを取得した処理を行っていました。その後、IMEStateManager::UpdateIMEState()が終了し、元のフォーカスの移動時の処理に戻ってきた後、<input>要素のエディタがフォーカスイベントを受け取った際に、本来なら存在しない、IMEContentObserverを正しい情報と共に再生成し、nsTextStoreが再び、フォーカスを取得したという通知を受け取って、assertionを吐いていた、という状態でした。

問題は何故、予期しないタイミングでIMEStateManager::UpdateIMEState()が呼び出されていたか、ということなのですが、要素が期待するIMEの状態を、nsIContent::GetDesiredIMEState()で取得しにいくと、その段階で、<input>要素がエディタをようやく作成し、エディタの初期化処理が、IMEStateManager::UpdateIMEState()を呼び出していたため、このような変なスタックになっていたわけです(エディタを後から作るのは、<input>要素が大量にあるサイトでも、初期化のパフォーマンスが低下しないようにするために、初めて必要になった時、例えば、フォーカスを受け取った時にエディタを生成するようになっています)。

IMEStateManager::UpdateIMEState()が呼び出された時点で、グローバル変数が期待通りの状態になっていれば大丈夫かと思いましたが、他のテストがやたらとオレンジになってしまうのでこのアプローチは断念し、ちょっとダサいですが、nsIContent::GetDesiredIMEState()を呼んでいる最中は、IMEStateManager::UpdateIMEState()が呼び出されても何もしない、という形で修正しています。

2014年7月31日

Bug-org 1045978 Create InternalSVGZoomEvent and InternalSMILTimeEvent for NS_SVGZOOM_EVENT and NS_SMIL_TIME_EVENT 初回投稿日時: 2014年07月31日18時48分44秒
カテゴリ: Events Mozilla Core Mozilla34 バグ修正
固定リンク: id=2014073100
SNS: (list)

イベントな内部クラスは、どのクラスのインスタンスかを高速に判断できるように、eventStructTypeというメンバがあり、ここに、各イベントクラスのコンストラクタが自身を示す値を書き込んでいるのですが、SVGのズームイベントと、SMILのタイムイベントの二つは、既存のイベントクラスのインスタンスに、専用の値をセットしてEventDispatcher::CreateEvent()等で本来とは異なる動作になるようにハックを仕込んでいました。

しかし、これらのせいで、一部、本来ならマクロを利用して生成できるコードができないという制約の原因になっていたので、今回、これらのイベント用の派生クラスを特にメンバを追加したりせずに作成し、整理しています。

Bug-org 496360 [TSF] Severe performance problems pasting into contenteditable editor or designMode editor when TSF is enabled 初回投稿日時: 2014年07月31日23時29分47秒
カテゴリ: Android Fennec Mozilla Core Mozilla34 Thunderbird TSF Windows バグ修正
固定リンク: id=2014073101
SNS: (list)

TSFを有効にしていると、膨大な行数の貼り付けのパフォーマンスが異様に悪くなる、というバグです。バグIDを見れば分かるように、かなり長い間修正できなかったバグですが、ここ最近のIMEContentObserver周りや、様々な他の修正により、この修正がようやく可能になりましたが、それでも強引な最適化を行って解決しています。

TSFは、フォーカスのあるエディタの内容が変更される度に、その旨をTSFに通知しないといけないのですが、その際に変更された範囲を示す、最初のオフセット、編集前の選択されていた文字列の最後のオフセット、編集後のオフセットを渡さなければいけませんが、これの計算が非常にコスト高だったことがこのバグの原因です。

もともとは、<textarea>要素でも再現していたのですが、プレーンテキストエディタは途中から、内部処理で、テキストノードと<br>要素ではなく、white-space: pre;なテキストノードで表現されるようになったため、このバグは、contenteditableや、designModeによって生成されたHTMLエディタでのみ発生するようになっていました。ですので、Thunderbirdや、Gmail等はこのバグの影響が大きかったはずです。

テキストが追加された場合、IMEContentObserverのmutation observerに、先頭のノードの追加から順次、通知されます。この際に、毎回、テキストの挿入位置、追加されたテキストの長さを計算し、widgetへ通知していました。

ちなみに、テストでは見つけにくいのですが、削除の場合にも同様の問題があり、IMEContentObserverのmutation observerに、先頭のノードの削除から順次、通知されます。この際に、毎回、削除されるテキストの先頭のオフセットと、削除されたテキストの長さを計算し、widgetへ通知していました。

これらを解決するために、まずは、複数の変更をひとつのテキスト変更と見なして通知できるように、widgetへ通知するオフセットをマージ可能にし、さらに、エディタ上での編集が終了するまで通知を行わないようにしました。これにより、一回の編集につき、一回のテキスト変更、選択範囲変更、レイアウト変更通知がwidgetへ送信されないようになっています。

しかし、この修正では残念ながら、手元の環境ではパフォーマンスは上がりませんでしたが、IME (TIP)の実装・仕様によっては、この修正だけでも劇的にパフォーマンスが上がっていると思います。

次に、連続したテキストの追加時と、連続したテキストの削除時に、何度も似たような、もしくは同じオフセットを計算している部分を改善することにしました。追加時は、追加されたノードの位置と、その終端のオフセットを記憶しておき、次の追加時に同じ範囲を計算する場合にはキャッシュしたオフセット値を使うようにしました。削除時は、削除されるノードの位置とその先頭までのオフセット値を記憶しておき、同じ位置が次に削除される場合にはキャッシュしたオフセットを使うようにしました。

この、ノード位置もキャッシュしておくことで、MutationObserverをJavascriptで利用して複雑な編集作業をエディタがDOMツリーに変更してしまい、キャッシュした値が破損した可能性がある場合には破棄して再計算するようにして、安全策を講じています。

これらの修正により、ひとまず、見つかっている編集時の極端なパフォーマンス低下は解決しました。手元での検証結果では、TSFモードと、IMMモードで同じようなスコアが出ました。理論上はTSFモードの方が、どれだけ最適化していても若干遅いはずではありますが。

また、コードを調べたところ、テキストの変更は、Windows版のTSFモードの他に、Android版でも監視対象となっていました。このため、Androidでもこのバグは発生していたようですが、同様に修正されています。

Bug-org 1043182 IMEContentObserver::CharacterDataChanged() doesn't convert new lines in replaced and inserted text to native new lines 初回投稿日時: 2014年07月31日23時39分16秒
カテゴリ: Mozilla Core Mozilla34 TSF Windows バグ修正
固定リンク: id=2014073102
SNS: (list)

台湾でメジャーらしい、ChangJieというIMEがあるのですが、<textarea>要素内で改行を挿入直後に、そのまま入力すると、前の行の行頭に入力されてしまうというバグです。何故かBug-org 496360を修正するパッチのうちのいくつかを適用したら再現するようになりました。

テキストノードの一部が変更された場合、IMEContentObserverのmutation observerの、CharacterDataChanged()が呼び出されるのですが、この際に、変更によって削除される範囲と、追加されるテキストの長さを計算する時に、改行文字をCRLFの二文字として計算していなかったことでした。

これにより、改行を挿入した際に、本来なら二文字追加されていると、TSFに通知しないといけないところを、一文字だけ追加された、と通知していたわけです。しかし、選択位置を調べて見ると、それとはまた違った、一文字分、後ろのオフセットが返されるという矛盾した状態になっていました。

TSFに変更箇所を通知する際に、きちんと、改行文字をWindowsでのみ二文字としてカウントするように修正しています。