         @keyIsDown = false
         @time += 1
 
+  # Return true if CoreScroller would not initiate a new scroll right now.
+  wouldNotInitiateScroll: -> @lastEvent?.repeat and @settings.get "smoothScroll"
+
   # Calibration fudge factors for continuous scrolling.  The calibration value starts at 1.0.  We then
   # increase it (until it exceeds @maxCalibration) if we guess that the scroll is too slow, or decrease it
   # (until it is less than @minCalibration) if we guess that the scroll is too fast.  The cutoff point for
   # which guess we make is @calibrationBoundary. We require: 0 < @minCalibration <= 1 <= @maxCalibration.
-  minCalibration: 0.5 # Controls how much we're willing to slow scrolls down; smaller => more slow down.
-  maxCalibration: 1.6 # Controls how much we're willing to speed scrolls up; bigger => more speed up.
-  calibrationBoundary: 150 # Boundary between scrolls which are considered too slow, and those too fast.
+  minCalibration: 0.5 # Controls how much we're willing to slow scrolls down; smaller means more slow down.
+  maxCalibration: 1.6 # Controls how much we're willing to speed scrolls up; bigger means more speed up.
+  calibrationBoundary: 150 # Boundary between scrolls which are considered too slow, or too fast.
 
   # Scroll element by a relative amount (a number) in some direction.
   scroll: (element, direction, amount) ->

    activatedElement ||= document.body
    return unless activatedElement

    element = findScrollableElement activatedElement, direction, amount, factor
    elementAmount = factor * getDimension element, direction, amount
    CoreScroller.scroll element, direction, elementAmount

  scrollTo: (direction, pos) ->
    return unless document.body or activatedElement
    activatedElement ||= document.body

    element = findScrollableElement activatedElement, direction, pos
    amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
    CoreScroller.scroll element, direction, amount


# Test whether element should be scrolled.
shouldScroll = (element, direction) ->
  computedStyle = window.getComputedStyle(element)
  # Elements with `overflow: hidden` must not be scrolled.
  return false if computedStyle.getPropertyValue("overflow-#{direction}") == "hidden"
  # Elements which are not visible should not be scrolled.
  return false if computedStyle.getPropertyValue("visibility") in ["hidden", "collapse"]
  return false if computedStyle.getPropertyValue("display") == "none"
  true

# Test whether element does actually scroll in the direction required when asked to do so.  Due to chrome bug
# 110149, scrollHeight and clientHeight cannot be used to reliably determine whether an element will scroll.
# Instead, we scroll the element by 1 or -1 and see if it moved (then put it back).
# Bug verified in Chrome 38.0.2125.104.
doesScroll = (element, direction, amount, factor) ->
  # amount is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls (only
  # gg, G, and friends), amount can be either 'max' or zero. In the former case, we're definitely scrolling
  # forwards, so any positive value will do for delta.  In the latter, we're definitely scrolling backwards,
  # so a delta of -1 will do.  For absolute scrolls, factor is always 1.
  delta = factor * getDimension(element, direction, amount) || -1
  delta = Math.sign delta # 1 or -1
  performScroll(element, direction, delta) and performScroll(element, direction, -delta)

# From element and its parents, find the first which we should scroll and which does scroll.
findScrollableElement = (element, direction, amount, factor) ->
  while element != document.body and
    not (doesScroll(element, direction, amount, factor) and shouldScroll(element, direction))
      element = element.parentElement || document.body
  element

checkVisibility = (element) ->
  # If the activated element has been scrolled completely offscreen, then subsequent changes in its scroll
  # position will not provide any more visual feedback to the user. Therefore, we deactivate it so that
  # subsequent scrolls affect the parent element.
  rect = activatedElement.getBoundingClientRect()
  if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
    activatedElement = element

# How scrolling is handled by CoreScroller.
#   - For jump scrolling, the entire scroll happens immediately.
#   - For smooth scrolling with distinct key presses, a separate animator is initiated for each key press.
#     Therefore, several animators may be active at the same time.  This ensures that two quick taps on `j`
#     scroll to the same position as two slower taps.
#   - For smooth scrolling with keyboard repeat (continuous scrolling), the most recently-activated animator
#     continues scrolling at least until its keyup event is received.  We never initiate a new animator on
#     keyboard repeat.

CoreScroller =
  init: (frontendSettings) ->
