aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dom_utils.coffee
diff options
context:
space:
mode:
authorPhil Crosby2012-06-10 23:52:43 -0700
committerPhil Crosby2012-06-12 22:00:35 -0700
commitf27180aefbc90c3bb3847faf68e58b440768e713 (patch)
treeed39fe4b2e2661a9defb4091952b0bc5bcca3935 /lib/dom_utils.coffee
parente8468e12f867cb71eb0d2b50c4904a1236b50e47 (diff)
downloadvimium-f27180aefbc90c3bb3847faf68e58b440768e713.tar.bz2
port dom_utils.js to coffeescript
Diffstat (limited to 'lib/dom_utils.coffee')
-rw-r--r--lib/dom_utils.coffee105
1 files changed, 105 insertions, 0 deletions
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
new file mode 100644
index 00000000..0ce9c50c
--- /dev/null
+++ b/lib/dom_utils.coffee
@@ -0,0 +1,105 @@
+DomUtils =
+ #
+ # Runs :callback if the DOM has loaded, otherwise runs it on load
+ #
+ documentReady: (->
+ loaded = false
+ window.addEventListener("DOMContentLoaded", -> loaded = true)
+ (callback) -> if loaded then callback() else window.addEventListener("DOMContentLoaded", callback)
+ )()
+
+ #
+ # 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()
+ clientRectsLength = clientRects.length
+
+ for i in [0...clientRectsLength]
+ if (clientRects[i].top < -2 || clientRects[i].top >= window.innerHeight - 4 ||
+ clientRects[i].left < -2 || clientRects[i].left >= window.innerWidth - 4)
+ continue
+
+ if (clientRects[i].width < 3 || clientRects[i].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 clientRects[i]
+
+ for i in [0...clientRectsLength]
+ # If the link has zero dimensions, it may be wrapping visible
+ # but floated elements. Check for this.
+ if (clientRects[i].width == 0 || clientRects[i].height == 0)
+ childrenCount = element.children.length
+ for j in [0...childrenCount]
+ computedStyle = window.getComputedStyle(element.children[j], null)
+ # Ignore child elements which are not floated and not absolutely positioned for parent elements with zero width/height
+ if (computedStyle.getPropertyValue('float') == 'none' && computedStyle.getPropertyValue('position') != 'absolute')
+ continue
+ childClientRect = this.getVisibleClientRect(element.children[j])
+ if (childClientRect == null)
+ continue
+ 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.body.appendChild(flashEl)
+ setTimeout((-> flashEl.parentNode.removeChild(flashEl)), 400)
+
+root = exports ? window
+root.DomUtils = DomUtils