Skip to content

Commit 5a32ecd

Browse files
Daniel Skogly (poacher2k)smblott-github
Daniel Skogly (poacher2k)
authored andcommitted
Adds new check to see if an element is covered by another
(This message is written by @smblott-github.) This is @poacher2k's philc#2251 rebased, tweaked and squashed into a single commit. It provides a new method of detecting when an overlay hides a clickable element. There is some possibility that this may have to be reverted (for example, if we find we're missing too many important hints). For that reason, I squashed this into a single commit. Closes philc#2251. Adds another check to find out if elem is visible Also adds 0.1 pixels inwards to corner coordinates to handle some edge cases where another element would be found instead Removes redundant contains-check Reverts change that made tests fail Tweak philc#2251 (better overlay detection).
1 parent 3df2dc7 commit 5a32ecd

File tree

3 files changed

+39
-39
lines changed

3 files changed

+39
-39
lines changed

CREDITS

+1
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ Contributors:
4848
Scott Pinkelman <scott@scottpinkelman.com> (github: sco-tt)
4949
Darryl Pogue <darryl@dpogue.ca> (github: dpogue)
5050
tobimensch
51+
Daniel Skogly <daniel@pnkt.no> (github: poacher2k)
5152

5253
Feel free to add real names in addition to GitHub usernames.

content_scripts/link_hints.coffee

+29-39
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,6 @@ LocalHints =
558558
getVisibleClickable: (element) ->
559559
tagName = element.tagName.toLowerCase()
560560
isClickable = false
561-
onlyHasTabIndex = false
562561
possibleFalsePositive = false
563562
visibleElements = []
564563
reason = null
@@ -650,18 +649,17 @@ LocalHints =
650649
if not isClickable and 0 <= element.getAttribute("class")?.toLowerCase().indexOf "button"
651650
possibleFalsePositive = isClickable = true
652651

653-
# Elements with tabindex are sometimes useful, but usually not. We can treat them as second class
654-
# citizens when it improves UX, so take special note of them.
655-
tabIndexValue = element.getAttribute("tabindex")
656-
tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue
657-
unless isClickable or isNaN(tabIndex) or tabIndex < 0
658-
isClickable = onlyHasTabIndex = true
652+
isClickable ?= do ->
653+
# Elements with tabindex are sometimes useful.
654+
# NOTE(smblott) We should consider removing these, they are noise more often than not.
655+
tabIndexValue = element.getAttribute "tabindex"
656+
tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue
657+
not isNaN(tabIndex) and 0 <= tabIndex
659658

660659
if isClickable
661-
clientRect = DomUtils.getVisibleClientRect element, true
662-
if clientRect != null
663-
visibleElements.push {element: element, rect: clientRect, secondClassCitizen: onlyHasTabIndex,
664-
possibleFalsePositive, reason}
660+
rect = DomUtils.getVisibleClientRect element, true
661+
if rect != null
662+
visibleElements.push {element, rect, possibleFalsePositive, reason}
665663

666664
visibleElements
667665

@@ -709,34 +707,26 @@ LocalHints =
709707
false # This is not a false positive.
710708
element
711709

712-
# TODO(mrmr1993): Consider z-index. z-index affects behaviour as follows:
713-
# * The document has a local stacking context.
714-
# * An element with z-index specified
715-
# - sets its z-order position in the containing stacking context, and
716-
# - creates a local stacking context containing its children.
717-
# * An element (1) is shown above another element (2) if either
718-
# - in the last stacking context which contains both an ancestor of (1) and an ancestor of (2), the
719-
# ancestor of (1) has a higher z-index than the ancestor of (2); or
720-
# - in the last stacking context which contains both an ancestor of (1) and an ancestor of (2),
721-
# + the ancestors of (1) and (2) have equal z-index, and
722-
# + the ancestor of (1) appears later in the DOM than the ancestor of (2).
723-
#
724-
# Remove rects from elements where another clickable element lies above it.
725-
localHints = nonOverlappingElements = []
726-
while visibleElement = visibleElements.pop()
727-
rects = [visibleElement.rect]
728-
for {rect: negativeRect} in visibleElements
729-
# Subtract negativeRect from every rect in rects, and concatenate the arrays of rects that result.
730-
rects = [].concat (rects.map (rect) -> Rect.subtract rect, negativeRect)...
731-
if rects.length > 0
732-
nonOverlappingElements.push extend visibleElement, rect: rects[0]
733-
else
734-
# Every part of the element is covered by some other element, so just insert the whole element's
735-
# rect. Except for elements with tabIndex set (second class citizens); these are often more trouble
736-
# than they're worth.
737-
# TODO(mrmr1993): This is probably the wrong thing to do, but we don't want to stop being able to
738-
# click some elements that we could click before.
739-
nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen
710+
# Only keep elements where one of the corners or the center is visible.
711+
localHints = nonOverlappingElements =
712+
for visibleElement in visibleElements
713+
{rect, element} = visibleElement
714+
715+
# We use a small offset inwards to handle some edge cases where another element would be found
716+
# instead.
717+
xs = [rect.left + 0.1, rect.right - 0.1]
718+
ys = [rect.top + 0.1, rect.bottom - 0.1]
719+
720+
# We check the center and then the four corners.
721+
points = [[rect.left + (rect.width * 0.5), rect.top + (rect.height * 0.5)],
722+
[xs[0], ys[0]], [xs[0], ys[1]], [xs[1], ys[0]], [xs[1], ys[1]]]
723+
724+
continue unless do ->
725+
for [x, y] in points
726+
return true if DomUtils.elementIsVisible element, x, y
727+
false
728+
729+
visibleElement
740730

741731
# Position the rects within the window.
742732
for hint in nonOverlappingElements

lib/dom_utils.coffee

+9
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ DomUtils =
178178
rects.push {element: area, rect: rect} if rect and not isNaN rect.top
179179
rects
180180

181+
# Determine whether element is visible at point (x,y); that is, that no other element is overlaying it.
182+
# document.elementFromPoint() finds an element at an x,y location. Node.contains() checks whether an
183+
# element contains another. Note that someNode.contains(someNode) is true. If we do not find our element as
184+
# a descendant of any element we find, then we assume that it's completely covered.
185+
elementIsVisible: (element, x, y) ->
186+
elementFromPoint = document.elementFromPoint x, y
187+
elementFromPoint and
188+
(element.contains(elementFromPoint) or elementFromPoint.contains element)
189+
181190
#
182191
# Selectable means that we should use the simulateSelect method to activate the element instead of a click.
183192
#

0 commit comments

Comments
 (0)