mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-23 09:46:04 -05:00
LibWeb: Transform PaintableBox::hit_test
positions
Elements with transforms were tested on their pre-transformed positions, causing incorrect hits. Copy the position transformation done in `StackingContext::hit_test` to ensure that hit tests are done on the _actual_ position.
This commit is contained in:
parent
d2ca522540
commit
a0fb092d94
Notes:
github-actions[bot]
2024-11-23 21:07:27 +00:00
Author: https://github.com/yyny Commit: https://github.com/LadybirdBrowser/ladybird/commit/a0fb092d942 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2534 Reviewed-by: https://github.com/kalenikaliaksandr
3 changed files with 42 additions and 12 deletions
|
@ -917,6 +917,13 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
||||||
auto position_adjusted_by_scroll_offset = position;
|
auto position_adjusted_by_scroll_offset = position;
|
||||||
position_adjusted_by_scroll_offset.translate_by(-cumulative_offset_of_enclosing_scroll_frame());
|
position_adjusted_by_scroll_offset.translate_by(-cumulative_offset_of_enclosing_scroll_frame());
|
||||||
|
|
||||||
|
// NOTE: This CSSPixels -> Float -> CSSPixels conversion is because we can't AffineTransform::map() a CSSPixelPoint.
|
||||||
|
Gfx::FloatPoint offset_position {
|
||||||
|
(position_adjusted_by_scroll_offset.x() - transform_origin().x()).to_float(),
|
||||||
|
(position_adjusted_by_scroll_offset.y() - transform_origin().y()).to_float()
|
||||||
|
};
|
||||||
|
auto transformed_position_adjusted_by_scroll_offset = combined_css_transform().inverse().value_or({}).map(offset_position).to_type<CSSPixels>() + transform_origin();
|
||||||
|
|
||||||
// TextCursor hit testing mode should be able to place cursor in contenteditable elements even if they are empty
|
// TextCursor hit testing mode should be able to place cursor in contenteditable elements even if they are empty
|
||||||
auto is_editable = layout_node_with_style_and_box_metrics().dom_node() && layout_node_with_style_and_box_metrics().dom_node()->is_editable();
|
auto is_editable = layout_node_with_style_and_box_metrics().dom_node() && layout_node_with_style_and_box_metrics().dom_node()->is_editable();
|
||||||
if (is_editable && m_fragments.is_empty() && !has_children() && type == HitTestType::TextCursor) {
|
if (is_editable && m_fragments.is_empty() && !has_children() && type == HitTestType::TextCursor) {
|
||||||
|
@ -934,7 +941,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
||||||
return PaintableBox::hit_test(position, type, callback);
|
return PaintableBox::hit_test(position, type, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hit_test_scrollbars(position_adjusted_by_scroll_offset, callback) == TraversalDecision::Break)
|
if (hit_test_scrollbars(transformed_position_adjusted_by_scroll_offset, callback) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
|
|
||||||
for (auto const* child = last_child(); child; child = child->previous_sibling()) {
|
for (auto const* child = last_child(); child; child = child->previous_sibling()) {
|
||||||
|
@ -946,10 +953,10 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
||||||
if (fragment.paintable().has_stacking_context())
|
if (fragment.paintable().has_stacking_context())
|
||||||
continue;
|
continue;
|
||||||
auto fragment_absolute_rect = fragment.absolute_rect();
|
auto fragment_absolute_rect = fragment.absolute_rect();
|
||||||
if (fragment_absolute_rect.contains(position_adjusted_by_scroll_offset)) {
|
if (fragment_absolute_rect.contains(transformed_position_adjusted_by_scroll_offset)) {
|
||||||
if (fragment.paintable().hit_test(position, type, callback) == TraversalDecision::Break)
|
if (fragment.paintable().hit_test(transformed_position_adjusted_by_scroll_offset, type, callback) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset), 0, 0 };
|
HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(transformed_position_adjusted_by_scroll_offset), 0, 0 };
|
||||||
if (callback(hit_test_result) == TraversalDecision::Break)
|
if (callback(hit_test_result) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
} else if (type == HitTestType::TextCursor) {
|
} else if (type == HitTestType::TextCursor) {
|
||||||
|
@ -972,30 +979,30 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
||||||
// the place to place the cursor. To determine the best place, we first find the closest fragment horizontally to
|
// the place to place the cursor. To determine the best place, we first find the closest fragment horizontally to
|
||||||
// the cursor. If we could not find one, then find for the closest vertically above the cursor.
|
// the cursor. If we could not find one, then find for the closest vertically above the cursor.
|
||||||
// If we knew the direction of selection, we would look above if selecting upward.
|
// If we knew the direction of selection, we would look above if selecting upward.
|
||||||
if (fragment_absolute_rect.bottom() - 1 <= position_adjusted_by_scroll_offset.y()) { // fully below the fragment
|
if (fragment_absolute_rect.bottom() - 1 <= transformed_position_adjusted_by_scroll_offset.y()) { // fully below the fragment
|
||||||
HitTestResult hit_test_result {
|
HitTestResult hit_test_result {
|
||||||
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
||||||
.index_in_node = fragment.start() + fragment.length(),
|
.index_in_node = fragment.start() + fragment.length(),
|
||||||
.vertical_distance = position_adjusted_by_scroll_offset.y() - fragment_absolute_rect.bottom(),
|
.vertical_distance = transformed_position_adjusted_by_scroll_offset.y() - fragment_absolute_rect.bottom(),
|
||||||
};
|
};
|
||||||
if (callback(hit_test_result) == TraversalDecision::Break)
|
if (callback(hit_test_result) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
} else if (fragment_absolute_rect.top() <= position_adjusted_by_scroll_offset.y()) { // vertically within the fragment
|
} else if (fragment_absolute_rect.top() <= transformed_position_adjusted_by_scroll_offset.y()) { // vertically within the fragment
|
||||||
if (position_adjusted_by_scroll_offset.x() < fragment_absolute_rect.left()) {
|
if (transformed_position_adjusted_by_scroll_offset.x() < fragment_absolute_rect.left()) {
|
||||||
HitTestResult hit_test_result {
|
HitTestResult hit_test_result {
|
||||||
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
||||||
.index_in_node = fragment.start(),
|
.index_in_node = fragment.start(),
|
||||||
.vertical_distance = 0,
|
.vertical_distance = 0,
|
||||||
.horizontal_distance = fragment_absolute_rect.left() - position_adjusted_by_scroll_offset.x(),
|
.horizontal_distance = fragment_absolute_rect.left() - transformed_position_adjusted_by_scroll_offset.x(),
|
||||||
};
|
};
|
||||||
if (callback(hit_test_result) == TraversalDecision::Break)
|
if (callback(hit_test_result) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
} else if (position_adjusted_by_scroll_offset.x() > fragment_absolute_rect.right()) {
|
} else if (transformed_position_adjusted_by_scroll_offset.x() > fragment_absolute_rect.right()) {
|
||||||
HitTestResult hit_test_result {
|
HitTestResult hit_test_result {
|
||||||
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
||||||
.index_in_node = fragment.start() + fragment.length(),
|
.index_in_node = fragment.start() + fragment.length(),
|
||||||
.vertical_distance = 0,
|
.vertical_distance = 0,
|
||||||
.horizontal_distance = position_adjusted_by_scroll_offset.x() - fragment_absolute_rect.right(),
|
.horizontal_distance = transformed_position_adjusted_by_scroll_offset.x() - fragment_absolute_rect.right(),
|
||||||
};
|
};
|
||||||
if (callback(hit_test_result) == TraversalDecision::Break)
|
if (callback(hit_test_result) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
|
@ -1005,7 +1012,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stacking_context() && is_visible() && absolute_border_box_rect().contains(position_adjusted_by_scroll_offset.x(), position_adjusted_by_scroll_offset.y())) {
|
if (!stacking_context() && is_visible() && absolute_border_box_rect().contains(transformed_position_adjusted_by_scroll_offset.x(), transformed_position_adjusted_by_scroll_offset.y())) {
|
||||||
if (callback(HitTestResult { const_cast<PaintableWithLines&>(*this) }) == TraversalDecision::Break)
|
if (callback(HitTestResult { const_cast<PaintableWithLines&>(*this) }) == TraversalDecision::Break)
|
||||||
return TraversalDecision::Break;
|
return TraversalDecision::Break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
clicked the <input>
|
22
Tests/LibWeb/Text/input/hit_testing/css-transforms.html
Normal file
22
Tests/LibWeb/Text/input/hit_testing/css-transforms.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<div>
|
||||||
|
<input id="input" type="text">
|
||||||
|
<div id="div" style="position: absolute; transform: translate(0px, 42px);">
|
||||||
|
This text should not be hit when clicking the input.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
asyncTest((done) => {
|
||||||
|
document.getElementById("input").addEventListener("click", () => {
|
||||||
|
println("clicked the <input>");
|
||||||
|
});
|
||||||
|
document.getElementById("div").addEventListener("click", () => {
|
||||||
|
println("clicked the <div>");
|
||||||
|
});
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
internals.click(12, 12);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Reference in a new issue