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

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

もずはっく日記(2013年3月)

2013年3月15日

Firefox上のFlash Playerで、保護モードが有効でもIMEが利用できるようにする方法 初回投稿日時: 2013年03月15日20時33分51秒
最終更新日時: 2013年03月19日14時26分00秒
カテゴリ: Firefox Flash Mozilla Core plugin Windows
固定リンク: id=2013031500
SNS: (list)

最初に断わっておきますが、これはユーザがどうこうする話ではなくて、Web開発者向けの話です。ただ、利用しているサイトでこの問題で困っていて、保護モードを切るのに抵抗がある方は、このエントリをそのサイトの運営者の方に報告してください。ひょっとすると対策をとってくれるかもしれません。

ではまず、この問題のおさらいですが、Windows版のFlash PlayerはFirefox上で動く場合にのみ、保護モードという、内部処理を権限が制限されたプロセスで動かすモードを有効にして動いてることは、これまで何度かここに書いてきました。このモードの最大の問題は、WindowsのIMEというのは、利用しているアプリの一部としてロードされ、動作するため、制限されたプロセス内でロードされてしまうと、変換エンジンが別プロセスの場合、そのプロセスと通信できなかったり(Google日本語入力のケース)、かな入力を行うためのカナロックを自動的にかけることができなかったり(Google日本語入力以外と、ATOK 2013で『自動カナロックの詳細設定』ダイアログで『ATOKの内部状態を優先する』にチェックを入れていない場合)、といった、IME自体にまで動作制限がかかってしまうことがその原因でした(この件に関しては、Windows版Flash Player 11.3と各IMEの動作確認表 (もちろん非公式)も参照してください)。

ユーザ側での対抗策として、これまでは保護モードを無効にすることを紹介していましたが、今回、テスト中にたまたま見つけた、Webサイト側でとれる対策を紹介します。その対策とは実にシンプルで、param要素で、wmodeopaqueか、transparentに指定し、Flash Playerをwindowlessモードで動かすことです(windowedモードと、windowlessモードの違いについては、Windows版Firefoxのwindowed modeとwindowless modeそれぞれのプラグインの構造を参照してください)。

以下、実例です。Flashコンテンツを作るツールを持っていないので、bugzilla-jpに添付されていたswfファイルを拝借してきました。実際に、Webコンテンツを作られている方は、wmodeを指定するparam要素を挿入するかどうかは、UA名にFirefoxが含まれているかどうかWindows版かどうか、この二点を確認した上で挿入した方が良いでしょう。一般的に、windowlessモードは各ブラウザの実装のバラツキが大きいためです。

wmode指定なし (windowedモード)
wmode opaque (windowlessモード)
wmode transparent (windowlessモード)

なぜこれで、IMEに関する互換問題が一掃されるのか、その理由は簡単です。

windowedモードでは、Flash Player自身がネイティブウインドウをFirefoxの上に保護モードで作り、このウインドウがフォーカスを持ち、入力されたメッセージを保護モードで処理します。このため、IMEは保護モード下で動作し、上述の制限を受けることになります。

これに対し、windowlessモードでは、Flash Playerのプロセスは自分自身でウインドウを生成しません。Flashの内容が、Firefoxのウインドウ上に、直接描画されるモードだからです。このため、IMEのイベントを受け取るのはFirefox自身のウインドウになります。これはつまり、IMEは保護モードの外、Firefoxのプロセス内で動作します。このため、通常の権限でIMEが動作することになります。Firefoxは、受け取ったIMEのイベントを、内部処理専用のDOMイベントを利用して、フォーカスを持ったwindowlessプラグインのプロセスへ渡します。これにより、IMEがFlash上でも使えるのです。

ただし、一つだけ、このwindowlessモードには欠点があります。それは、on-the-spotでIME経由で入力できないということです。つまり、未確定文字列が、点滅するキャレットの位置に描画されません。これは、Windowsでは、アプリ自身が未確定文字列を描画する場合や、候補ウインドウ位置を指定する時にはWindowsのAPIを通じて、動作中のIMEのコンテキストにアクセスしなければいけないのですが、別プロセスのコンテキストにアクセスすることはセキュリティ上、できないため、このようなことになります。

この件に関しては、そのうちに、プラグインの描画領域のすぐ下にでも表示されてマシになるように、対策はとりたいとは考えています。

2013年3月27日

Bug-org 558976 IME related methods of nsIWidget should be merged 初回投稿日時: 2013年03月27日18時55分51秒
カテゴリ: Mozilla Core Mozilla22 バグ修正
固定リンク: id=2013032700
SNS: (list)

GeckoのXP部分のソースコードは、nsIWidgetの様々なメソッドを使って、ネイティブのIMEにリクエストを出したり、通知したりしていますが、今までは、それぞれのためにメソッドを追加する、という形をとっていました。しかし、これでは、ただでさえ巨大化しているnsIWidgetをますます肥大化させる可能性があり、メンテナンスの際にもインターフェースの変更が必要な分、super reviewの手間がかかります。これでは、非常に効率が悪いので、今回、整理を行いました。

新たに、nsIWidget::NotifyIME(mozilla::widget::NotificationToIME aNotification)というAPIを追加しました。aNotificationで、リクエストの内容や、通知内容を指定します。

NOTIFY_IME_OF_CURSOR_POS_CHANGED

エディタに未確定文字列が無い場合に、nsIEditorIMESupport::forceCompositionEnd()が呼ばれた場合にのみ、呼び出されます。これは、従来のnsIWidget::ResetInputState()の本来の機能を置き換えます。

REQUEST_TO_COMMIT_COMPOSITION

エディタに未確定文字列がある場合に、これを強制確定する際に呼び出されます。nsIWidget::ResetInputState()のモダンな実装を置き換えます。

REQUEST_TO_CANCEL_COMPOSITION

エディタに未確定文字列がある場合に、これを破棄する際に呼び出されます。nsIWidget::CancelComposition()を置き換えます。

NOTIFY_IME_OF_FOCUS

エディタがフォーカスを得た場合に呼び出されます。nsIWidget::OnIMEFocusChange(true)を置き換えます。

NOTIFY_IME_OF_BLUR

エディタがフォーカスを失った場合に呼び出されます。nsIWidget::OnIMEFocusChange(false)を置き換えます。

NOTIFY_IME_OF_SELECTION_CHANGE

エディタ内のキャレット位置、もしくは選択範囲が変更された場合に呼び出されます。nsIWidget::OnIMESelectionChange()を置き換えます。

REQUESTというネーミングから分かるように、実際に、これらが動作するかは、ネイティブIMEのAPIに依存します。

置き換えられたメソッドは全て、削除され、すっきりとしています。

また、新しいメソッド名にあわせて、nsIWidget::OnIMETextChange(uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd)は、nsIWidget::NotifyIMEOfTextChange(uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd)にリネームしています。

Bug-org 790516 [TSF] Should dispatch DOM events when document is unlocked 初回投稿日時: 2013年03月27日19時21分07秒
カテゴリ: Mozilla Core Mozilla22 TSF Windows バグ修正
固定リンク: id=2013032701
SNS: (list)

TSFのドキュメントロックが、仕様通りに動かないのでデザインを大幅に見直しました。

TSFは、TIP (IME)がアプリにアクセスを行う際に、ITextStoreACP::RequestLock()を呼び出し、アプリ自身や、他の何らかのテキストサービスが、そのコンテンツを変更できないようにロックを行ってから処理を開始します。

ITextStoreACPの実装側(GeckoではnsTextStore)は、ロックを受け入れることができるなら、ITextStoreACP::RequestLock()内でITextStoreACPSink::OnLockGranted()を呼び出します。TIPは、この、OnLockGranted()が呼び出されている間が、ロックが実際に実行されている、という状況になりますので、アプリ側から見ると、OnLockGranted()を呼び出している最中に、文字列を変更するメソッドが呼ばれたり、キャレット位置を変更するメソッドが呼ばれたり、コンテンツの内容を調べるメソッドが呼ばれたり、コンテンツ中の特定の文字の画面座標を調べるメソッドが呼ばれたりします。

従来のnsTextStoreは、各種メソッドがTSFから呼び出された時に、逐一、DOMイベントを発行して、エディタの内容に反映し、最新のコンテンツの情報を調べて返す、という実装でした。つまり、OnLockGranted()の最中に、DOMイベントが発生するため、そのイベントハンドラから、エディタの内容や、フォーカスの移動といった、何らかの変更が可能で、実際にはロックができていない状態でした。このため、実際にそのような状況が発生すると、TIPが混乱してしまうということがありました。

今回の修正で、nsTextStoreは以下のように修正されました。

まず、未確定文字列の変更等、ドキュメントの変更に関するアクションが呼ばれた場合、アクションという形で記録だけを行い、OnLockGranted()から処理が戻ってきてから、ため込んだアクションから、DOMイベントを一気に発行するようになりました。

そして、最新のコンテンツの情報は、アクションがペンディング状態になっている時には取得できませんので、ロック後、最初に、最新のコンテンツ情報や、選択位置の情報をキャッシュし、各アクションが記録される際に、このキャッシュしたコンテンツのみを更新して、TSFにはこの情報をもとに応答する、という形をとっています。

また、コンテンツの任意の文字の位置情報だけはどうしようもありませんので、レイアウトがまだ完了していない、というエラーを発行するようにし、記録されたアクションを全て実行した直後に、TSFに対して、レイアウトの計算が終了した旨を伝えるようにしています。

今回の修正により、まず、コンテンツの情報は、一回のロックにつき、一回しか取得しにいかなくなりましたので、CPUパワーの必要な、クエリイベントを利用を大幅に削減しました。これにより、軽快に動作するようになっています。

また、選択位置の変更と、文字列の変更の合間でクエリが行われた場合に対応するための中途半端な、コンテンツ情報のキャッシュコードをごっそりと削除することができました。

そして余分なtextイベントを発行して、未確定文字列がちらついてしまうのを阻止するために、最後に発行したtextイベントを記録し、比較するコードの削除もできました。

これにより、かなり、壊れにくい、安定した、読みやすいコードに生まれ変わったと言えます。

ただし、この修正が入った今でも、RequestLock()中にDOMイベントが発生することになりますので、そのハンドラにより、何らかの変更が行われた場合に、TSFにそれを通知する手段がありません。これはまた、別のバグで対応予定ですが、XP側に相当量の変更が必要なので、今年中に取りかかれるかすら、見通しが立っていません。

Bug-org 829952 Scrolling using some high-resolution-scroll aware touchpads feels slow 初回投稿日時: 2013年03月27日19時30分30秒
カテゴリ: Mozilla Core Mozilla22 Windows バグ修正
固定リンク: id=2013032702
SNS: (list)

高解像度スクロールに対応した、一部ノートPCのタッチパッドでは、スクロールが非常に遅く、解決策としてGoogle Chromeに逃げ出してるユーザが多いぞ、というバグです。

D3EのWheelEventの実装時に、当然、システムのスクロールスピードがカスタマイズされていない場合に、ルートのスクロール速度だけ倍にする、という機能の部分にも手を入れたのですが、その変更時に、高解像度スクロールに対応した環境では、カーソルの加速に対応しており、この機能が逆に邪魔をしてしまう、という判断から、高解像度スクロールに対応していない環境でのみ、速度を倍にする処理を呼び出していました。

しかし実際には、高解像度スクロールに対応はしていても、ノートPCのタッチパッドだと、カーソルの加速には対応していない環境が多いようで、システム設定の倍の量、スクロールしてしまうGoogle Chromeに比べて「遅い」ブラウザだと感じる人が多数居るようでした。

今回の修正で、D3E WheelEventの実装前の状態になっていることになりますので、これによるregressionは無いとは思うのですが、システム設定を無視するこの機能が不要な方は、mousewheel.system_scroll_override_on_root_content.enabledfalseに変更してください。

行儀の悪いアプリの方が評価される、この状況、どうにかならんものかといつも思うんですけどね。

Bug-org 807241 [TSF] Should use ITfMessagePump and ITfKeystrokeMgr 初回投稿日時: 2013年03月27日19時49分24秒
カテゴリ: Mozilla Core Mozilla22 TSF Windows バグ修正
固定リンク: id=2013032703
SNS: (list)

WindowsのIMEのAPIセットが、IMMからTSFへの移行するにあたり、TSFにネイティブ対応していないアプリのために、IMM-TSFのエミュレーション機能である、CUASと呼ばれるシステムが、通常のアプリの場合は動作しています。これは、メッセージをフックし、様々な処理を自動で行い、必要があればIMMのメッセージを発行したりしています。

ITfMessagePumpは、PeekMessageW()メソッドを持っていて、これを利用すると、通常のPeekMessageW()よりは生のメッセージキューからメッセージを取得し、処理を行うことができます。

TSF対応アプリが、従来からあるPeekMessageW() APIを利用してメッセージを取得した際に、CUASが先にメッセージをTSFに渡してしまい、もし、キーイベントが処理されてしまった場合、アプリはキーイベントの発生自体が分からなくなっていることに、テスト中に気付きました。

これでは、D3Eの仕様書で定義されている、keydownイベントと、keyupイベントを、未確定文字列がある場合でも発行する、という仕様に従うことができないので、その準備として今回の修正を行いました。

これにより、TSFモードの場合、PeekMessageW()の呼び出しが、virtual callになってしまうので、かなりパフォーマンスが悪くなっているのではないかと思われます。色々と検討してみたんですが、virtual callを阻止して、直接実装されているメソッドを呼び出す方法が思いつきませんでしたので、今のところ、必要悪なパフォーマンス低下になっています(IMMモードでは、従来通り、CのPeekMessageW()を呼び出すので、パフォーマンスに変化はありません)。

また、この修正により、CUASが自動でキーイベントをTSFに渡してくれなくなりますので、ITfKeystrokeMgrを利用して、nsTextStore自身がTSFにキーイベントを通知するように修正しています。

Bug-org 852024 [TSF] MetroWidget hasn't implemented GetIMEUpdatePreference() 初回投稿日時: 2013年03月27日19時57分07秒
カテゴリ: Mozilla Core Mozilla22 TSF Windows バグ修正
固定リンク: id=2013032704
SNS: (list)

Metroアプリ版のFirefoxでは、IMMは利用できないので、TSFモードで動作しますが、Metro版用のnsIWidgetの実装である、MetroWidgetnsIWidget::GetIMEUpdatePreference()の実装を忘れていたため、IMEを利用して日本語を入力しようとすると、常に、フォーカスをあわせた際のキャレット位置に文字が挿入される、という状態になっていました。

この原因は、nsIWidget::GetIMEUpdatePreference()が適切に実装されていないため、nsIWidget::NotifyIME(NOTIFICATION_TO_SELECTION_CHANGE)や、nsIWidget::NotifyIMEOfTextChange()が一切呼び出されず、TIPにキャレット位置や、テキストが変化したことを通知できていない、ということでした。

それにしても、このレベルのバグがあったことから、日本人は全然、Metro版Firefoxをテストしていないのが露呈しちゃってますね(そもそもMetro版の完成度低くて使えないってのはありますが)。

Bug-org 849647 It's better to remove message order optimization on Windows if it's possible 初回投稿日時: 2013年03月27日20時10分36秒
最終更新日時: 2013年03月27日20時10分57秒
カテゴリ: Flash Mozilla Core Mozilla23 Windows バグ修正
固定リンク: id=2013032705
SNS: (list)

Windows版Geckoのメッセージループは、開発者には有名な話ですが、独自のメッセージ取得順序の最適化を行っています。本来は、SendMessage()で送信されたメッセージ、PostMessage()で送信されたメッセージ、SendInput()等で発生した入力に関するメッセージ、といった感じになっているのですが、Geckoは10年以上前から、入力処理を最優先で処理するようになっていました。

その理由は、古いFlash Playerが、描画の更新のために、WM_USER + 1というメッセージを大量にメッセージキューに投げてくるため、入力イベントの処理がスムーズにいかない、というものがあったようですが、現在のFlash Playerではこの問題は無くなっています。

また、このメッセージの最適化により、ATOKをオンにした直後の入力イベントが、ATOKが処理する前に、Geckoに取得され、最初の何文字かが半角英数のまま入力されてしまう、というバグが、TSFモードでは再発していました。

こういった理由から、もはやこのメッセージ順序の最適化を行い続ける理由はないだろう、ということで、私のハック史上、最も影響範囲の広大な、この修正を行うことになりました。

現在、Mozilla 22向けのmozilla-centralにパッチは投入されていますが、これほどリスキーな変更をNightlyで一週間しかテストしないのは危険なので、Mozilla 22がAuroraになった時点で、ひとまず、Auroraからバックアウトすることを予定しています。そのため、最初にこの修正が入るリリースビルドは23になると思われます。

この処理の副作用として、メッセージの取得順序最適化の際に大量に呼び出していたPeekMessageW()の呼び出しが大幅に削減されていますので、特にvirtual callでこのAPIを呼ばなくてはいけないTSFモードで、パフォーマンスが改善されています。

Bug-org 840837 test_imestate testing for MozIMEFocusOut is broken 初回投稿日時: 2013年03月27日20時15分17秒
カテゴリ: Mozilla Core Mozilla22 バグ修正
固定リンク: id=2013032706
SNS: (list)

test_imestate.htmlというchromeの自動テストが、isnot()と書くべき所を、typoにより、is_not()と書いてしまっている、というバグです。

現在、mochitest-chromeはJavascriptにエラーがあっても、そのままテストが完走してしまうんだとか。

で、単に、この名前だけを修正しても、エラーが出てしまうので、修正依頼が、原因作った私のところへ来ていました。

幸い、エラーはテストのバグであり、Gecko側のバグではありませんでしたので、テストのみを修正しました。

2013年3月28日

Bug-org 848672 [TSF] Redesign focus and IME state handling of nsTextStore 初回投稿日時: 2013年03月28日07時24分48秒
最終更新日時: 2013年03月28日09時14分59秒
カテゴリ: Mozilla Core Mozilla22 TSF Windows バグ修正
固定リンク: id=2013032800
SNS: (list)

以前、widget::IMEHandlerを整備することで、IMMモードと、TSFモードのコードの完全分離を図りましたが、IMEの有効・無効状態が、nsTextStoreのコードでは不十分で、エディタにフォーカスが無い場合に無効化することができなかったので、IMMのコンテキストでの操作を続けていました。このバグの修正目標は、これをやめ、nsTextStoreのみで無効化できるようにするところにありました。

このバグの修正にあたっては、Chromiumのコードを参考にさせてもらったり、NyaRuRuさんの助言を参考にさせてもらったりと、地味ながら、エコシステム素晴らしい、みたいな修正になりました。

キーボード無効化のため、nsTextStoreのインスタンスが管理するITfDocumentMgr以外に、もうひとつ、別のITfDocumentMgrを作成し、これ用に作成したITfContextで、キーボードを無効化し、さらに、ITextStoreACPを実装しなくても良いように、空のコンテキストであると、マークし、このITfDocumentMgrが、エディタがフォーカスを持たない場合にフォーカスを持つように修正しました。

しかし、これだけでは、不十分で、不意にIMEが有効化されてしまう状況があることに気付きました。その時のログを読んでみると、nsIWidget::SetInputContext()が呼び出されて、IMEの状態は変更されるものの、nsIWidget::NotifyIME(NOTIFY_IME_OF_FOCUS)や、nsIWidget::NotifyIME(NOTIFY_IME_OF_BLUR)が呼び出されていない、という状況が確認できました。この際に、SetInputContext()は、FOCUS_NOT_CHANGEDで呼び出されていることが確認できましたので、その際には、SetInputContext()が、フォーカス移動をエミュレートするようにして解決しています。

また、エディタがフォーカスを持つ間だけ、ITfThreadMgr::AssociateFocus() APIで、フォーカスを持つウインドウハンドルをTSFに登録するようにしました。この修正により、入力している最中に、Firefoxのプロセスがビジー状態になった時に、フォーカスが一時的に失われ、その後、復帰した時に、on-the-spotで未確定文字列を入力できなくなる、というバグが修正されています。