LibWeb: Exclude UA internal shadow root elements in elementFromPoint()
When `elementFromPoint()` or `elementsFromPoint()` returns an element that is inside a UA internal shadow root, we now return the shadow host for that element.
This commit is contained in:
parent
5f434a442a
commit
c44b30f0f1
12 changed files with 338 additions and 19 deletions
|
|
@ -5755,6 +5755,17 @@ WebIDL::ExceptionOr<void> Document::set_design_mode(String const& design_mode)
|
|||
return {};
|
||||
}
|
||||
|
||||
static Element* retarget_from_ua_internal_shadow_root(Element& element)
|
||||
{
|
||||
auto* result = &element;
|
||||
while (auto shadow_root = result->containing_shadow_root()) {
|
||||
if (!shadow_root->is_user_agent_internal())
|
||||
break;
|
||||
result = shadow_root->host();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint
|
||||
Element const* Document::element_from_point(double x, double y)
|
||||
{
|
||||
|
|
@ -5772,19 +5783,20 @@ Element const* Document::element_from_point(double x, double y)
|
|||
|
||||
// 2. If there is a box in the viewport that would be a target for hit testing at coordinates x,y, when applying the transforms
|
||||
// that apply to the descendants of the viewport, return the associated element and terminate these steps.
|
||||
Optional<Painting::HitTestResult> hit_test_result;
|
||||
if (auto const* paintable_box = this->paintable_box(); paintable_box) {
|
||||
GC::Ptr<Element> hit_element;
|
||||
if (auto const* paintable_box = this->paintable_box()) {
|
||||
(void)paintable_box->hit_test(position, Painting::HitTestType::Exact, [&](Painting::HitTestResult result) {
|
||||
auto* dom_node = result.dom_node();
|
||||
if (dom_node && dom_node->is_element()) {
|
||||
hit_test_result = result;
|
||||
if (auto* element = as_if<Element>(result.dom_node())) {
|
||||
hit_element = element;
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
if (hit_test_result.has_value())
|
||||
return static_cast<Element*>(hit_test_result->dom_node());
|
||||
if (hit_element) {
|
||||
// AD-HOC: If element is inside a UA internal shadow root, retarget to the host.
|
||||
return retarget_from_ua_internal_shadow_root(*hit_element);
|
||||
}
|
||||
|
||||
// 3. If the document has a root element, return the root element and terminate these steps.
|
||||
if (auto const* root_element = document_element())
|
||||
|
|
@ -5817,8 +5829,13 @@ GC::RootVector<GC::Ref<Element>> Document::elements_from_point(double x, double
|
|||
// apply to the descendants of the viewport, append the associated element to sequence.
|
||||
if (auto const* paintable_box = this->paintable_box()) {
|
||||
(void)paintable_box->hit_test(position, Painting::HitTestType::Exact, [&](Painting::HitTestResult result) {
|
||||
if (auto* element = as_if<Element>(result.dom_node()))
|
||||
sequence.append(*element);
|
||||
if (auto* element = as_if<Element>(result.dom_node())) {
|
||||
// AD-HOC: If element is inside a UA internal shadow root, retarget to the host.
|
||||
element = retarget_from_ua_internal_shadow_root(*element);
|
||||
// AD-HOC: Avoid adding duplicates when multiple internal elements retarget to the same host.
|
||||
if (sequence.is_empty() || sequence.last() != element)
|
||||
sequence.append(*element);
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,3 +292,6 @@ Text/input/wpt-import/css/css-shapes/shape-outside/values/shape-outside-circle-0
|
|||
Text/input/wpt-import/css/css-shapes/shape-outside/values/shape-outside-ellipse-004.html
|
||||
Text/input/wpt-import/css/css-shapes/shape-outside/values/shape-outside-ellipse-005.html
|
||||
Text/input/wpt-import/css/css-shapes/shape-outside/values/shape-outside-inset-003.html
|
||||
|
||||
; This is used internally by cssom-view elementsFromPoint tests.
|
||||
Text/input/wpt-import/css/cssom-view/iframe.html
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 2 tests
|
||||
|
||||
2 Pass
|
||||
Pass elementsFromPoint on the root document for points in iframe elements
|
||||
Pass elementsFromPoint on inner documents
|
||||
|
|
@ -2,11 +2,11 @@ Harness status: OK
|
|||
|
||||
Found 6 tests
|
||||
|
||||
2 Pass
|
||||
4 Fail
|
||||
Fail elementsFromPoint for each corner of a simple div
|
||||
4 Pass
|
||||
2 Fail
|
||||
Pass elementsFromPoint for each corner of a simple div
|
||||
Fail elementsFromPoint for each corner of a div that has a pseudo-element
|
||||
Fail elementsFromPoint for each corner of a div that is between another div and its pseudo-element
|
||||
Fail elementsFromPoint for each corner of a div that has a margin
|
||||
Pass elementsFromPoint for each corner of a div that has a margin
|
||||
Pass elementsFromPoint for each corner of a div with pointer-events:none
|
||||
Pass elementsFromPoint for each corner of a div with a 3d transform
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 9 tests
|
||||
|
||||
7 Pass
|
||||
2 Fail
|
||||
Pass Negative co-ordinates
|
||||
Pass co-ordinates larger than the viewport
|
||||
Pass co-ordinates larger than the viewport from in iframe
|
||||
Pass Return first element that is the target for hit testing
|
||||
Pass First element to get mouse events with pointer-events css
|
||||
Pass SVG element at x,y
|
||||
Fail transformed element at x,y
|
||||
Pass no hit target at x,y
|
||||
Fail No viewport available
|
||||
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
|||
|
||||
Found 3 tests
|
||||
|
||||
2 Pass
|
||||
1 Fail
|
||||
3 Pass
|
||||
Pass The dialog element should support :open.
|
||||
Pass The details element should support :open.
|
||||
Fail The select element should support :open.
|
||||
Pass The select element should support :open.
|
||||
|
|
@ -2,9 +2,9 @@ Harness status: OK
|
|||
|
||||
Found 4 tests
|
||||
|
||||
1 Pass
|
||||
3 Fail
|
||||
Fail :user-valid selector should respond to user action
|
||||
2 Pass
|
||||
2 Fail
|
||||
Pass :user-valid selector should respond to user action
|
||||
Pass :user-valid selector properly interacts with submit & reset buttons
|
||||
Fail Checkboxes should match :user-valid after the user clicks on it.
|
||||
Fail Date inputs should match :user-valid after the user types a value into it.
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<!DOCTYPE HTML>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/elementsFromPoint.js"></script>
|
||||
<script>
|
||||
var loadedFrameCount = 0;
|
||||
var t1 = async_test('elementsFromPoint on the root document for points in iframe elements');
|
||||
var t2 = async_test('elementsFromPoint on inner documents');
|
||||
|
||||
function onFrameLoaded() {
|
||||
loadedFrameCount++;
|
||||
if (loadedFrameCount < 2)
|
||||
return;
|
||||
|
||||
var body = document.body;
|
||||
var html = document.documentElement;
|
||||
var iframe = document.getElementById('iframe');
|
||||
var scrollableIframe = document.getElementById('scrollableIframe');
|
||||
t1.step(function() {
|
||||
checkElementsFromPointFourCorners('document', 'iframe',
|
||||
[iframe, body, html],
|
||||
[iframe, body, html],
|
||||
[iframe, body, html],
|
||||
[scrollableIframe, iframe, body, html]);
|
||||
|
||||
checkElementsFromPointFourCorners('document', 'scrollableIframe',
|
||||
[scrollableIframe, iframe, body, html],
|
||||
[scrollableIframe, iframe, body, html],
|
||||
[scrollableIframe, iframe, body, html],
|
||||
[scrollableIframe, iframe, body, html]);
|
||||
});
|
||||
t1.done();
|
||||
|
||||
t2.step(function() {
|
||||
var iframeDocument = document.getElementById('iframe').contentDocument;
|
||||
var iframeRoot = iframeDocument.documentElement;
|
||||
var iframeBody = iframeDocument.body;
|
||||
var iframeDiv = iframeDocument.getElementById('div');
|
||||
checkElementsFromPointFourCorners('document.getElementById(\'iframe\').contentDocument', 'div',
|
||||
[iframeDiv, iframeBody, iframeRoot],
|
||||
[iframeDiv, iframeBody, iframeRoot],
|
||||
[iframeDiv, iframeBody, iframeRoot],
|
||||
[iframeDiv, iframeBody, iframeRoot]);
|
||||
|
||||
var iframeDocument2 = document.getElementById('scrollableIframe').contentDocument;
|
||||
var iframeRoot2 = iframeDocument2.documentElement;
|
||||
var iframeBody2 = iframeDocument2.body;
|
||||
var iframeSmallDiv = iframeDocument2.getElementById('small');
|
||||
var iframeBigDiv = iframeDocument2.getElementById('big');
|
||||
checkElementsFromPointFourCorners('document.getElementById(\'scrollableIframe\').contentDocument', 'big',
|
||||
[iframeSmallDiv, iframeBigDiv, iframeBody2, iframeRoot2],
|
||||
[iframeBigDiv, iframeBody2, iframeRoot2],
|
||||
[],
|
||||
[]);
|
||||
});
|
||||
t2.done();
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
height: 500px;
|
||||
}
|
||||
#iframe {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
#scrollableIframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate(50px, 50px);
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
</style>
|
||||
<iframe id="iframe" src="resources/iframe1.html"></iframe>
|
||||
<iframe id="scrollableIframe" src="resources/iframe2.html"></iframe>
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE html>
|
||||
<title>cssom-view - elementsFromPoint</title>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<style>
|
||||
.size {
|
||||
width:60px;
|
||||
height:60px;
|
||||
}
|
||||
.overlay {
|
||||
position:absolute;
|
||||
top:69px;
|
||||
pointer-events:none;
|
||||
}
|
||||
.purple {
|
||||
background-color: rebeccapurple;
|
||||
}
|
||||
.yellow {
|
||||
background-color: yellow;
|
||||
}
|
||||
.teal {
|
||||
background-color: teal;
|
||||
}
|
||||
.pink {
|
||||
background-color: pink;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id='purple' class="size purple" > </div>
|
||||
<div id='yellow' class="size yellow"> </div>
|
||||
<div id='teal' class="size overlay teal"> </div>
|
||||
<iframe id=iframe-1 src="iframe.html" style='display:none;position:absolute; left:300px;'></iframe>
|
||||
<iframe id=iframe-2 src="iframe.html" width="" height=""></iframe>
|
||||
<iframe id=iframe-3 width="" height=""></iframe>
|
||||
<svg id=squiggle xmlns="http://www.w3.org/2000/svg" height="98" width="500" viewBox="0 0 581 98">
|
||||
<path stroke-dashoffset="0.00" stroke-dasharray="" d="M62.9 14.9c-25-7.74-56.6 4.8-60.4 24.3-3.73 19.6 21.6 35 39.6 37.6 42.8 6.2 72.9-53.4 116-58.9 65-18.2 191 101 215 28.8 5-16.7-7-49.1-34-44-34 11.5-31 46.5-14 69.3 9.38 12.6 24.2 20.6 39.8 22.9 91.4 9.05 102-98.9 176-86.7 18.8 3.81 33 17.3 36.7 34.6 2.01 10.2.124 21.1-5.18 30.1" stroke="#000" stroke-width="4.3" fill="none">
|
||||
</path>
|
||||
</svg>
|
||||
<svg id=svg-transform width="180" height="140"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<!-- Now we add a text element and apply rotate and translate to both -->
|
||||
<rect x="50" y="50" height="60" width="60" style="stroke:#000; fill: #0086B2" transform="translate(30) rotate(45 50 50)"></rect>
|
||||
<text x="60" y="105" transform="translate(30) rotate(45 50 50)"> Hello! </text>
|
||||
|
||||
</svg>
|
||||
<div id='pink' class='size pink' style='transform: translate(10px)'> </div>
|
||||
<div id='anotherteal' class='size teal' style='pointer-events:none'>Another teal</div>
|
||||
<script>
|
||||
setup({explicit_done:true});
|
||||
window.onload = function () {
|
||||
test(function () {
|
||||
assert_array_equals(document.elementsFromPoint(-1, -1), [],
|
||||
"both co-ordinates passed in are negative so should have returned a []");
|
||||
assert_array_equals(document.elementsFromPoint(-1, -1), [],
|
||||
"x co-ordinates passed in are negative so should have returned a []");
|
||||
assert_array_equals(document.elementsFromPoint(-1, -1), [],
|
||||
"y co-ordinates passed in are negative so should have returned a []");
|
||||
}, "Negative co-ordinates");
|
||||
|
||||
test(function () {
|
||||
var viewportX = window.innerWidth;
|
||||
var viewportY = window.innerHeight;
|
||||
assert_array_equals(document.elementsFromPoint(viewportX + 100, 10), [],
|
||||
"X co-ordinates larger than viewport so should have returned a []");
|
||||
assert_array_equals(document.elementsFromPoint(10, viewportY + 100), [],
|
||||
"Y co-ordinates larger than viewport so should have returned a []");
|
||||
assert_array_equals(document.elementsFromPoint(viewportX + 100, viewportY + 100), [],
|
||||
"X, Y co-ordinates larger than viewport so should have returned a []");
|
||||
}, "co-ordinates larger than the viewport");
|
||||
|
||||
test(function () {
|
||||
var viewportX = window.frames[1].innerWidth;
|
||||
var viewportY = window.frames[1].innerHeight;
|
||||
var iframeRect = document.getElementById('iframe-2').getBoundingClientRect();
|
||||
assert_array_equals([], window.frames[1].document.elementsFromPoint(iframeRect.right + viewportX + 100, 10),
|
||||
"X co-ordinates larger than viewport so should have returned a []");
|
||||
assert_array_equals([], window.frames[1].document.elementsFromPoint(10, iframeRect.bottom + viewportY + 100),
|
||||
"Y co-ordinates larger than viewport so should have returned a []");
|
||||
assert_array_equals([], window.frames[1].document.elementsFromPoint(iframeRect.right + viewportX + 100,
|
||||
iframeRect.bottom + viewportY + 100),
|
||||
"X, Y co-ordinates larger than viewport so should have returned a []");
|
||||
}, "co-ordinates larger than the viewport from in iframe");
|
||||
|
||||
test(function () {
|
||||
assert_array_equals(document.elementsFromPoint(10, 10),
|
||||
[document.getElementById('purple'), document.body, document.querySelector('html')],
|
||||
"Should have returned a sequence with `[purple element, document.body, html]`");
|
||||
}, "Return first element that is the target for hit testing");
|
||||
|
||||
test(function () {
|
||||
assert_array_equals(document.elementsFromPoint(10, 80),
|
||||
[document.getElementById('yellow'), document.body, document.querySelector('html')],
|
||||
"Should have returned a sequence with `[yellow element, document.body, html]`");
|
||||
}, "First element to get mouse events with pointer-events css");
|
||||
|
||||
test(function () {
|
||||
var svg = document.getElementById('squiggle');
|
||||
var svgRect = svg.getBoundingClientRect();
|
||||
assert_array_equals(document.elementsFromPoint(svgRect.left + Math.round(svgRect.width/2),
|
||||
svgRect.top + Math.round(svgRect.height/2)),
|
||||
[svg, document.body, document.querySelector('html')],
|
||||
"Should have returned a sequence with [svg, body, html]");
|
||||
}, "SVG element at x,y");
|
||||
|
||||
test(function () {
|
||||
var svg = document.getElementById('svg-transform');
|
||||
var svgRect = svg.getBoundingClientRect();
|
||||
assert_array_equals(document.elementsFromPoint(svgRect.left + Math.round(svgRect.width/2),
|
||||
svgRect.top + Math.round(svgRect.height/2)),
|
||||
[svg.querySelector("rect"), svg, document.body, document.querySelector('html')],
|
||||
"Should have returned [svg rect, svg, body, html]");
|
||||
|
||||
var pink = document.getElementById('pink');
|
||||
var pinkRect = pink.getBoundingClientRect();
|
||||
assert_array_equals(document.elementsFromPoint(pinkRect.left + Math.round(pinkRect.width/2),
|
||||
pinkRect.top + Math.round(pinkRect.height/2)),
|
||||
[pink, document.body, document.querySelector('html')],
|
||||
"Should have returned a sequence with [pink, body, html]");
|
||||
|
||||
}, "transformed element at x,y");
|
||||
|
||||
test(function () {
|
||||
var anotherteal = document.getElementById('anotherteal');
|
||||
var anothertealRect = anotherteal.getBoundingClientRect();
|
||||
assert_array_equals(document.elementsFromPoint(anothertealRect.left + Math.round(anothertealRect.width/2),
|
||||
anothertealRect.top + Math.round(anothertealRect.height/2)),
|
||||
[document.body, document.querySelector('html')],
|
||||
"Should have returned the sequence [body, html]");
|
||||
|
||||
var doc = frames[1].document;
|
||||
assert_array_equals([doc.querySelector('html')], doc.elementsFromPoint(0, 100),
|
||||
"Should have returned the sequence [html]")
|
||||
|
||||
var doc = frames[2].document;
|
||||
doc.removeChild(doc.documentElement);
|
||||
assert_array_equals(doc.elementsFromPoint(0, 0), [],
|
||||
"Should have returned [] as no root element");
|
||||
|
||||
}, "no hit target at x,y");
|
||||
|
||||
test(function () {
|
||||
var doc = document.implementation.createHTMLDocument('foo');
|
||||
assert_array_equals(doc.elementsFromPoint(0, 0), [],
|
||||
"Should have returned []")
|
||||
}, "No viewport available");
|
||||
done();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<style>
|
||||
html, body { padding:9px; border:1px solid }
|
||||
</style>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: red;
|
||||
}
|
||||
</style>
|
||||
<div id='div'></div>
|
||||
<script>
|
||||
window.onload = window.parent.onFrameLoaded();
|
||||
</script>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE HTML>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#big {
|
||||
width: 125px;
|
||||
height: 500px;
|
||||
background: blue;
|
||||
}
|
||||
#small {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
<div id='big'></div>
|
||||
<div id='small'></div>
|
||||
<script>
|
||||
window.onload = window.parent.onFrameLoaded();
|
||||
</script>
|
||||
Loading…
Reference in a new issue