From 44057c94821ea5dbfa2747de7cd4053fd5fe0742 Mon Sep 17 00:00:00 2001 From: Igor Pissolati Date: Sun, 3 Jul 2022 18:34:51 -0300 Subject: [PATCH] LibWeb: Make hit testing respect hidden overflow --- .../Libraries/LibWeb/Painting/Paintable.h | 26 ++++++----- .../LibWeb/Painting/StackingContext.cpp | 43 ++++++++++++++----- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index ea4cd28f26..7bd7b045bf 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -13,6 +13,12 @@ namespace Web::Painting { +enum class TraversalDecision { + Continue, + SkipChildrenAndContinue, + Break, +}; + enum class PaintPhase { Background, Border, @@ -53,27 +59,27 @@ public: Paintable const* next_sibling() const; template - IterationDecision for_each_in_inclusive_subtree_of_type(Callback callback) const + TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) const { if (is(*this)) { - if (callback(static_cast(*this)) == IterationDecision::Break) - return IterationDecision::Break; + if (auto decision = callback(static_cast(*this)); decision != TraversalDecision::Continue) + return decision; } for (auto* child = first_child(); child; child = child->next_sibling()) { - if (child->template for_each_in_inclusive_subtree_of_type(callback) == IterationDecision::Break) - return IterationDecision::Break; + if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) + return TraversalDecision::Break; } - return IterationDecision::Continue; + return TraversalDecision::Continue; } template - 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()) { - if (child->template for_each_in_inclusive_subtree_of_type(callback) == IterationDecision::Break) - return IterationDecision::Break; + if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) + return TraversalDecision::Break; } - return IterationDecision::Continue; + return TraversalDecision::Continue; } virtual void paint(PaintContext&, PaintPhase) const { } diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index b376f1c6e5..cb46c00077 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -288,20 +288,27 @@ Gfx::FloatPoint StackingContext::transform_origin() const Optional 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 affine_transform = combine_transformations_2d(m_box.computed_values().transformations()); 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. // https://www.w3.org/TR/CSS22/visuren.html#z-index // 7. the child stacking contexts with positive stack levels (least positive first). for (ssize_t i = m_children.size() - 1; i >= 0; --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); if (result.has_value()) return result; @@ -310,12 +317,18 @@ Optional StackingContext::hit_test(Gfx::FloatPoint const& positio Optional result; // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. paintable().for_each_in_subtree_of_type([&](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(); if (layout_box.is_positioned() && !paint_box.stacking_context()) { if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value()) result = move(candidate); } - return IterationDecision::Continue; + return TraversalDecision::Continue; }); if (result.has_value()) return result; @@ -329,12 +342,18 @@ Optional StackingContext::hit_test(Gfx::FloatPoint const& positio // 4. the non-positioned floats. paintable().for_each_in_subtree_of_type([&](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(); if (layout_box.is_floating()) { if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value()) result = move(candidate); } - return IterationDecision::Continue; + return TraversalDecision::Continue; }); if (result.has_value()) return result; @@ -342,12 +361,18 @@ Optional StackingContext::hit_test(Gfx::FloatPoint const& positio // 3. the in-flow, non-inline-level, non-positioned descendants. if (!m_box.children_are_inline()) { paintable().for_each_in_subtree_of_type([&](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(); if (!layout_box.is_absolutely_positioned() && !layout_box.is_floating()) { if (auto candidate = paint_box.hit_test(transformed_position, type); candidate.has_value()) result = move(candidate); } - return IterationDecision::Continue; + return TraversalDecision::Continue; }); if (result.has_value()) return result; @@ -356,10 +381,6 @@ Optional StackingContext::hit_test(Gfx::FloatPoint const& positio // 2. the child stacking contexts with negative stack levels (most negative first). for (ssize_t i = m_children.size() - 1; i >= 0; --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); if (result.has_value()) return result;