Browse Source

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

Elements are now collected according to paint order as spec says,
replacing the depth-first traversal of the paint tree with hit-testing
on each box.

This change resolves a FIXME in an existing test and adds a new
previously non-working test.
Aliaksandr Kalenik 1 year ago
parent
commit
9d2809146f

+ 4 - 0
Tests/LibWeb/Text/expected/DOM/Elements-from-point-2.txt

@@ -0,0 +1,4 @@
+hello     <DIV id="c" >
+<DIV id="b" >
+<DIV id="a" >
+<HTML >

+ 1 - 0
Tests/LibWeb/Text/expected/DOM/Elements-from-point-3.txt

@@ -0,0 +1 @@
+ Some text  Elements at point 30, 20: p < div < body < html   

+ 2 - 4
Tests/LibWeb/Text/expected/DOM/Elements-from-point.txt

@@ -3,11 +3,9 @@ Coordinates outside the viewport return empty array: true
 == Elements at (500, 10) ==
 <DIV id="large-box" >
 <HTML >
-== FIXME: Elements at (550, 60) ==
+== Elements at (550, 60) ==
 <DIV id="small-box" >
-<PRE id="out" >
-<PRE id="out" >
 <DIV id="large-box" >
-<DIV id="small-box" >
 <PRE id="out" >
+<BODY >
 <HTML >

+ 37 - 0
Tests/LibWeb/Text/input/DOM/Elements-from-point-2.html

@@ -0,0 +1,37 @@
+<!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(() => {
+        for (const element of document.elementsFromPoint(150, 50)) {
+            printElement(element)
+        }
+    });
+</script>

+ 18 - 0
Tests/LibWeb/Text/input/DOM/Elements-from-point-3.html

@@ -0,0 +1,18 @@
+<div>
+    <p>Some text</p>
+</div>
+<p>Elements at point 30, 20:</p>
+<div id="output"></div>
+<script src="../include.js"></script>
+<script>
+    test(() => {
+        let output = document.getElementById("output");
+        let elements = document.elementsFromPoint(30, 20);
+        elements.forEach((elt, i) => {
+            output.textContent += elt.localName;
+            if (i < elements.length - 1) {
+                output.textContent += " < ";
+            }
+        });
+    });
+</script>

+ 1 - 7
Tests/LibWeb/Text/input/DOM/Elements-from-point.html

@@ -32,15 +32,9 @@
         for (let elem of document.elementsFromPoint(500, 10)) {
             printElement(elem);
         }
-        println("== FIXME: Elements at (550, 60) ==")
+        println("== Elements at (550, 60) ==")
         for (let elem of document.elementsFromPoint(550, 60)) {
             printElement(elem);
         }
-        // FIXME: 550, 60 is supposed to print the following, but the algorithm is wrong.
-        // <DIV id="small-box" >
-        // <DIV id="large-box" >
-        // <PRE id="out" >
-        // <BODY >
-        // <HTML >
     });
 </script>

+ 5 - 9
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -3829,16 +3829,12 @@ Vector<JS::NonnullGCPtr<Element>> Document::elements_from_point(double x, double
     // 3. For each box in the viewport, in paint order, starting with the topmost box, that would be a target for
     //    hit testing at coordinates x,y even if nothing would be overlapping it, when applying the transforms that
     //    apply to the descendants of the viewport, append the associated element to sequence.
-    // FIXME: Paintable box tree order is not the same as paint order. We need a helper to traverse the paint tree in
-    //        paint order with a custom callback.
     if (auto const* paintable_box = this->paintable_box(); paintable_box) {
-        paintable_box->for_each_in_inclusive_subtree_of_type<Painting::PaintableBox>([&](auto& 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())
-                    sequence.append(*static_cast<Element*>(dom_node));
-                return Painting::TraversalDecision::Continue;
-            }
-            return Painting::TraversalDecision::SkipChildrenAndContinue;
+        (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())
+                sequence.append(*static_cast<Element*>(dom_node));
+            return Painting::TraversalDecision::Continue;
         });
     }