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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
DomUtils =
#
# Runs :callback if the DOM has loaded, otherwise runs it on load
#
documentReady: do ->
loaded = false
window.addEventListener("DOMContentLoaded", -> loaded = true)
(callback) -> if loaded then callback() else window.addEventListener("DOMContentLoaded", callback)
#
# Adds a list of elements to a page.
# Note that adding these nodes all at once (via the parent div) is significantly faster than one-by-one.
#
addElementList: (els, overlayOptions) ->
parent = document.createElement("div")
parent.id = overlayOptions.id if overlayOptions.id?
parent.className = overlayOptions.className if overlayOptions.className?
parent.appendChild(el) for el in els
document.documentElement.appendChild(parent)
parent
#
# Remove an element from its DOM tree.
#
removeElement: (el) -> el.parentNode.removeChild el
#
# Takes an array of XPath selectors, adds the necessary namespaces (currently only XHTML), and applies them
# to the document root. The namespaceResolver in evaluateXPath should be kept in sync with the namespaces
# here.
#
makeXPath: (elementArray) ->
xpath = []
for i of elementArray
xpath.push("//" + elementArray[i], "//xhtml:" + elementArray[i])
xpath.join(" | ")
evaluateXPath: (xpath, resultType) ->
namespaceResolver = (namespace) ->
if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null
document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null)
#
# Returns the first visible clientRect of an element if it exists. Otherwise it returns null.
#
getVisibleClientRect: (element) ->
# Note: this call will be expensive if we modify the DOM in between calls.
clientRects = element.getClientRects()
for clientRect in clientRects
if (clientRect.top < -2 || clientRect.top >= window.innerHeight - 4 ||
clientRect.left < -2 || clientRect.left >= window.innerWidth - 4)
continue
if (clientRect.width < 3 || clientRect.height < 3)
continue
# eliminate invisible elements (see test_harnesses/visibility_test.html)
computedStyle = window.getComputedStyle(element, null)
if (computedStyle.getPropertyValue('visibility') != 'visible' ||
computedStyle.getPropertyValue('display') == 'none')
continue
return clientRect
for clientRect in clientRects
# If the link has zero dimensions, it may be wrapping visible
# but floated elements. Check for this.
if (clientRect.width == 0 || clientRect.height == 0)
for child in element.children
computedStyle = window.getComputedStyle(child, null)
# Ignore child elements which are not floated and not absolutely positioned for parent elements with
# zero width/height
continue if (computedStyle.getPropertyValue('float') == 'none' &&
computedStyle.getPropertyValue('position') != 'absolute')
childClientRect = @getVisibleClientRect(child)
continue if (childClientRect == null)
return childClientRect
null
#
# Selectable means the element has a text caret; this is not the same as "focusable".
#
isSelectable: (element) ->
selectableTypes = ["search", "text", "password"]
(element.nodeName.toLowerCase() == "input" && selectableTypes.indexOf(element.type) >= 0) ||
element.nodeName.toLowerCase() == "textarea"
simulateSelect: (element) ->
element.focus()
# When focusing a textbox, put the selection caret at the end of the textbox's contents.
element.setSelectionRange(element.value.length, element.value.length)
simulateClick: (element, modifiers) ->
modifiers ||= {}
eventSequence = ["mouseover", "mousedown", "mouseup", "click"]
for event in eventSequence
mouseEvent = document.createEvent("MouseEvents")
mouseEvent.initMouseEvent(event, true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, false, false,
modifiers.metaKey, 0, null)
# Debugging note: Firefox will not execute the element's default action if we dispatch this click event,
# but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
element.dispatchEvent(mouseEvent)
# momentarily flash a rectangular border to give user some visual feedback
flashRect: (rect) ->
flashEl = document.createElement("div")
flashEl.id = "vimiumFlash"
flashEl.className = "vimiumReset"
flashEl.style.left = rect.left + window.scrollX + "px"
flashEl.style.top = rect.top + window.scrollY + "px"
flashEl.style.width = rect.width + "px"
flashEl.style.height = rect.height + "px"
document.documentElement.appendChild(flashEl)
setTimeout((-> DomUtils.removeElement flashEl), 400)
suppressEvent: (event) ->
event.preventDefault()
event.stopPropagation()
root = exports ? window
root.DomUtils = DomUtils
|