What is the height of a “line” in a wheel event? (deltaMode = DOM_DELTA_LINE)

Overview:

While the scroll value resulting from DOM_DELTA_LINE may not be specifically defined by any specification, based on the following comment from the Chromium issue tracker and my own observations, it appears that Firefox is presently the only browser that will report wheel events with anything other than deltaMode as DOM_DELTA_PIXEL (0).

Comment from Chromium issue Implement DOM3 wheel event:

We ended up doing what IE does and report the exact number of pixels we would scroll the element.

The following asserts are always true (given that the element can be
scrolled).

element.scrollTop = 0;
element.addEventListener('wheel', function(e) {
  assert(e.deltaMode === MouseEvent. DOM_DELTA_PIXEL);
  assert(element.scrollTop === e.deltaY);
});
// scroll

This way you know exactly how much to scroll at all times.

We never set the deltaMode to anything else but DOM_DELTA_PIXEL.

According to that comment, Chromium matches IE’s only-pixel deltas. Though not covered, this almost certainly extends directly to modern Opera and Safari. My own observations from testing on Window, Mac, and Linux with multiple input devices did not disprove this.

So since Firefox is the only browser that will report a deltaMode of DOM_DELTA_LINE (1) or DOM_DELTA_PAGE (2), for the time being anyway, we only need to know what Firefox computes these values as. Well, that’s where things get a bit tricky, but through searching through the Firefox source and some trial and error, I’ve determined it directly corresponds the default font and default font-size. Specifically those configured in the following preferences, ignoring any CSS on the page.

Firefox fonts preferences options

That means that to correctly detect the line-height used in scrolling, you must have a completely un-styled inline element to detect the computed rendering height from. Since CSS on the page will almost certainly interfere, we must create a new and clean window in an iframe to do the detection.

Detecting the line-height used by DOM_DELTA_LINE triggered scroll events:

For this purpose, I have created the following little function to synchronously detect the pure, unaltered line-height.

function getScrollLineHeight() {
    var r;
    var iframe = document.createElement('iframe');
    iframe.src="#";
    document.body.appendChild(iframe);
    var iwin = iframe.contentWindow;
    var idoc = iwin.document;
    idoc.open();
    idoc.write('<!DOCTYPE html><html><head></head><body><span>a</span></body></html>');
    idoc.close();
    var span = idoc.body.firstElementChild;
    r = span.offsetHeight;
    document.body.removeChild(iframe);
    return r;
}

// Get the native croll line height.
console.log(getScrollLineHeight());

Ok, so now we know just how much a line-height delta should translate to in pixels. However, on Window, there is one other thing that you may need to take into consideration. On Windows, the default scroll speed is overridden to make it 2x faster by default, but only for the root element.

Override system of system scroll speed:

We’re providing an override mechanism of system scroll speed because the default system scrolling speed of Windows is slower than WebKit’s scrolling speed. This was suggested for alternative way of the acceleration system (see next section).

This is enabled in default settings only on Windows. mousewheel.system_scroll_override_on_root_content.enabled can switch it.

On Windows, only when the system scroll speed settings are not customized by user or mouse driver, this overrides the scrolling speed. On the others, this always overrides the speed.

The ratio can be specified by hidden prefs. mousewheel.system_scroll_override_on_root_content.vertical.factor is for vertical scrolling event. mousewheel.system_scroll_override_on_root_content.horizontal.factor is for horizontal scrolling event. The values are used as 1/100 (i.e., the default value 200 means 2.0).

nsEventStateManager multiplies the scrolling speed by the ratio when it executes to scroll a root scrollable view of a document. So, DOMMouseScroll event’s delta value has never been overwritten by this.

See also bug 513817.

This is only applicable by default to the root of the document, scrolling elements within the document like a textarea are not affected (except maybe iframes?). There’s also no way to get the specified value if the default 2x is reconfigured, so if you deem this value important, you will have to resort to user-agent OS sniffing, perhaps with the technically non-standard yet popular navigator.platform.

What about DOM_DELTA_PAGE?:

Windows also has an alternate setting for vertical scrolling where instead of scrolling by the number of lines, it scroll by an almost full page. To clarify, this is the dialog with the “One screen at a time” setting controlling this in Windows 7.

Windows 7 wheel settings

When this is activated, a single click will scroll almost a full page, here meaning the height of the scrolling panel, minus the scroll bars. I say almost, because Firefox takes some other things into consideration to reduce the amount of scrolling. Namely, reducing by some lines, a percentage, and somehow subtracting the fixed position headers and footers. See the following block of code for some of the logic.

Excerpt from layout/generic/nsGfxScrollFrame.cpp:

nsSize
ScrollFrameHelper::GetPageScrollAmount() const
{
  nsSize lineScrollAmount = GetLineScrollAmount();
  nsSize effectiveScrollPortSize;
  if (mIsRoot) {
    // Reduce effective scrollport height by the height of any fixed-pos
    // headers or footers
    nsIFrame* root = mOuter->PresContext()->PresShell()->GetRootFrame();
    effectiveScrollPortSize =
      GetScrollPortSizeExcludingHeadersAndFooters(root, mScrollPort);
  } else {
    effectiveScrollPortSize = mScrollPort.Size();
  }
  // The page increment is the size of the page, minus the smaller of
  // 10% of the size or 2 lines.
  return nsSize(
    effectiveScrollPortSize.width -
      std::min(effectiveScrollPortSize.width/10, 2*lineScrollAmount.width),
    effectiveScrollPortSize.height -
      std::min(effectiveScrollPortSize.height/10, 2*lineScrollAmount.height));
}

I’m not 100% sure what exactly is detected as a fixed position header and footer element and what isn’t, but this should give a pretty good overview of how the DOM_DELTA_PAGE mode also works, in addition to the DOM_DELTA_LINE which this question asks about.

Leave a Comment