aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormrmr19932014-12-29 11:46:03 +0000
committermrmr19932014-12-29 11:46:03 +0000
commitda04ee17472177b7ae0474712090d0604db2556e (patch)
tree254221f11bf284f5dd1302bb583b50a6e49bda11
parenta78d49c8a9cac57492f78a90246ce7695cf8e036 (diff)
downloadvimium-da04ee17472177b7ae0474712090d0604db2556e.tar.bz2
Move link hint clickable element detection to its own function
-rw-r--r--content_scripts/link_hints.coffee127
1 files changed, 68 insertions, 59 deletions
diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee
index b605c2ec..ea4be397 100644
--- a/content_scripts/link_hints.coffee
+++ b/content_scripts/link_hints.coffee
@@ -125,6 +125,72 @@ LinkHints =
marker
#
+ # Determine whether the element is visible and clickable. If it is, return the element and the rect
+ # bounding the element in the viewport.
+ getVisibleClickable: (element) ->
+ tagName = element.tagName.toLowerCase()
+ isClickable = false
+ onlyHasTabIndex = false
+
+ # Insert area elements that provide click functionality to an img.
+ if tagName == "img"
+ mapName = element.getAttribute "usemap"
+ if mapName
+ imgClientRects = element.getClientRects()
+ mapName = mapName.replace(/^#/, "").replace("\"", "\\\"")
+ map = document.querySelector "map[name=\"#{mapName}\"]"
+ if map and imgClientRects.length > 0
+ areas = map.getElementsByTagName "area"
+ areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas
+ visibleElements.push areasAndRects...
+
+ # Check aria properties to see if the element should be ignored.
+ if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or
+ element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"])
+ return null # This element should never have a link hint.
+
+ # Check for attributes that make an element clickable regardless of its tagName.
+ if (element.hasAttribute("onclick") or
+ element.getAttribute("role")?.toLowerCase() in ["button", "link"] or
+ element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or
+ element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"])
+ isClickable = true
+
+ # Check for jsaction event listeners on the element.
+ if element.hasAttribute "jsaction"
+ jsactionRules = element.getAttribute("jsaction").split(";")
+ for jsactionRule in jsactionRules
+ ruleSplit = jsactionRule.split ":"
+ isClickable ||= ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none")
+
+ # Check for tagNames which are natively clickable.
+ switch tagName
+ when "a"
+ isClickable = true
+ when "textarea"
+ isClickable ||= not element.disabled and not element.readOnly
+ when "input"
+ isClickable ||= not (element.getAttribute("type")?.toLowerCase() == "hidden" or
+ element.disabled or
+ (element.readOnly and DomUtils.isSelectable element))
+ when "button", "select"
+ isClickable ||= not element.disabled
+
+ # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class
+ # citizens when it improves UX, so take special note of them.
+ tabIndexValue = element.getAttribute("tabindex")
+ tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue
+ unless isClickable or isNaN(tabIndex) or tabIndex < 0
+ isClickable = onlyHasTabIndex = true
+
+ return null unless isClickable # The element isn't clickable.
+ clientRect = DomUtils.getVisibleClientRect element
+ if clientRect == null
+ null
+ else
+ {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex}
+
+ #
# Returns all clickable elements that are not hidden and are in the current viewport, along with rectangles
# at which (parts of) the elements are displayed.
# In the process, we try to find rects where elements do not overlap so that link hints are unambiguous.
@@ -141,65 +207,8 @@ LinkHints =
# NOTE(mrmr1993): Our previous method (combined XPath and DOM traversal for jsaction) couldn't provide
# this, so it's necessary to check whether elements are clickable in order, as we do below.
for element in elements
- tagName = element.tagName.toLowerCase()
- isClickable = false
- onlyHasTabIndex = false
-
- # Insert area elements that provide click functionality to an img.
- if tagName == "img"
- mapName = element.getAttribute "usemap"
- if mapName
- imgClientRects = element.getClientRects()
- mapName = mapName.replace(/^#/, "").replace("\"", "\\\"")
- map = document.querySelector "map[name=\"#{mapName}\"]"
- if map and imgClientRects.length > 0
- areas = map.getElementsByTagName "area"
- areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas
- visibleElements.push areasAndRects...
-
- # Check aria properties to see if the element should be ignored.
- if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or
- element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"])
- continue # No point continuing the loop; this element should never have a link hint
-
- # Check for attributes that make an element clickable regardless of its tagName.
- if (element.hasAttribute("onclick") or
- element.getAttribute("role")?.toLowerCase() in ["button", "link"] or
- element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or
- element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"])
- isClickable = true
-
- # Check for jsaction event listeners on the element.
- if element.hasAttribute "jsaction"
- jsactionRules = element.getAttribute("jsaction").split(";")
- for jsactionRule in jsactionRules
- ruleSplit = jsactionRule.split ":"
- isClickable ||= ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none")
-
- # Check for tagNames which are natively clickable.
- switch tagName
- when "a"
- isClickable = true
- when "textarea"
- isClickable ||= not element.disabled and not element.readOnly
- when "input"
- isClickable ||= not (element.getAttribute("type")?.toLowerCase() == "hidden" or
- element.disabled or
- (element.readOnly and DomUtils.isSelectable element))
- when "button", "select"
- isClickable ||= not element.disabled
-
- # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class
- # citizens when it improves UX, so take special note of them.
- tabIndexValue = element.getAttribute("tabindex")
- tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue
- unless isClickable or isNaN(tabIndex) or tabIndex < 0
- isClickable = onlyHasTabIndex = true
-
- continue unless isClickable # If the element isn't clickable, do nothing.
- clientRect = DomUtils.getVisibleClientRect element
- if clientRect != null
- visibleElements.push {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex}
+ visibleElement = @getVisibleClickable element
+ visibleElements.push visibleElement if visibleElement?
# TODO(mrmr1993): Consider z-index. z-index affects behviour as follows:
# * The document has a local stacking context.