DomUtils =
#
# Runs :callback if the DOM has loaded, otherwise runs it on load
#
documentReady: do ->
[isReady, callbacks] = [document.readyState != "loading", []]
unless isReady
window.addEventListener "DOMContentLoaded", onDOMContentLoaded = forTrusted ->
window.removeEventListener "DOMContentLoaded", onDOMContentLoaded
isReady = true
callback() for callback in callbacks
callbacks = null
(callback) -> if isReady then callback() else callbacks.push callback
documentComplete: do ->
[isComplete, callbacks] = [document.readyState == "complete", []]
unless isComplete
window.addEventListener "load", onLoad = forTrusted ->
window.removeEventListener "load", onLoad
isComplete = true
callback() for callback in callbacks
callbacks = null
(callback) -> if isComplete then callback() else callbacks.push callback
createElement: (tagName) ->
element = document.createElement tagName
if element instanceof HTMLElement
# The document namespace provides (X)HTML elements, so we can use them directly.
@createElement = (tagName) -> document.createElement tagName
element
else
# The document namespace doesn't give (X)HTML elements, so we create them with the correct namespace
# manually.
@createElement = (tagName) ->
document.createElementNS "http://www.w3.org/1999/xhtml", tagName
@createElement(tagName)
#
# 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 = @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
#
# Test whether the current frame is the top/main frame.
#
isTopFrame: ->
window.top == window.self
#
# 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 element in elementArray
xpath.push(".//" + element, ".//xhtml:" + element)
xpath.join(" | ")
# Evaluates an XPath on the whole document, or on the contents of the fullscreen element if an element is
# fullscreen.
evaluateXPath: (xpath, resultType) ->
contextNode =
if document.webkitIsFullScreen then document.webkitFullscreenElement else document.documentElement
namespaceResolver = (namespace) ->
if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null
document.evaluate(xpath, contextNode, namespaceResolver, resultType, null)
#
# Returns the first visible clientRect of an element if it exists. Otherwise it returns null.
#
# WARNING: If testChildren = true then the rects of visible (eg. floated) children may be returned instead.
# This is used for LinkHints and focusInput, **BUT IS UNSUITABLE FOR MOST OTHER PURPOSES**.
#
getVisibleClientRect: (element, testChildren = false) ->
# Note: this call will be expensive if we modify the DOM in between calls.
clientRects = (Rect.copy clientRect for clientRect in element.getClientRects())
# Inline elements with font-size: 0px; will declare a height of zero, even if a child with non-zero
# font-size contains text.
isInlineZeroHeight = ->
elementComputedStyle = window.getComputedStyle element, null
isInlineZeroFontSize = (0 == elementComputedStyle.getPropertyValue("display").indexOf "inline") and
(elementComputedStyle.getPropertyValue("font-size") == "0px")
# Override the function to return this value for the rest of this context.
isInlineZeroHeight = -> isInlineZeroFontSize
isInlineZeroFontSize
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 or clientRect.height == 0) and testChildren
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, as long as the case described at isInlineZeroHeight does not apply.
# NOTE(mrmr1993): This ignores floated/absolutely positioned descendants nested within inline
# children.
continue if (computedStyle.getPropertyValue("float") == "none" and
not (computedStyle.getPropertyValue("position") in ["absolute", "fixed"]) and
not (clientRect.height == 0 and isInlineZeroHeight() and
0 == computedStyle.getPropertyValue("display").indexOf "inline"))
childClientRect = @getVisibleClientRect child, true
continue if childClientRect == null or childClientRect.width < 3 or childClientRect.height < 3
return childClientRect
else
clientRect = @cropRectToVisible clientRect
continue if clientRect == null or clientRect.width < 3 or clientRect.height < 3
# eliminate invisible elements (see test_harnesses/visibility_test.html)
computedStyle = window.getComputedStyle(element, null)
continue if computedStyle.getPropertyValue('visibility') != 'visible'
return clientRect
null
#
# Bounds the rect by the current viewport dimensions. If the rect is offscreen or has a height or width < 3
# then null is returned instead of a rect.
#
cropRectToVisible: (rect) ->
boundedRect = Rect.create(
Math.max(rect.left, 0)
Math.max(rect.top, 0)
rect.right
rect.bottom
)
if boundedRect.top >= window.innerHeight - 4 or boundedRect.left >= window.innerWidth - 4
null
else
boundedRect
#
# Get the client rects for the elements in a