Bläddra i källkod

LibWeb: Implement hit testing a bit closer to spec

We now perform hit testing in reverse paint order as described in CSS2's
section about the z-index property.
Andreas Kling 3 år sedan
förälder
incheckning
15ed0ebdc6

+ 1 - 1
Userland/Libraries/LibWeb/Layout/BlockContainer.cpp

@@ -101,7 +101,7 @@ HitTestResult BlockContainer::hit_test(const Gfx::IntPoint& position, HitTestTyp
 
     if (type == HitTestType::TextCursor && last_good_candidate.layout_node)
         return last_good_candidate;
-    return { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
+    return { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
 }
 
 bool BlockContainer::is_scrollable() const

+ 1 - 1
Userland/Libraries/LibWeb/Layout/Box.cpp

@@ -178,7 +178,7 @@ HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) con
     // FIXME: It would be nice if we could confidently skip over hit testing
     //        parts of the layout tree, but currently we can't just check
     //        m_rect.contains() since inline text rects can't be trusted..
-    HitTestResult result { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
+    HitTestResult result { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
     for_each_child_in_paint_order([&](auto& child) {
         auto child_result = child.hit_test(position, type);
         if (child_result.layout_node)

+ 2 - 8
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -80,15 +80,9 @@ bool Node::establishes_stacking_context() const
     return computed_values().opacity() < 1.0f;
 }
 
-HitTestResult Node::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+HitTestResult Node::hit_test(Gfx::IntPoint const&, HitTestType) const
 {
-    HitTestResult result;
-    for_each_child_in_paint_order([&](auto& child) {
-        auto child_result = child.hit_test(position, type);
-        if (child_result.layout_node)
-            result = child_result;
-    });
-    return result;
+    VERIFY_NOT_REACHED();
 }
 
 HTML::BrowsingContext const& Node::browsing_context() const

+ 69 - 15
Userland/Libraries/LibWeb/Painting/StackingContext.cpp

@@ -148,27 +148,81 @@ void StackingContext::paint(PaintContext& context)
 
 HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, HitTestType type) const
 {
+    // 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.computed_values().z_index().value_or(0) < 0)
+            break;
+        auto result = child.hit_test(position, type);
+        if (result.layout_node)
+            return result;
+    }
+
     HitTestResult result;
-    if (!is<InitialContainingBlock>(m_box)) {
-        result = m_box.hit_test(position, type);
-    } else {
-        // NOTE: InitialContainingBlock::hit_test() merely calls StackingContext::hit_test()
-        //       so we call its base class instead.
-        result = verify_cast<InitialContainingBlock>(m_box).BlockContainer::hit_test(position, type);
+    // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
+    m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) {
+        if (box.is_positioned() && !box.stacking_context()) {
+            result = box.hit_test(position, type);
+            if (result.layout_node)
+                return IterationDecision::Break;
+        }
+        return IterationDecision::Continue;
+    });
+    if (result.layout_node)
+        return result;
+
+    // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
+    if (m_box.children_are_inline() && is<BlockContainer>(m_box)) {
+        auto result = m_box.hit_test(position, type);
+        if (result.layout_node)
+            return result;
     }
 
-    int z_index = m_box.computed_values().z_index().value_or(0);
+    // 4. the non-positioned floats.
+    m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) {
+        if (box.is_floating()) {
+            result = box.hit_test(position, type);
+            if (result.layout_node)
+                return IterationDecision::Break;
+        }
+        return IterationDecision::Continue;
+    });
+
+    // 3. the in-flow, non-inline-level, non-positioned descendants.
+    if (!m_box.children_are_inline()) {
+        m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) {
+            if (!box.is_absolutely_positioned() && !box.is_floating()) {
+                result = box.hit_test(position, type);
+                if (result.layout_node)
+                    return IterationDecision::Break;
+            }
+            return IterationDecision::Continue;
+        });
+        if (result.layout_node)
+            return result;
+    }
 
-    for (auto* child : m_children) {
-        int child_z_index = child->m_box.computed_values().z_index().value_or(0);
-        if (result.layout_node && (child_z_index < z_index))
-            continue;
+    // 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)
+            break;
+        auto result = child.hit_test(position, type);
+        if (result.layout_node)
+            return result;
+    }
 
-        auto result_here = child->hit_test(position, type);
-        if (result_here.layout_node)
-            result = result_here;
+    // 1. the background and borders of the element forming the stacking context.
+    if (m_box.absolute_border_box_rect().contains(position.to_type<float>())) {
+        return HitTestResult {
+            .layout_node = m_box,
+        };
     }
-    return result;
+
+    return {};
 }
 
 void StackingContext::dump(int indent) const