     amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
     doScrollBy element, direction, amount, wantSmooth
 
-root = exports ? window
 root.Scroller = Scroller

# activatedElement is different from document.activeElement -- the latter seems to be reserved mostly for
# input elements. This mechanism allows us to decide whether to scroll a div or to scroll the whole document.
#
activatedElement = null
settings = null


  element[axisName] - before

# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->
  time = 0
  lastActivationId = -1
  keyupHandler = null

  # When the keyboard is repeating and the key is released, sometimes the last key event is delivered *after*
  # the keyup event.  This could cause indefinite scrolling with no key pressed, because no further keyup
  # event is delivered (we behave as if the final key is still pressed).  To get around this, we briefly hold
  # a lock to prevent a new smooth scrolling event from beginning too soon after the end of the previous
  # scroll.  This is a hack.
  keyLock = false

  (element, direction, amount, wantSmooth) ->
    axisName = scrollProperties[direction].axisName

    unless wantSmooth and settings.get "smoothScroll"
      return performScroll element, axisName, amount

    # Assumption.  If animator A is started before animator B, then A will also finish before B.  We need to
    # verify this - it may not always be true.

    console.log("locked") if keyLock
    if lastActivationId == time or keyLock
      # This is a keyboard repeat, and the most-recently activated animator will handle it.
      return

    if not keyupHandler
      keyupHandler = root.handlerStack.push keyup: ->
        time += 1
        keyLock = true
        setTimeout((-> keyLock = false), 10)
        console.log "keyup", time, lastActivationId
        true # Do not prevent propagation.

    lastActivationId = activationId = ++time
    console.log activationId

    duration = 100 # Duration in ms.
    fudgeFactor = 25

    # Allow a bit longer for longer scrolls.
    duration += fudgeFactor * Math.log Math.abs amount

    # Round away from 0, so that we don't leave any scroll amount unscrolled.
    roundOut = if 0 <= amount then Math.ceil else Math.floor
    delta = roundOut(amount / duration)

    animatorId = null
    lastTime = null
    start = null
    scrolledAmount = 0

    stopScrolling = (progress) ->
      if activationId == time
        # This is the most recent animator, and the key is still pressed.
        false
      else
        duration <= progress

    animate = (timestamp) ->
      start ?= timestamp

      progress = timestamp - start
      scrollDelta = roundOut(delta * progress) - scrolledAmount
      scrolledAmount += scrollDelta

      if performScroll(element, axisName, scrollDelta, false) != scrollDelta or stopScrolling progress
          # One final call of performScroll to check the visibility of the activated element.
          performScroll(element, axisName, 0, true)
          window.cancelAnimationFrame(animatorId)
          time += 1 if activationId == time
      else
        animatorId = window.requestAnimationFrame(animate)

    animatorId = window.requestAnimationFrame(animate)

Scroller =
  init: (frontendSettings) ->
