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

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

もずはっく日記(2012年8月)

2012年8月16日

Bug-org 719320 Implement DOM3 wheel event #2
初回投稿日時: 2012年08月16日13時01分29秒
最終更新日時: 2012年08月16日13時01分53秒
カテゴリ: Events GTK IE Mac Mozilla Core Mozilla17 Windows バグ修正
SNS: (list)

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属性値が、deltaXdeltaYdeltaZの値の単位を示します。Windowsでは、WheelEvent.DOM_DELTA_LINEか、WheelEvent.DOM_DELTA_PAGEになります。Macでは、WheelEvent.DOM_DELTA_PIXELか、WheelEvent.DOM_DELTA_LINEになります。どちらになるかは、使用しているデバイスのドライバや、その設定に依存します。これら以外のプラットフォームではWheelEvent.DOM_DELTA_LINEのみです。

deltaXdeltaYdeltaZは整数ではなく、小数を返します。たとえば、一回のイベントで、一行の三分の一だけスクロールする場合、deltaModeWheelEvent.DOM_DELTA_LINEで、delta値が0.3333...になります。正の値が、下方向、もしくは右方向で、負の値が、上方向、もしくは左方向を意味します。つまり、そのまま座標に可算できます。

deltaZは、今のところ、どのプラットフォームでも常にゼロとなります。Mac(Cocoa)のネイティブイベントにはZ軸方向のdelta値も存在するのですが、これを活用しているデバイスがないため、ネイティブイベントの符号と、DOMイベントの符号を同一のものとするか、反転させるべきなのか、その判断がつかないためです。

ちなみに、Macのみ、ネイティブイベント一つで斜め方向のスクロールが表現できるため、deltaXdeltaYが同時に非ゼロになることがあるので注意してください。

if (event.deltaX) {

} else if (event.deltaY) {

}

このようなコードを書いてしまうと、多くのイベントを捨てることになり、レスポンスの悪いアプリケーションになってしまいます。

このため、今までの垂直方向と、水平方向を同時に指定できていたユーザ設定は再利用不能になりました。また、最近のネイティブホイールイベントは操作速度に応じて、様々な大きさの値をデルタ値に指定してきますので、今までのデルタ値をそのまま固定した値で置換してしまう設定もすでに時代遅れとなっています。

この問題を解決するため、また、ESR10とprofileを共有しても問題が出ないように、全ての設定が新設されました。

まず、モディファイアキーを指定する部分の名前が変わりました。defaultwith_altwith_controlwith_metawith_shiftwith_winとなっています。二つ以上のモディファイアキーを同時押ししている場合は、defaultになります。MacのCommandキーはwith_metaになります。

各イベントのデフォルトアクションは、mousewheel.(モディファイアキー).actionで指定できます。値も以前とは変更されていて、0は「何もしない」、1は「スクロール」、2は「履歴を戻る、もしくは進む」、3は「ズーム」となっています。

デルタ値のカスタマイズは係数を設定で指定し、ネイティブイベントのデルタ値に掛けるようになりました。mousewheel.(モディファイアキー).delta_multiplier_xmousewheel.(モディファイアキー).delta_multiplier_ymousewheel.(モディファイアキー).delta_multiplier_zでその係数を指定します。値は、1001.0を意味します。-200と指定すると、-2.0倍されるため、スクロール方向は逆になり、速度も倍になります。また、この設定は、DOMのwheelイベントのdeltaXdeltaYdeltaZの値も書き換えますので注意してください。現在、100未満、-100より大きい数値には対応していません

ちなみに、今までのレガシーイベントDOMMouseScrollイベントと、MozMousePixelScrollイベントも、これまでとできる限り同じタイミングで発生します。イベントの発生順序は、

  1. wheelイベントが通常イベントグループで
  2. 垂直方向のDOMMouseScrollイベントが両方のイベントグループで、ただし、wheelイベントがpreventDefault()で消費されておらず、wheelイベントが1行以上のスクロールの場合に限る
  3. 垂直方向のMozMousePixelScrollイベントが両方のイベントグループで、ただし、wheelイベントがpreventDefault()で消費されておらず、wheelイベントが1ピクセル以上のスクロールの場合に限る
  4. 水平方向のDOMMouseScrollイベントが両方のイベントグループで、ただし、wheelイベントがpreventDefault()で消費されておらず、wheelイベントが1行以上のスクロールの場合に限る
  5. 水平方向のMozMousePixelScrollイベントが両方のイベントグループで、ただし、wheelイベントがpreventDefault()で消費されておらず、wheelイベントが1ピクセル以上のスクロールの場合に限る
  6. wheelイベントがシステムイベントグループで

となっています。システムイベントグループは、Webコンテンツからは登録できないグループで、通常イベントグループでイベントが全て伝播した後に、再び全ての要素のシステムイベントグループのイベントリスナにイベントが配信されます。これは、通常イベントグループでstopPropagation()が呼び出されていても行われるので、デフォルトアクションの実装に最適です。

ですので、アドオン作者の方は、システムイベントグループでwheelイベントのリスナを登録することで、レガシーイベントが処理されたかどうかを確認してからwheelイベントを処理することが可能になっています。

関連するかもしれないエントリ

bug-org 719320を含むエントリ

Bug-org 719320 Implement DOM3 wheel event