2015年10月2日
Flash Playerのせいで犠牲になっている、Firefoxの軽い動作を取り戻しましょう
初回投稿日時: 2015年10月02日22時33分45秒
カテゴリ: Firefox Flash Mozilla Core 雑談
固定リンク: id=2015100200
SNS:
(list)
Firefoxの動作が遅い
、よく聞く苦情です。その原因のうち、かなりの割合は以前から指摘しているようにパフォーマンスを犠牲にしたアドオンをインストールしていることに起因していると思われます。ですが、もう一つ、大多数の人が無駄にコストを払っているであろう原因があります。それは、Flash Playerです。
Flash Playerが必要なコンテンツがページ内に存在している場合に、Flash Playerをロードするためのplugin-container.exeや、Flash Player内部での保護モード(Windowsのみ)のためのプロセス間通信等、非常に多くの時間、CPUを占有してしまいます。これが、Webページ読み込み時の「重たさ」の原因の一つとなっています。
しかし、意外とこれに対して対策をきちんととっている人は少ないのではないでしょうか?
また、自分のアクセスしているWebサイトでは動画プレイヤーやブラウザゲームぐらいでしかFlash Playerが利用されていないので関係無いと思ってはいないでしょうか?
ここで紹介する設定を行うと驚くと思いますが、思わぬページで"見えない"Flash Playerが存在して、Firefoxのパフォーマンスを落としているのです。しかし、Flash Playerは依然として一部のWebサービスのメインコンテンツとして重要なポジションを確立していますので、もちろん、完全にアンインストールしたり、無効化したりすることは現実的な解決策ではありません。そこで、Firefoxに搭載しているClick-to-Playを利用します。
まず、パネルにある「アドオン」をクリックします。
続いて、表示されるアドオンマネージャの左側のペインで「プラグイン」を選択し、「Shockwave Flash」という行にある「常に有効化する」をクリックし、「実行時に確認する」に変更します。
この状態でFlash Playerを利用しているWebページにアクセスすると、以下のような通知バーがページ上部に表示されます。
そのWebサイト全体でFlash Playerが不要な場合、「ブロックを継続」ボタンを押すと、そのサイトへアクセスした時にこの通知バーが表示されることはありません。
そのWebサイトでFlash Playerを表示する必要がある場合、「許可...」ボタンを押します。すると、次のダイアログがURLバーの左端下に表示されます。
ここにある「常に許可する」を押すと、そのWebサイトでは今まで通り、Flash Playerは常に自動的に実行されるようになります。
「今だけ許可」を押すと、Flash Playerが実行されますが、次回以降にアクセスした場合にはまた問い合わせが表示されます。Flash PlayerがそのWebサイトの機能の一部で必要なものの、毎回必要というわけでもなく、その頻度も低い場合はこちらを選択しておく方が良いでしょう。
この設定はユーザにとって不要なFlash Playerを利用している多くのWebサイトで効果が絶大です。ハングアップやクラッシュ、さらには、セキュリティホールの温床となっているFlash Playerを必要な(逆に言えば信頼しているサイト)以外で無効化させつつ、Flash Playerの利用自体も可能なこの機能、是非、利用してみてください。
2015年10月31日
contenteditable
でEnterキーを押した時の動作には2種類あって、<div contenteditable>foo</div>
というエディタ上では<br>
要素が挿入し、キャレットのその後ろに移動させますが、<div contenteditable><p>foo</p></div>
というエディタで(<p>
要素内にキャレットがある時に)は新しい<p>
要素を生成して、その内部にキャレットを移動させます。このバグは後者の場合に、Enterキーを押した直後、つまり空の<p>
要素でハングル文字を入力しようとすると、ひとつ前の<p>
要素の最後に入力されてしまうというバグです。
まずそもそも何故このようなことが発生しているのかというと、ハングル用のMS-IMEは非常に特殊な挙動で未確定文字列の入力開始を通知してきている点にありました。普通のTIPは、ITfContextOwnerCompositionSink::OnStartComposition()
を呼び出して、明示的に未確定文字列の編集が始まったことをアプリに通知してから、文字の入力や、選択範囲の設定を行います。それに対してハングル用のMS-IMEは、先にITextStoreACP::InsertTextAtSelection()
を呼び出し、最後にITfContextOwnerCompositionSink::OnStartComposition()
をその時に期待される選択範囲と共に呼び出します。
このような順序のため、TSFTextStore
は、まずテキストの挿入が未確定文字列の編集無しに行われ、その後、別の範囲をターゲットに未確定文字列の編集が開始されたと考えていました。ですので実際に、最初の編集に対してcompositionstart
、compositionupdate
、compositionend
を送信した後、compositionstart
が再度送信されるという動作になってしまっていました。
そして、何故、挿入位置がずれてたかというと、現在、GeckoがDOMツリーからIME APIに対して渡すコンテンツの内容は<br>
要素のみを改行文字に変更したプレーンテキストになっています(ContentEventHandler
で生成)。このため、<p>foo</p><p></p>
というDOMツリーでは、空の<p>
要素内にキャレットがある場合のオフセットと、最初の<p>
要素の末尾(つまり、foo
テキストノードの末尾)のオフセットが同じになってしまいます。未確定文字列が指定された範囲で変換が開始されるこのケースでは選択範囲を再設定しなくてはいけませんが、選択範囲をContentEventHandler
を利用して設定した場合、常に、後者の位置が優先されてしまうため、このようなバグの発生につながっていました。
今回の修正では、未確定文字列の編集が開始される際に、直前に行われてキューに入っている内容を確認し、直前の内容がITfContextOwnerCompositionSink::OnStartComposition()
の指定してきた範囲に直接文字を挿入していた場合、compositionstart
、compositionupdate
、compositionend
をキューから取り除き、新たにcompositionstart
とcompositionupdate
をあるべき形でキューに入れ直すことでContentEventHandler
側のオフセットの矛盾問題に触れないように修正しました。
エディタか、その祖先の要素がCSSのtransform
プロパティで移動や変形されている場合、IMEの候補ウインドウがその変形を適用前の座標に表示されてしまうというバグです。TweetDeckで新しいツイートを入力する際に候補ウインドウ位置がずれるのはこのバグが原因です。
以前、Army of Awesomeでこのバグを知ったときには適切なAPIが内部になく、修正が簡単では無さそうだったので放置して忘れてしまっていましたが、nsLayoutUtils
にこれを解決するAPIが用意されていたので、今回、修正を行いました。
e10sモードでは、windowlessモードで動作するFlash Player上でIMEを使用した場合、未確定文字列が昔のように画面左上に表示されていました。これを現在の非e10sモード時のように、プラグインコンテンツの左下に表示すべきというバグです。
今回の修正により、IMEContentObserver
はプラグインがフォーカスを持っている間にも生成されるようになりました。ただし、通常の各プラットフォーム用のwidgetはプラグインがフォーカスを持つ場合にはどの通知も要求しませんので、非e10sモードでは存在するだけという形になります。それに対してPuppetWidget
はレイアウトや位置変更の通知のみを要求します。これにより、ContentCacheInParent
にエディタの矩形(プラグインがフォーカスを持つ場合にはプラグインコンテンツ全体の矩形)がキャッシュされるようになりましたので、IMMHandler
がこのキャッシュを利用できるようになり、非e10sモードの時と同様に動作するようになっています。
Windowsでは、windowlessプラグインに対してWM_MOSUEWHEEL
メッセージや、WM_MOUSEHWHEEL
メッセージが送信されていないため、windowlessプラグイン上でマウスホイールが使えないというバグです。
調べてみると、そもそも、WidgetWheelEvent
にネイティブのメッセージをそのまま挿入していませんでした。しかし、単純にこれを行えば修正できるという話でもありません。なぜならGeckoは高解像度スクロールに完全に対応しているため、WidgetWheelEvent
をWM_MOSUEWHEEL
やWM_MOUSEHWHEEL
を受信した時に必ず送信している訳ではないからです(小数の端数による問題のため)。また、Flash Playerやその他のプラグインも高解像度スクロールに完全に対応しているのか怪しいという点もあります。少なくとも昔、Logitechのエンジニアからの要請でGeckoに高解像度スクロールを実装した際にテストした時には、当時のFlash Playerは非対応でした。
そこで、今回はトリックを使って(この点に関しては)簡単に実装してしまうことにしました。
WidgetWheelEvent
には、DOMMouseScroll
イベントの発火に利用するためのlineOrPageDeltaX
とlineOrPageDeltaY
というメンバがあります。これらは、それぞれの方向へのスクロール量が整数で表現できるようになった時にだけ値がセットされます。例えばWindowsの場合、0.5行ずつのWM_MOUSEWHEEL
メッセージが連続して来た場合、最初のWidgetWheelEvent::lineOrPageDelatY
は1で、二つ目は0、三つ目は再び1、となり、delta値の合計とつじつまが合うようになっています。
これを利用し、この値からWM_MOUSEWHEEL
メッセージやWM_MOUSEHWHEEL
メッセージのwParam
を計算することで常に、整数行のスクロールが発生するデルタ値のメッセージをwindowlessプラグインに対して送信するようにし、互換性問題を低くしています(ひょっとするとWHEEL_DELTA値で送信しないとスクロールが早すぎるという古くて、当時でも間違っていた実装方法のままのプラグインもあるかもしれませんが……)。
ただし、この修正だけだと、マウスホイールでページ全体をスクロール中にwindowlessプラグインがカーソルの下へ移動してくると、全てのマウスホイールイベントをそのwindowlessプラグインに消費されてしまいます。つまり、スクロールが止まってしまうというバグが発生します。これを防ぐために、windowlessプラグインにマウスホイールのメッセージを送信するのはeWheel
イベントのデフォルトアクションという扱いにすることにしました。これにより、マウスホイールトランザクションが活用可能になりますので、直前のスクロールが優先されることになります。
ただし、マウスホイールトランザクションで救えないケース、例えばwindowlessプラグイン上でマウスホイールの操作を開始した場合には、そのwindowlessプラグインが実際にホイールを処理しているか否かに関わらず、そのプラグインに消費されてしまいます。つまり、見た目からページ全体のスクロールを期待しているとバグっているように見えます。しかし、これは別プロセスで動くwindowlessプラグインからホイールの処理が必要だったかどうか、その結果を受けとる仕組みが存在しないので解決不能な問題です。
Bug-org 1208043で報告されているバグが、TavultesoftKeyman90とTavultesoftKeyman80というテキストサービスを利用したTIPでも再現するというバグです。
Bug-org 1208043は前述の通り修正済みですが、Betaである42では万全を期すため、報告されているTIPがアクティブな場合のみ、新しい動作を行うようにしていました。そこで、このバグではアクティブなTIPが問題となっているテキストサービスを利用している場合にも修正が適用されるようにホワイトリストへの追加を行いました。
e10sモードが有効になっているNightlyではAPZCがデフォルトで有効になっていますが、この場合、Bug-org 376679の修正がAPZCによって無視され、windowlessプラグインにホイールイベントが伝達されません。
単純にAPZCにイベントが伝達された後にもwindowlessプラグインにホイールイベントを渡すように修正しても、windowlessプラグイン、ページ全体共にスクロールする、ダブルアクションな状況になってしまったので、APZCの担当であるMarkus Stange [:mstange]に聞いてみたところ、APZCはDisplayListからホイールイベントを消費する可能性のある要素の範囲を予めキャッシュしておくことでスクロールの非同期をスムーズに行っているので、そのチェックの際にwindowlessプラグインも対象として含めないといけないとのことでした。
似たような修正をされる方はこのバグに添付したパッチを参考にしてみてたください。
Bug-org 1208043の解説記事で述べたように、現在のContentEventHandler
の仕様には問題があります。そして、ハングル用のMS-IMEにTSFモードで完全に対応するにはこの問題を完全に解決する必要があることが別のバグ報告から分かりました。そのため、このバグで先行して、ContentEventHandler
のcontenteditable
エディタがフォーカスを持っている場合の挙動のテストを大量に追加しました。
このテストで分かりましたが、エッジケースでは特にバグが多々存在しています。
ContentEventHandler
の修正をしていて発見したnsContentIterator
のバグです。NS_NewPreContentIterator()
でインスタンスを生成した場合、指定した範囲のルートとなる要素から最初の葉ノードまで列挙していく、開始タグを列挙する動作になりますが、この場合に指定した範囲の最後のノードが子ノードを持たず、ノード内のオフセットが0の場合に、その要素自身を列挙してしまうというバグです。
例えば、<p>[<br>]<br></p>
の"[]"の選択範囲を表現しようとした場合、開始位置が最初の<br>
要素のオフセット0、終了位置が二つ目の<br>
要素のオフセット0という形もあり得ます。この場合、最初の<br>
要素のみが列挙されるべきという話です。
ContentEventHandler
の修正をしていて発見したnsContentIterator
のバグです。NS_NewPreContentIterator()
でインスタンスを生成した場合、指定した範囲のルートとなる要素から最初の葉ノードまで列挙していく、開始タグを列挙する動作になりますが、この際、指定した範囲の開始位置が空要素のオフセット0だと、その開始要素を無視してしまうというバグです。
例えば、[<br>text]node
の"[]"部分を指定したい場合、開始位置が<br>
要素のオフセット0、終了位置がtextnode
のオフセット4というケースがありますが、この場合、textnode
のみが列挙されていました。
Windows XPのMS-IMEが、一文字目のかなを入力した時点でハングアップしたり、Firefoxがクラッシュするというバグです。Aliceさんが報告してくれた上に、regression範囲を調査してくれたところ、奇妙なことにその原因となっているのはnsIMM32Handler
をmozilla::widget::IMMHandler
にリネームしたバグでした。
さらにAliceさんがその時のログを提出してくれたおかげで、IMR_DOCUMENTFEED
の結果を返したところでハングアップが発生しているところまで分かりました。そこで注意深くソースを見てみると、MOZ_LOG()
内の不要な\n
を削除した置換の際に、誤って、\n
を検索しているコードまで置換してしまっていることに気付きました。そのため、空文字列を検索して(どのような数値が返ってきているのか分かりませんが)、その奇妙な値のせいでその後の処理がおかしくなってしまっていました。
ラッキーなことに、ギリギリで、42のRCビルドに入れてもらうことができました。
Bug-org 1189396と同じ話のe10sモード版です。
e10sモードでは、compositionstart
イベントハンドラでコンテンツの変化が発生した場合、その選択範囲の変化通知が未確定文字列が存在している時点で(未確定文字列が存在する前の)通知を受け取ることになります。この場合、GTK版とWindowsのTSFモードでは、未確定文字列を強制的に確定していました。
しかし、そのためにFacebookという有名サイトで問題が発生していたため、TSF版はオフセットが変化している場合には挙動がおかしくなってしまうものの、ひとまず問題が無いように強引に修正しています。
今回は単純に、選択範囲の変化が未確定文字列がエディタ上に存在しているかそうではなかったかを示すフラグを新たに追加し、未確定文字列が存在する場合に、未確定文字列が存在していなかった時の選択範囲の変更通知を受け取った場合に強制確定を行わないように修正しました。