1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 19:37:36 +00:00

LibWeb: Make hit testing respect hidden overflow

This commit is contained in:
Igor Pissolati 2022-07-03 18:34:51 -03:00 committed by Andreas Kling
parent 4b70ddf5a0
commit 44057c9482
2 changed files with 48 additions and 21 deletions

View file

@ -13,6 +13,12 @@
namespace Web::Painting { namespace Web::Painting {
enum class TraversalDecision {
Continue,
SkipChildrenAndContinue,
Break,
};
enum class PaintPhase { enum class PaintPhase {
Background, Background,
Border, Border,
@ -53,27 +59,27 @@ public:
Paintable const* next_sibling() const; Paintable const* next_sibling() const;
template<typename U, typename Callback> template<typename U, typename Callback>
IterationDecision for_each_in_inclusive_subtree_of_type(Callback callback) const TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) const
{ {
if (is<U>(*this)) { if (is<U>(*this)) {
if (callback(static_cast<const U&>(*this)) == IterationDecision::Break) if (auto decision = callback(static_cast<const U&>(*this)); decision != TraversalDecision::Continue)
return IterationDecision::Break; return decision;
} }
for (auto* child = first_child(); child; child = child->next_sibling()) { for (auto* child = first_child(); child; child = child->next_sibling()) {
if (child->template for_each_in_inclusive_subtree_of_type<U>(callback) == IterationDecision::Break) if (child->template for_each_in_inclusive_subtree_of_type<U>(callback) == TraversalDecision::Break)
return IterationDecision::Break; return TraversalDecision::Break;
} }
return IterationDecision::Continue; return TraversalDecision::Continue;
} }
template<typename U, typename Callback> template<typename U, typename Callback>
IterationDecision for_each_in_subtree_of_type(Callback callback) const TraversalDecision for_each_in_subtree_of_type(Callback callback) const
{ {
for (auto* child = first_child(); child; child = child->next_sibling()) { for (auto* child = first_child(); child; child = child->next_sibling()) {
if (child->template for_each_in_inclusive_subtree_of_type<U>(callback) == IterationDecision::Break) if (child->template for_each_in_inclusive_subtree_of_type<U>(callback) == TraversalDecision::Break)
return IterationDecision::Break; return TraversalDecision::Break;
} }
return IterationDecision::Continue; return TraversalDecision::Continue;
} }
virtual void paint(PaintContext&, PaintPhase) const { } virtual void paint(PaintContext&, PaintPhase) const { }

View file

@ -288,20 +288,27 @@ Gfx::FloatPoint StackingContext::transform_origin() const
Optional<HitTestResult> StackingContext::hit_test(Gfx::FloatPoint const& position, HitTestType type) const Optional<HitTestResult> StackingContext::hit_test(Gfx::FloatPoint const& position, HitTestType type) const
{ {
if (!m_box.is_visible())
return {};
if (m_box.computed_values().z_index().value_or(0) < 0)
return {};
auto transform_origin = this->transform_origin(); auto transform_origin = this->transform_origin();
auto affine_transform = combine_transformations_2d(m_box.computed_values().transformations()); auto affine_transform = combine_transformations_2d(m_box.computed_values().transformations());
auto transformed_position = affine_transform.inverse().value_or({}).map(position - transform_origin) + transform_origin; auto transformed_position = affine_transform.inverse().value_or({}).map(position - transform_origin) + transform_origin;
// FIXME: Support more overflow variations.
if (paintable().computed_values().overflow_x() == CSS::Overflow::Hidden && paintable().computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paintable().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
return {};
}
// NOTE: Hit testing basically happens in reverse painting order. // NOTE: Hit testing basically happens in reverse painting order.
// https://www.w3.org/TR/CSS22/visuren.html#z-index // https://www.w3.org/TR/CSS22/visuren.html#z-index
// 7. the child stacking contexts with positive stack levels (least positive first). // 7. the child stacking contexts with positive stack levels (least positive first).
for (ssize_t i = m_children.size() - 1; i >= 0; --i) { for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
auto const& child = *m_children[i]; auto const& child = *m_children[i];
if (!child.m_box.is_visible())
continue;
if (child.m_box.computed_values().z_index().value_or(0) < 0)
continue;
auto result = child.hit_test(transformed_position, type); auto result = child.hit_test(transformed_position, type);
if (result.has_value()) if (result.has_value())
return result; return result;
@ -310,12 +317,18 @@ Optional<HitTestResult> StackingContext::hit_test(Gfx::FloatPoint const& positio
Optional<HitTestResult> result; Optional<HitTestResult> result;
// 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
paintable().for_each_in_subtree_of_type<PaintableBox>([&](auto& paint_box) { paintable().for_each_in_subtree_of_type<PaintableBox>([&](auto& paint_box) {
// FIXME: Support more overflow variations.
if (paint_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paint_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paint_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
return TraversalDecision::SkipChildrenAndContinue;
}
auto& layout_box = paint_box.layout_box(); auto& layout_box = paint_box.layout_box();
if (layout_box.is_positioned() && !paint_box.stacking_context()) { if (layout_box.is_positioned() && !paint_box.stacking_context()) {
if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value()) if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value())
result = move(candidate); result = move(candidate);
} }
return IterationDecision::Continue; return TraversalDecision::Continue;
}); });
if (result.has_value()) if (result.has_value())
return result; return result;
@ -329,12 +342,18 @@ Optional<HitTestResult> StackingContext::hit_test(Gfx::FloatPoint const& positio
// 4. the non-positioned floats. // 4. the non-positioned floats.
paintable().for_each_in_subtree_of_type<PaintableBox>([&](auto const& paint_box) { paintable().for_each_in_subtree_of_type<PaintableBox>([&](auto const& paint_box) {
// FIXME: Support more overflow variations.
if (paint_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paint_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paint_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
return TraversalDecision::SkipChildrenAndContinue;
}
auto& layout_box = paint_box.layout_box(); auto& layout_box = paint_box.layout_box();
if (layout_box.is_floating()) { if (layout_box.is_floating()) {
if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value()) if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value())
result = move(candidate); result = move(candidate);
} }
return IterationDecision::Continue; return TraversalDecision::Continue;
}); });
if (result.has_value()) if (result.has_value())
return result; return result;
@ -342,12 +361,18 @@ Optional<HitTestResult> StackingContext::hit_test(Gfx::FloatPoint const& positio
// 3. the in-flow, non-inline-level, non-positioned descendants. // 3. the in-flow, non-inline-level, non-positioned descendants.
if (!m_box.children_are_inline()) { if (!m_box.children_are_inline()) {
paintable().for_each_in_subtree_of_type<PaintableBox>([&](auto const& paint_box) { paintable().for_each_in_subtree_of_type<PaintableBox>([&](auto const& paint_box) {
// FIXME: Support more overflow variations.
if (paint_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paint_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paint_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
return TraversalDecision::SkipChildrenAndContinue;
}
auto& layout_box = paint_box.layout_box(); auto& layout_box = paint_box.layout_box();
if (!layout_box.is_absolutely_positioned() && !layout_box.is_floating()) { if (!layout_box.is_absolutely_positioned() && !layout_box.is_floating()) {
if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value()) if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value())
result = move(candidate); result = move(candidate);
} }
return IterationDecision::Continue; return TraversalDecision::Continue;
}); });
if (result.has_value()) if (result.has_value())
return result; return result;
@ -356,10 +381,6 @@ Optional<HitTestResult> StackingContext::hit_test(Gfx::FloatPoint const& positio
// 2. the child stacking contexts with negative stack levels (most negative first). // 2. the child stacking contexts with negative stack levels (most negative first).
for (ssize_t i = m_children.size() - 1; i >= 0; --i) { for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
auto const& child = *m_children[i]; auto const& child = *m_children[i];
if (child.m_box.computed_values().z_index().value_or(0) < 0)
continue;
if (!child.m_box.is_visible())
continue;
auto result = child.hit_test(transformed_position, type); auto result = child.hit_test(transformed_position, type);
if (result.has_value()) if (result.has_value())
return result; return result;