]> git.gir.st - VimFx.git/blob - extension/packages/hints.coffee
Give more weight to link elements
[VimFx.git] / extension / packages / hints.coffee
1 utils = require 'utils'
2 { getPref } = require 'prefs'
3 { Marker } = require 'marker'
4 { addHuffmanCodeWordsTo } = require 'huffman'
5
6 { interfaces: Ci } = Components
7
8 HTMLDocument = Ci.nsIDOMHTMLDocument
9 XULDocument = Ci.nsIDOMXULDocument
10 XPathResult = Ci.nsIDOMXPathResult
11 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
12
13 CONTAINER_ID = 'VimFxHintMarkerContainer'
14
15 # All the following elements qualify for their own marker in hints mode
16 MARKABLE_ELEMENTS = [
17 "a"
18 "iframe"
19 "area[@href]"
20 "textarea"
21 "button"
22 "select"
23 "input[not(@type='hidden' or @disabled or @readonly)]"
24 "embed"
25 "object"
26 ]
27
28 # All elements that have one or more of the following properties
29 # qualify for their own marker in hints mode
30 MARKABLE_ELEMENT_PROPERTIES = [
31 "@tabindex"
32 "@onclick"
33 "@onmousedown"
34 "@onmouseup"
35 "@oncommand"
36 "@role='link'"
37 "@role='button'"
38 "contains(@class, 'button')"
39 "contains(@class, 'js-new-tweets-bar')"
40 "@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true'"
41 ]
42
43
44 # Remove previously injected hints from the DOM
45 removeHints = (document) ->
46 if container = document.getElementById(CONTAINER_ID)
47 document.documentElement.removeChild(container)
48
49 for frame in document.defaultView.frames
50 removeHints(frame.document)
51
52
53 # Like `injectMarkers`, but also sets hints for the markers
54 injectHints = (document) ->
55 markers = injectMarkers(document)
56 hintChars = utils.getHintChars()
57
58 addHuffmanCodeWordsTo markers,
59 alphabet: hintChars
60 setCodeWord: (marker, hint, index) -> marker.setHint(hint)
61
62 return markers
63
64
65 # Creates and injects markers into the DOM
66 injectMarkers = (document) ->
67 # First remove previous hints container
68 removeHints(document)
69
70 # For now we aren't able to handle hint markers in XUL Documents :(
71 if document instanceof HTMLDocument# or document instanceof XULDocument
72 if document.documentElement
73 # For performance use Document Fragment
74 fragment = document.createDocumentFragment()
75
76 # Select all markable elements in the document, create markers
77 # for each of them, and position them on the page.
78 # Note that the markers are not given hints.
79 set = getMarkableElements(document)
80 markers = []
81 for i in [0...set.snapshotLength] by 1
82 element = set.snapshotItem(i)
83 if rect = getElementRect(element)
84 marker = new Marker(element)
85
86 marker.setPosition(rect)
87 fragment.appendChild(marker.markerElement)
88
89 marker.weight = rect.area
90
91 markers.push(marker)
92
93 container = createHintsContainer(document)
94 container.appendChild(fragment)
95 document.documentElement.appendChild(container)
96
97 for frame in document.defaultView.frames
98 markers = markers.concat(injectMarkers(frame.document))
99
100 return markers or []
101
102
103 createHintsContainer = (document) ->
104 container = document.createElement('div')
105 container.id = CONTAINER_ID
106 container.className = 'VimFxReset'
107 return container
108
109
110 # Returns elements that qualify for hint markers in hints mode.
111 # Generates and memoizes an XPath query internally
112 getMarkableElements = do ->
113 # Some preparations done on startup
114 elements = [
115 MARKABLE_ELEMENTS...
116 "*[#{ MARKABLE_ELEMENT_PROPERTIES.join(' or ') }]"
117 ]
118
119 reduce = (m, rule) ->
120 m.concat(["//#{ rule }", "//xhtml:#{ rule }"])
121 xpath = elements
122 .reduce(reduce, [])
123 .join(' | ')
124
125 namespaceResolver = (namespace) ->
126 if namespace == 'xhtml' then 'http://www.w3.org/1999/xhtml' else null
127
128 # The actual function that will return the desired elements
129 return (document, resultType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) ->
130 return document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null)
131
132
133 # Uses `element.getBoundingClientRect()`. If that does not return a visible rectange, then looks at
134 # the children of the markable node.
135 #
136 # The logic has been copied over from Vimiun originally.
137 getElementRect = (element) ->
138 document = element.ownerDocument
139 window = document.defaultView
140 docElem = document.documentElement
141 body = document.body
142
143 clientTop = docElem.clientTop or body?.clientTop or 0
144 clientLeft = docElem.clientLeft or body?.clientLeft or 0
145 scrollTop = window.pageYOffset or docElem.scrollTop
146 scrollLeft = window.pageXOffset or docElem.scrollLeft
147
148 clientRect = element.getBoundingClientRect()
149
150 if isRectOk(clientRect, window)
151 areaRatio = if (element instanceof HTMLAnchorElement) then 100 else 1
152 return {
153 top: clientRect.top + scrollTop - clientTop
154 left: clientRect.left + scrollLeft - clientLeft
155 width: clientRect.width
156 height: clientRect.height
157 area: areaRatio * (clientRect.width * clientRect.height)
158 }
159
160 # If the rect has 0 dimensions, then check what's inside.
161 # Floated or absolutely positioned elements are of particular interest.
162 if clientRect.width is 0 or clientRect.height is 0
163 for childElement in element.children
164 if computedStyle = window.getComputedStyle(childElement, null)
165 if computedStyle.getPropertyValue('float') != 'none' or \
166 computedStyle.getPropertyValue('position') == 'absolute'
167
168 return getElementRect(childElement)
169
170 return
171
172
173 # Checks if the given TextRectangle object qualifies
174 # for its own Marker with respect to the `window` object
175 isRectOk = (rect, window) ->
176 minimum = 2
177 rect.width > minimum and rect.height > minimum and \
178 rect.top > -minimum and rect.left > -minimum and \
179 rect.top < window.innerHeight - minimum and \
180 rect.left < window.innerWidth - minimum
181
182
183 exports.injectHints = injectHints
184 exports.removeHints = removeHints
Imprint / Impressum