aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/scroller.coffee
blob: b3a14c78293a0f0f611851d5f6e8b2ceb5508b3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
window.Scroller = root = {}

#
# 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

root.init = ->
  handlerStack.push DOMActivate: -> activatedElement = event.target

scrollProperties =
  x: {
    axisName: 'scrollLeft'
    max: 'scrollWidth'
    viewSize: 'clientHeight'
  }
  y: {
    axisName: 'scrollTop'
    max: 'scrollHeight'
    viewSize: 'clientWidth'
  }

getDimension = (el, direction, name) ->
  # the clientSizes of the body are the dimensions of the entire page, but the viewport should only be the
  # part visible through the window
  if name is 'viewSize' and el is document.body
    if direction is 'x' then window.innerWidth else window.innerHeight
  else
    el[scrollProperties[direction][name]]

# Chrome does not report scrollHeight accurately for nodes with pseudo-elements of height 0 (bug 110149).
# Therefore we cannot figure out if we have scrolled to the bottom of an element by testing if scrollTop +
# clientHeight == scrollHeight. So just try to increase scrollTop blindly -- if it fails we know we have
# reached the end of the content.
ensureScrollChange = (direction, changeFn) ->
  axisName = scrollProperties[direction].axisName
  element = activatedElement
  loop
    oldScrollValue = element[axisName]
    # Elements with `overflow: hidden` should not be scrolled.
    overflow = window.getComputedStyle(element).getPropertyValue("overflow-#{direction}")
    changeFn(element, axisName) unless overflow == "hidden"
    break unless element[axisName] == oldScrollValue && element != document.body
    # we may have an orphaned element. if so, just scroll the body element.
    element = element.parentElement || document.body

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

# scroll the active element in :direction by :amount * :factor.
# :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
root.scrollBy = (direction, amount, factor = 1) ->
  # if this is called before domReady, just use the window scroll function
  if (!document.body and amount instanceof Number)
    if (direction == "x")
      window.scrollBy(amount, 0)
    else
      window.scrollBy(0, amount)
    return

  if (!activatedElement || !isRendered(activatedElement))
    activatedElement = document.body

  ensureScrollChange direction, (element, axisName) ->
    if Utils.isString amount
      elementAmount = getDimension element, direction, amount
    else
      elementAmount = amount
    elementAmount *= factor
    element[axisName] += elementAmount

root.scrollTo = (direction, pos) ->
  return unless document.body

  if (!activatedElement || !isRendered(activatedElement))
    activatedElement = document.body

  ensureScrollChange direction, (element, axisName) ->
    if Utils.isString pos
      elementPos = getDimension element, direction, pos
    else
      elementPos = pos
    element[axisName] = elementPos

# TODO refactor and put this together with the code in getVisibleClientRect
isRendered = (element) ->
  computedStyle = window.getComputedStyle(element, null)
  return !(computedStyle.getPropertyValue("visibility") != "visible" ||
      computedStyle.getPropertyValue("display") == "none")