nsIPopupBoxObject::setConsumeRollupEvent (2)

前の記事に書いた時点では、 Firefox 3 で新たに追加された nsIPopupBoxObject::setConsumeRollupEvent メソッドは Windows でのみ動作し、 Linux と Mac OS X では効果が無かった。

Linuxのその後の状況

1月11日にLinuxで動作しない件についてバグを立てた。
Bug 411903 – nsIPopupBoxObject#setConsumeRollupEvent has no effect on Linux
1月20日頃、以下のバグへのパッチ適用に伴い、Linuxでもめでたく正常動作するようになった。
Bug 188126 – gtk2: autocomplete/popup widgets should not block clicks outside of themselves

Mac OS Xのその後の状況

2月3日にMac OS Xで動作しない件についてバグを立てた。有識者による確認待ち。
Bug 415440 – nsIPopupBoxObject#setConsumeRollupEvent has no effect on Mac OS X
Neal Deakin 氏によると、 Mac OS X では単純にまだ実装されていないだけのようだ。

TOP

nsIPopupBoxObject::setConsumeRollupEvent

ポップアップを開いた状態でポップアップの外側のどこかをクリックしたときの挙動は、ウィジェットやプラットフォームに依存する。わかりやすい例として、コンテントエリア内で右クリックメニューを開き、そのままページ内のどこかのハイパーリンクをクリックすると、

プラットフォーム 挙動
Windows ポップアップが閉じ、リンク先へ遷移する
Linux ポップアップが閉じるだけ
Mac ポップアップが閉じるだけ

これに対して、検索バーの検索エンジンのドロップダウンリストを開いた状態で、コンテントエリア内のハイパーリンクをクリックしたときの挙動は、プラットフォームによらずドロップダウンリストが閉じるだけで、リンク先へは遷移しない。

このように、ポップアップの外側をクリックしたときにポップアップを自動で閉じる(ロールアップする)だけで、実際にクリックした何かに対するクリックイベントが発生しないような挙動のことを、「クリックの横取り」と呼ぶ。クリック横取りに関する動作仕様の詳細は、 nsMenuPopupFrame::ConsumeOutsideClicks のソース内に記載されており、通常は以下のルールに従った挙動となる。

ウィジェットの種類 ポップアップの親要素 挙動
メニュー <menu> か <popupset> Windows: クリックの横取りなし
Linux / Mac: クリックの横取りあり
オートコンプリート <textbox type=”autocomplete”> プラットフォームによらず、クリックの横取りなし
コンボボックス <menulist> プラットフォームによらず、クリックの横取りあり

Firefox 3 では新たに追加された nsIPopupBoxObject::setConsumeRollupEvent メソッドを使って、クリックの横取りの動作仕様を無理やり変更できるようになった。このメソッドの引数は3種類あり、0(デフォルト、ウィジェット・プラットフォーム依存)、1(クリックの横取りを強制的にありにする)、2(クリックの横取りを強制的になしにする)の3種類が指定可能である。実際に試すために以下の手順を試す。

  1. テストケースを開く
  2. [ROLLUP_DEFAULT] ボタンをクリックしてポップアップを開く
  3. ポップアップを開いたまま [TEST] ボタンをクリックする
  4. [ROLLUP_CONSUME] ボタンについても同様に手順2~3を繰り返す
  5. [ROLLUP_CONSUME] ボタンについても同様に手順2~3を繰り返す

すると、 Windows + Firefox 3.0b3pre では期待通り以下の結果が得られたものの、 Linux / Mac + Firefox 3.0b3pre では期待に反した結果となった。

最初に押下するボタン プラットフォーム [TEST] ボタン押下後の挙動
[ROLLUP_DEFAULT] Windows ポップアップが閉じるだけ
Linux ポップアップが閉じるだけ
Mac ポップアップが閉じるだけ
[ROLLUP_CONSUME] Windows ポップアップが閉じるだけ
Linux ポップアップが閉じるだけ
Mac ポップアップが閉じるだけ
[ROLLUP_NO_CONSUME] Windows ポップアップが閉じてダイアログが表示
Linux ポップアップが閉じるだけ
Mac ポップアップが閉じるだけ

どうやら Linux / Mac では nsIPopupBoxObject#setConsumeRollupEvent の引数に ROLLUP_NO_CONSUME を渡したときの効果が何も無いようなので、 Bugzilla へバグを立ててみた。ここまで自分が述べたことに確信を持っているわけではないので、有識者の判断を待つこととする。

Bug 411903 – nsIPopupBoxObject#setConsumeRollupEvent has no effect on Linux

関連記事

Firefox 3 でのポップアップ仕様変更
nsIPopupBoxObject::enableRollup
nsIPopupBoxObject::enableKeyboardNavigator

TOP

Firefox 3 でのポップアップ仕様変更

Firefox 3 (Gecko 1.9) では XUL ポップアップの仕様が大幅に変更される。
Neil’s Place » Blog Archive » XUL Popup Improvements

特に注意すべき点

  1. popup 要素は非推奨となり、その代わりにまったく等価な menupopup 要素を使用することが推奨されている。
  2. panel という汎用のポップアップ型要素が新たに追加される。子に menuitem を配置するなら menupopup 、それ以外の色々なUI部品を配置したければ panel 、というふうに使い分ける。
  3. showPopup メソッドが非推奨となり、代わりに openPopup と openPopupAtScreen メソッドが追加される。
  4. state プロパティが追加され、ポップアップが現在開いているか、閉じているかなどが判別可能になる。
  5. ポップアップを開く動作が非同期的なイベントになる。したがってポップアップが開いた直後に何らかの処理をしたければ、 popupshown イベントハンドラを使用する必要がある。
  6. popup / menupopup 要素の見た目がOSネイティブな見た目になる。

openPopup / openPopupAtScreen メソッド

showPopup メソッドは引数の指定方法がわかりにくく混乱を招いていたが、 Firefox 3 で新たに追加される2つのメソッドは、スクリーンに対する絶対位置へポップアップを開くための openPopupAtScreen と、ある要素に対する相対位置へポップアップを開くための openPopup というように明瞭化されている。
例1) スクリーン上の位置 (100, 200) にポップアップ popupElt を開く

Firefox 2
document.popupNode = null;  // 位置ズレ防止
popupElt.showPopup(document.documentElement, 100, 200, "popup", null, null);
Firefox 3
popupElt.openPopupAtScreen(100, 200, false);

例2) 要素 aAnchorElt の左下にポップアップ popupElt を開く

Firefox 2
popupElt.showPopup(aAnchorElt, -1, -1, "popup", "bottomleft", "topleft");
Firefox 3
popupElt.openPopup(aAnchorElt, "after_start", 0, 0, false, true);

openPopup メソッドの第5引数 isContextMenu はコンテキストメニューかどうかを示す。しかし、コンテキストメニューにした場合、実際にどのような変化が現れるのかは不明。 openPopup メソッドの第6引数 attributesOverride は position 属性をメソッドの第2引数で上書きするかどうかを示す。

state プロパティ

nsIPopupBoxObject#popupState プロパティへのエイリアス。
Firefox 2 では特定のポップアップが開いているか否かという状態を調べるためには popupshowing や popuphidden などのイベントを監視して自前のフラグを管理したりする必要があったが、そういった苦労は state プロパティで一気に解消される。

state プロパティの値 意味
showing ポップアップは開く途中である。 popupshowing イベント発生時はこの状態。
open ポップアップは開いている。 popupshown イベント発生後はこの状態。
hiding ポップアップは閉じる途中である。 popuphiding イベント発生時はこの状態。
closed ポップアップは閉じている。 popuphidden イベント発生後はこの状態。

ポップアップの非同期的な動作

例えば以下のようなコードを実行すると Firefox 2 では「123」の順番で出力されるが、 Firefox 3 では「132」の順番になる。

XUL
<menupopup id="testPopup" onpopupshowing="dump('1');" onpopupshown="dump('2');"> ...
JavaScript
document.getElementById("testPopup").showPopup( ... ); dump('3');

他には?

nsIPopupBoxObject#enableKeyboardNavigator について、現段階 (Minefield 3.0a8pre) では実行すべきタイミングや得られる効果が Firefox 2 と異なる。効果が逆なのは明らかにバグなのでBug 279703のコメントに書いたところ、Bug 396517としてパッチも提供されてチェックイン間近となっている。なお、代替として ignorekeys 属性を使用することが推奨されており、こちらの動作は問題ない。

nsIPopupBoxObject#enableRollup について、現段階 (Minefield 3.0a8pre) では何もしない
代わりに、新しく追加された setConsumeRollupEvent を使うことで Windows + Minefield 3.0a8pre では期待通りの動作結果が得られた。しかし Linux + Minefield 3.0a8pre では効果がない模様?これも詳細を調べ中。

また、C++レベルでの内部的な実装が一新されたことに伴い、ポップアップの位置ズレやサイズ不正、クラッシュバグ、複数のポップアップを開いたときに閉じられなくなることがあるバグなど、多くのバグが解消されているようだ。

関連リンク

mozilla mozilla/toolkit/content/widgets/popup.xml
mozilla mozilla/layout/xul/base/public/nsIPopupBoxObject.idl
XUL:PopupGuide – MDC

TOP

xul:popup#showPopup の位置ズレを直す

popup 要素に対して

showPopup(document.documentElement, event.screenX, event.screenY, "popup", null, null);

とかすると、マウスポインタを基点とした位置にポップアップが表示されるが、その後に右クリックのコンテキストメニューを表示させてからもう一度上記の showPopup を実行すると、ポップアップが表示される位置が大きくズレるという問題に悩まされた。

しかし、showPopup する前に、以下のようにしてやることで直った。

document.popupNode = null;

TOP

nsIPopupBoxObject::enableKeyboardNavigator

通常、 ポップアップ (*1) を開いているときは、キーボードからの入力が効かなくなる。このキーイベント横取りは nsIPopupBoxObject::enableKeyboardNavigator(false) で無効化することができる。このメソッドは enableRollup 同様、 showPopup した直後に呼び出す必要がある。一方ツールチップ (*1) ではデフォルトでキーボード入力イベントの横取りはしないようになっている。

*1 ここでいうポップアップとは popup か tooltip 要素を showPopup メソッドの引数 popuptype を「popup」にして開いたものを指し、ツールチップとは引数 popuptype を「tooltip」にしたものを指す。

ところで、 Firefox 3 では nsIPopupBoxObject::setConsumeRollupEvent というメソッドも使用可能だが、これがどういう効果があるのかよくわからん。

TOP

nsIPopupBoxObject::enableRollup

通常、 ポップアップを開いているときは、Firefox のウィンドウ内のどの部分をクリックしてもクリックイベントが横取りされ、クリックイベントが発生する代わりにポップアップが閉じられる。この動作は nsIPopupBoxObject::enableRollup メソッドで変更が可能である。 enableRollup はポップアップを開いた後に呼び出す必要があることに注意。

ここでひとつプラットフォーム依存の悩ましい問題があり、 Windows では enableRollup(false) すると、ツールチップのようにポップアップ内に配置したボタンなどの要素がクリック不可となってしまう。

Windows 以外での環境での動作を知りたいので、テストケースを使った動作確認よろしくお願いします。
popup testcase


Windows + Firefox 2 では以下のようになるはずです。
Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3
[1] x | o
[2] o | x
[3] o | x
[4] o | x

Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.9a4pre) Gecko/20070405 Minefield/3.0a4pre
上記と同じ結果。

一方、Linux + Firefox 2 ではポップアップ内のボタンは常にクリック可能。
Mozilla/5.0 (X11; U; Linux i686; ja; rv:1.8.1.1) Gecko/20060601 Firefox/2.0.0.1 (Ubuntu-edgy)
[1] x | o
[2] o | o
[3] o | o
[4] o | o
テストケースにはありませんが、 enableRollup(true) も試したところ、OKダイアログを閉じるときにクラッシュするバグがありました。

TOP

ポップアップとツールチップの仕様がOSによって違う (1への補足)

SCRAPBLOG : ポップアップとツールチップの仕様がOSによって違う (1) の補足

Windows + Firefox 2 で、以下の動作を確認。

1. テストケースを新しいタブで開く。
2. [popup as tooltip] か [tooltip as tooltip] ボタンを押してポップアップ・ツールチップを開く。
3. ツールチップを閉じずに別のタブへ切り替える。
4. [label] [menuitem] [button] [toolbarbutton] それぞれをクリックしてみる。

すると、なぜか4つすべての要素がクリック可能である。

TOP

ポップアップとツールチップの仕様がOSによって違う (2)

<tooltip> 要素には、マウスがツールチップ上から逃げたときに自動で閉じる仕組みがあるのだが、その挙動が Windows と Linux とで異なるのが悩ましい。

まず前回と同じテストケースを開き、 [tooltip as popup] か [tooltip as tooltip] ボタンをクリックしてツールチップを表示させる。次にツールチップ上へマウスを乗せてからツールチップの外へ移動する。
このとき、 Windows + Firefox 2 ではすぐにツールチップが閉じる。しかし、 Linux ではすぐには閉じず、さらにもう一度ツールチップ上に乗る→ツールチップ外へ移動を繰り返したときにツールチップが閉じる。この動きは果たして正しいのだろうか?

この <tooltip> 要素特有の自動で閉じる仕組みは XBL にて実装されていて chrome://global/content/bindings/popup.xml の <handler event=”mouseout”> を見ればわかる。
Windows では event.relatedTarget を調べてツールチップ外へ逃げたことを識別可能であるようだが、 Linux ではなぜか event.relatedTarget が null となり、代わりに _mouseOutCount の値が2になった時点でツールチップを閉じているようだ。

ソースコードのコメントを読むと、マウスがツールチップ上に乗った時点で _mouseOutCount が1となり、マウスがツールチップから逃げた時点で _mouseOutCount が2となってツールチップが閉じられるような仕組みを想定しているようだ。しかし、実際はマウスがツールチップに乗ったときに発生する mouseout イベントでは event.relatedTarget が null ではないため、 if (!rel) の条件は偽となって _mouseOutCount は増えない。したがって、 Linux では2回ツールチップ外へ逃げたときにツールチップが閉じるという挙動になっている。もしかしてこれはバグだろうか?

なお、Windows + Firefox 3 では、SCRAPBLOG : Bug 373518 – event.relatedTarget is never set when leaving popup にあるようにツールチップ外へ逃げたときの event.relatedTarget が null となるため、 Linux とまったく同じ動きになる。そもそも Bug 373518 自体もバグなのか何なのかよくわからなくなってきた。

例によってMacでの動作は未確認ですので、情報お待ちしております。

TOP

ポップアップとツールチップの仕様がOSによって違う (1)

ポップアップとツールチップの仕様が Windows と Linux で色々と違いがあって悩ましい。

まず、 <popup> と <tooltip> 要素内に <button> や <menuitem> などのクリックが可能な要素をいくつか配置する。次に、それぞれを showPopup(event.target, -1, -1, type, "bottomleft", "topleft") などとやってツールチップとして表示させる。
すると、 Linux では <button> も <menuitem> も問題なくクリック可能である。多分どんな要素に対してもマウスクリックイベントが伝わりそうな雰囲気。
しかし、 Windows では <button> をクリックしようとしても、何か透明な壁にさえぎられているかのようにクリックできない。なぜか <menuitem> だけはこの壁をすり抜けてクリックすることができる。これは憶測だが、この透明な壁によるマウスクリックイベントの奪取は DOM のレイヤーよりも上で行われているため、 キャプチャフェーズに対するイベントリスナをセットしたりしても、一向にクリックイベントを発生させることができない。

テストケース

Mac ではどうなるか知らん。情報求む!

TOP

Bug 373518 – event.relatedTarget is never set when leaving popup

mouseover イベントの event.relatedTarget によってマウスがどこから来たか調べたり、 mouseout イベントの event.relatedTarget によってマウスがどこへ行くのかを調べることができる。ところが Firefox 3.0a3pre で試したところ、 popup 要素上で発生した mouseover/mouseout イベントの relatedTarget が null であるため、マウスがポップアップを離れてどこへ行ったのかを判別することができないという問題が発生した。 Firefox 2 では問題なく動作するので、おかしいなと思い Bugzilla へ登録しておいた。
Bug 373518 – event.relatedTarget is never set when leaving popup

ついでに MDC に DOM:event:Comparison of Event Targets – MDC という未完成記事があったので、 mouseover/mouseout に対する target と relatedTarget の違いについて加筆しておいた。

TOP