Browse Source

LibWeb: Do paint-order traversal in Document::element_from_point()

Specify callback for hit-test function to identify closest DOM element,
excluding text nodes. Add a previously failing test case.
Aliaksandr Kalenik 1 year ago
parent
commit
88ad871e2b

+ 1 - 0
Tests/LibWeb/Text/expected/DOM/Element-from-point-2.txt

@@ -0,0 +1 @@
+hello     <DIV id="c" >

+ 36 - 0
Tests/LibWeb/Text/input/DOM/Element-from-point-2.html

@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<style>
+    .box {
+        width: 100px;
+        height: 100px;
+        position: absolute;
+    }
+
+    #a {
+        background-color: magenta;
+        z-index: 1;
+        transform: translate(110px, 10px);
+    }
+
+    #b {
+        background-color: mediumaquamarine;
+        z-index: 2;
+        transform: translate(120px, 20px);
+    }
+
+    #c {
+        background-color: greenyellow;
+        z-index: 3;
+        transform: translate(130px, 30px);
+    }
+</style>
+<div id="a" class="box"></div>
+<div id="b" class="box"></div>
+<div id="c" class="box">hello</div>
+<script src="../include.js"></script>
+<script>
+    test(() => {
+        const element = document.elementFromPoint(150, 50);
+        printElement(element);
+    });
+</script>

+ 11 - 4
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -3793,12 +3793,19 @@ 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) {
-        if (auto result = paintable_box->hit_test(position, Painting::HitTestType::Exact); result.has_value()) {
-            if (auto* dom_node = result->dom_node(); dom_node && dom_node->is_element())
-                return static_cast<Element const*>(dom_node);
-        }
+        (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;
+                return Painting::TraversalDecision::Break;
+            }
+            return Painting::TraversalDecision::Continue;
+        });
     }
+    if (hit_test_result.has_value())
+        return static_cast<Element*>(hit_test_result->dom_node());
 
     // 3. If the document has a root element, return the root element and terminate these steps.
     if (auto const* document_root_element = first_child_of_type<Element>(); document_root_element)