In chapter 12. Elements, section 12.1 Interactability:
The first step of the algorithm to calculate in-view center point says:
1. Let rectangle be the first object of the DOMRect collection returned by calling getClientRects() on element.
That rectangle is then used to find the center of the element.
The problem is when that first object has zero width and height, when another of the returned rects in the collection contain the main part of the element. This happens when an inline element such as a <a> contains block-level elements.
When coordinates are not integer, the final floor operation will yield coordinates that are outside the element. The floor operation doesn't make a lot of sense to me (the user can click on non-integer CSS pixels when the dpi isn't 1), but just removing this floor operation wouldn't be good enough: clicks would work, but they wouldn't be at the center of the element.
So I think the step 1. above should be changed to:
1. Let rectangle be the first object of the DOMRect collection returned by calling getClientRects() on element, that has non-null width and height. If all have a null width and height, let rectangle be the first object of the returned collection.
(maybe this could be extracted to another algorithm)
Here is a test that demonstrates the problem in Firefox:
def test_inline_element_with_zero_size_rects_at_fractional_position(
session, inline
):
# An inline <a> wrapping a block-level child produces zero-size line-box
# rects at the start and end of its getClientRects() list: [0x0, 32x32,
# 0x0]. Placing the element at a fractional y-coordinate (via
# margin-top: 0.5px) means Math.floor on the zero-size rect's y lands
# outside the element and elementsFromPoint misses it.
# This test verifies that clicking such an element succeeds despite the
# zero-size first rect, i.e. that the non-zero rect is used to determine
# the element's position.
session.url = inline("""
<style>
body { margin: 0; }
</style>
<div style="margin-top: 0.5px">
<a id="link" href="#"
onclick="document.getElementById('log').textContent = 'clicked'">
<div style="width: 32px; height: 32px;"></div>
</a>
</div>
<div id="log"></div>
""")
element = session.find.css("#link", all=False)
# Verify the layout actually produces zero-size first rect; if this fails
# the test premise has changed and the test should be revisited.
first_rect = session.execute_script(
"""
const r = arguments[0].getClientRects()[0];
return { w: r.width, h: r.height };
""",
args=[element],
)
assert first_rect["w"] == 0 and first_rect["h"] == 0, (
"expected zero-size first rect, got {}x{}".format(
first_rect["w"], first_rect["h"]
)
)
response = element_click(session, element)
assert_success(response)
log = session.find.css("#log", all=False)
assert log.property("textContent") == "clicked"
In chapter 12. Elements, section 12.1 Interactability:
The first step of the algorithm to calculate
in-view center pointsays:That rectangle is then used to find the center of the element.
The problem is when that first object has zero width and height, when another of the returned rects in the collection contain the main part of the element. This happens when an inline element such as a
<a>contains block-level elements.When coordinates are not integer, the final
flooroperation will yield coordinates that are outside the element. Theflooroperation doesn't make a lot of sense to me (the user can click on non-integer CSS pixels when the dpi isn't 1), but just removing thisflooroperation wouldn't be good enough: clicks would work, but they wouldn't be at the center of the element.So I think the step 1. above should be changed to:
(maybe this could be extracted to another algorithm)
Here is a test that demonstrates the problem in Firefox: