]> git.gir.st - VimFx.git/blob - extension/lib/scrollable-elements.coffee
Merge branch 'master' into develop
[VimFx.git] / extension / lib / scrollable-elements.coffee
1 ###
2 # Copyright Simon Lydell 2015.
3 #
4 # This file is part of VimFx.
5 #
6 # VimFx is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # VimFx is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
18 ###
19
20 # This file contains an abstraction for keeping track of scrollable elements,
21 # automatically keeping the largest scrollable element up-to-date. It stops
22 # tracking elements that are removed from the DOM.
23
24 utils = require('./utils')
25
26 class ScrollableElements
27 constructor: (@window, @MINIMUM_SCROLL) ->
28 @elements = new Set()
29 @largest = null
30
31 # In quirks mode (when the page lacks a doctype), such as on Hackernews,
32 # `<body>` is considered the root element rather than `<html>`. The 'overflow'
33 # event is triggered for `<html>` though (_not_ `<body>`!). This method takes
34 # care of returning the appropriate element, so we don’t need to think about
35 # it anywhere else.
36 quirks: (element) ->
37 document = element.ownerDocument
38 if element == document.documentElement and
39 document.compatMode == 'BackCompat' and document.body?
40 return document.body
41 else
42 return element
43
44 has: (element) -> @elements.has(@quirks(element))
45
46 add: (element) ->
47 element = @quirks(element)
48 @elements.add(element)
49 utils.onRemoved(@window, element, @delete.bind(this, element))
50 @largest = element if @isLargest(element)
51
52 delete: (element) =>
53 element = @quirks(element)
54 @elements.delete(element)
55 @updateLargest() if @largest == element
56
57 reject: (fn) ->
58 @elements.forEach((element) => @elements.delete(element) if fn(element))
59 @updateLargest()
60
61 isScrollable: (element) ->
62 element = @quirks(element)
63 return element.scrollTopMax >= @MINIMUM_SCROLL or
64 element.scrollLeftMax >= @MINIMUM_SCROLL
65
66 # It makes the most sense to consider the uppermost scrollable element the
67 # largest. In other words, if a scrollable element contains another scrollable
68 # element (or a frame containing one), the parent should be considered largest
69 # even if the child has greater area.
70 isLargest: (element) ->
71 return true unless @largest
72 return true if utils.containsDeep(element, @largest)
73 return false if utils.containsDeep(@largest, element)
74 return utils.area(element) > utils.area(@largest)
75
76 updateLargest: ->
77 # Reset `@largest` and find a new largest scrollable element (if there are
78 # any left).
79 @largest = null
80 @elements.forEach((element) => @largest = element if @isLargest(element))
81
82 # Elements may overflow when zooming in or out. However, the `.scrollHeight`
83 # of the element is not correctly updated when the 'overflow' event occurs,
84 # making it possible for unscrollable elements to slip in. This method tells
85 # whether the largest element really is scrollable, updating it if needed.
86 hasOrUpdateLargestScrollable: ->
87 if @largest and @isScrollable(@largest)
88 return true
89 else
90 @reject((element) => not @isScrollable(element))
91 return @largest?
92
93 module.exports = ScrollableElements
Imprint / Impressum