4月あたりにほぼ完成していたD3E WheelEventの実装がようやく終わりました。実に、29のパッチに分割され、私個人では最高記録です。
この変更により、各widgetのコードは、ネイティブイベントの伝達に、nsMouseScrollEvent
ではなく、mozilla::widget::WheelEvent
を利用するようになり、widget側のコードが非常にシンプルになっています。特に、high resolution scrollをサポートしているWindowsとMacでは、生成するイベントの回数が、最大で半分、もしくは1/4にまで減っているので若干のパフォーマンスの向上も期待できます。
Webアプリから見ると、この変更により、IE9以降とコードを共通化できるようになっています。もう、DOMMouseScroll
や、MozMousePixelScroll
といった、Geckoの複雑な独自イベントを処理する必要はありません。単に、wheel
イベントを処理するだけで良いのです。
簡単なサンプルを書いておくと、こんな感じです。
var customScrollableElement = document.getElementById("scrollableElement");
var lineHeight = 16;
var pageWidth = 100;
var pageHeight = 100;
customScrollableElement.addEventListener("wheel", function (event) {
// the event might be consumed.
if (event.defaultPrevented) {
return; // shouldn't do anything
}
// consume the event for preventing default action such as scroll.
event.preventDefault();
var deltaInPixels = { x: 0, y: 0 };
switch (event.deltaMode) {
case WheelEvent.DOM_DELTA_PIXEL:
// delta values are number of pixels to scroll
deltaInPixels.x = event.deltaX;
deltaInPixels.y = event.deltaY;
break;
case WheelEvent.DOM_DELTA_LINE:
// delta values are number of lines to scroll
deltaInPixels.x = event.deltaX * lineHeight;
deltaInPixels.y = event.deltaY * lineHeight;
break;
case WheelEvent.DOM_DELTA_PAGE:
// delta values are number of pages to scroll
deltaInPixels.x = event.deltaX * pageWidth;
deltaInPixels.y = event.deltaY * pageHeight;
break;
}
}
if (!deltaInPixels.x && !deltaInPixels.y) {
return;
}
// do scroll here!
}, true);
wheel
イベントはブラウザが拡大解釈せず、できるだけネイティブイベントの情報をそのまま伝達できるように設計されていますので、deltaMode
属性値が、deltaX
、deltaY
、deltaZ
の値の単位を示します。Windowsでは、WheelEvent.DOM_DELTA_LINE
か、WheelEvent.DOM_DELTA_PAGE
になります。Macでは、WheelEvent.DOM_DELTA_PIXEL
か、WheelEvent.DOM_DELTA_LINE
になります。どちらになるかは、使用しているデバイスのドライバや、その設定に依存します。これら以外のプラットフォームではWheelEvent.DOM_DELTA_LINE
のみです。
deltaX
、deltaY
、deltaZ
は整数ではなく、小数を返します。たとえば、一回のイベントで、一行の三分の一だけスクロールする場合、deltaMode
がWheelEvent.DOM_DELTA_LINE
で、delta値が0.3333...になります。正の値が、下方向、もしくは右方向で、負の値が、上方向、もしくは左方向を意味します。つまり、そのまま座標に可算できます。
deltaZ
は、今のところ、どのプラットフォームでも常にゼロとなります。Mac(Cocoa)のネイティブイベントにはZ軸方向のdelta値も存在するのですが、これを活用しているデバイスがないため、ネイティブイベントの符号と、DOMイベントの符号を同一のものとするか、反転させるべきなのか、その判断がつかないためです。
ちなみに、Macのみ、ネイティブイベント一つで斜め方向のスクロールが表現できるため、deltaX
とdeltaY
が同時に非ゼロになることがあるので注意してください。
if (event.deltaX) {
} else if (event.deltaY) {
}
このようなコードを書いてしまうと、多くのイベントを捨てることになり、レスポンスの悪いアプリケーションになってしまいます。
このため、今までの垂直方向と、水平方向を同時に指定できていたユーザ設定は再利用不能になりました。また、最近のネイティブホイールイベントは操作速度に応じて、様々な大きさの値をデルタ値に指定してきますので、今までのデルタ値をそのまま固定した値で置換してしまう設定もすでに時代遅れとなっています。
この問題を解決するため、また、ESR10とprofileを共有しても問題が出ないように、全ての設定が新設されました。
まず、モディファイアキーを指定する部分の名前が変わりました。default
、with_alt
、with_control
、with_meta
、with_shift
、with_win
となっています。二つ以上のモディファイアキーを同時押ししている場合は、default
になります。MacのCommandキーはwith_meta
になります。
各イベントのデフォルトアクションは、mousewheel.(モディファイアキー).action
で指定できます。値も以前とは変更されていて、0
は「何もしない」、1
は「スクロール」、2
は「履歴を戻る、もしくは進む」、3
は「ズーム」となっています。
デルタ値のカスタマイズは係数を設定で指定し、ネイティブイベントのデルタ値に掛けるようになりました。mousewheel.(モディファイアキー).delta_multiplier_x
、mousewheel.(モディファイアキー).delta_multiplier_y
、mousewheel.(モディファイアキー).delta_multiplier_z
でその係数を指定します。値は、100
が1.0
を意味します。-200
と指定すると、-2.0
倍されるため、スクロール方向は逆になり、速度も倍になります。また、この設定は、DOMのwheel
イベントのdeltaX
、deltaY
、deltaZ
の値も書き換えますので注意してください。現在、100未満、-100より大きい数値には対応していません。
ちなみに、今までのレガシーイベントDOMMouseScroll
イベントと、MozMousePixelScroll
イベントも、これまでとできる限り同じタイミングで発生します。イベントの発生順序は、
wheel
イベントが通常イベントグループで
- 垂直方向の
DOMMouseScroll
イベントが両方のイベントグループで、ただし、wheel
イベントがpreventDefault()
で消費されておらず、wheel
イベントが1行以上のスクロールの場合に限る
- 垂直方向の
MozMousePixelScroll
イベントが両方のイベントグループで、ただし、wheel
イベントがpreventDefault()
で消費されておらず、wheel
イベントが1ピクセル以上のスクロールの場合に限る
- 水平方向の
DOMMouseScroll
イベントが両方のイベントグループで、ただし、wheel
イベントがpreventDefault()
で消費されておらず、wheel
イベントが1行以上のスクロールの場合に限る
- 水平方向の
MozMousePixelScroll
イベントが両方のイベントグループで、ただし、wheel
イベントがpreventDefault()
で消費されておらず、wheel
イベントが1ピクセル以上のスクロールの場合に限る
wheel
イベントがシステムイベントグループで
となっています。システムイベントグループは、Webコンテンツからは登録できないグループで、通常イベントグループでイベントが全て伝播した後に、再び全ての要素のシステムイベントグループのイベントリスナにイベントが配信されます。これは、通常イベントグループでstopPropagation()
が呼び出されていても行われるので、デフォルトアクションの実装に最適です。
ですので、アドオン作者の方は、システムイベントグループでwheel
イベントのリスナを登録することで、レガシーイベントが処理されたかどうかを確認してからwheel
イベントを処理することが可能になっています。
font-size: 0;
のスクロール可能な要素では、ホイールによってスクロールできない、というバグです。実際には、キーボードの矢印キーでもスクロールできませんでした。
nsIScrollableFrame::GetScrollLineAmount()
がフォントサイズを元に、行高を返してくるので、この値を元にスクロール処理を行うのですが、中途半端なことに、このメソッドはfont-size: 0;
の場合に、1 app unit
を返してきていました。これは、Gecko内部では、60分の1pxとなりますので結果、スクロールしないという形になっていました。
このバグの修正で、mousewheel.min_line_scroll_amount
という設定を追加しました。nsIScrollableFrame::GetScrollLineAmount()
は、この設定値(意味はピクセル数)より、計算した戻り値が小さい場合、この設定値と同じ意味の値を返すようになります。
また、ホイールイベントは、0.1行ずつスクロール、といったことが普通にありますので、その修正だけではまだスクロールできません。このため、nsEventStateManager::DeltaAccumulator
で、スクロールに利用されなかった端数を記録しておくようにしました。このため、スクロール量が小さい場合でも、ホイールを回し続ければ、トータルでは期待通りのスクロールスピードでスクロールするようになっています。