/
dom_utils.coffee
139 lines (120 loc) · 5.29 KB
/
dom_utils.coffee
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
DomUtils =
#
# Adds the given CSS to the page.
#
addCssToPage: (css, id) ->
return if document.getElementById(id)
head = document.getElementsByTagName("head")[0]
if (!head)
head = document.createElement("head")
document.documentElement.appendChild(head)
style = document.createElement("style")
style.id = id
style.type = "text/css"
style.appendChild(document.createTextNode(css))
head.appendChild(style)
#
# 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