aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/scroller.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'content_scripts/scroller.coffee')
-rw-r--r--content_scripts/scroller.coffee89
1 files changed, 89 insertions, 0 deletions
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
new file mode 100644
index 00000000..08ab14a1
--- /dev/null
+++ b/content_scripts/scroller.coffee
@@ -0,0 +1,89 @@
+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]
+ changeFn(element, axisName)
+ lastElement = element
+ # we may have an orphaned element. if so, just scroll the body element.
+ element = element.parentElement || document.body
+ break unless (lastElement[axisName] == oldScrollValue && lastElement != 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 = lastElement
+
+# 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
+
+ amount = getDimension activatedElement, direction, amount if Utils.isString amount
+
+ amount *= factor
+
+ if (amount != 0)
+ ensureScrollChange direction, (element, axisName) -> element[axisName] += amount
+
+root.scrollTo = (direction, pos) ->
+ return unless document.body
+
+ if (!activatedElement || !isRendered(activatedElement))
+ activatedElement = document.body
+
+ pos = getDimension activatedElement, direction, pos if Utils.isString pos
+
+ ensureScrollChange direction, (element, axisName) -> element[axisName] = pos
+
+# 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")