diff options
| author | mrmr1993 | 2014-12-29 11:46:03 +0000 | 
|---|---|---|
| committer | mrmr1993 | 2014-12-29 11:46:03 +0000 | 
| commit | da04ee17472177b7ae0474712090d0604db2556e (patch) | |
| tree | 254221f11bf284f5dd1302bb583b50a6e49bda11 | |
| parent | a78d49c8a9cac57492f78a90246ce7695cf8e036 (diff) | |
| download | vimium-da04ee17472177b7ae0474712090d0604db2556e.tar.bz2 | |
Move link hint clickable element detection to its own function
| -rw-r--r-- | content_scripts/link_hints.coffee | 127 | 
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.  | 
