浏览代码

LibWeb: Implement `document.elementFromPoint()`

This function uses our existing hit testing code to determine the
topmost element at the given coordinates relative to the viewport.
Tim Ledbetter 1 年之前
父节点
当前提交
69df94ec5c

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

@@ -0,0 +1,5 @@
+  Negative coordinates return null: true
+Coordinates outside the viewport return null: true
+<HTML id="root-element" >
+<DIV id="large-box" >
+<DIV id="small-box" >

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

@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html id="root-element">
+<style>
+    #large-box {
+        position: absolute;
+        top: 10px;
+        left: 500px;
+        width: 100px;
+        height: 100px;
+        background-color: magenta;
+    }
+
+    #small-box {
+        position: absolute;
+        top: 35px;
+        left: 525px;
+        width: 50px;
+        height: 50px;
+        background-color: yellow;
+    }
+</style>
+<script src="../include.js"></script>
+<div id="large-box"></div><div id="small-box"></div>
+<script>    
+    test(() => {
+        println(`Negative coordinates return null: ${document.elementFromPoint(-1, -1) === null}`);
+        println(`Coordinates outside the viewport return null: ${document.elementFromPoint(99999, 99999) === null}`);
+        printElement(document.elementFromPoint(0, 0));
+        printElement(document.elementFromPoint(500, 10));
+        printElement(document.elementFromPoint(550, 60));        
+    });
+</script>
+</html>

+ 32 - 0
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -3721,4 +3721,36 @@ void Document::remove_form_associated_element_with_form_attribute(HTML::FormAsso
     });
 }
 
+// https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint
+Element const* Document::element_from_point(double x, double y)
+{
+    // 1. If either argument is negative, x is greater than the viewport width excluding the size of a rendered scroll
+    //    bar (if any), or y is greater than the viewport height excluding the size of a rendered scroll bar (if any), or
+    //    there is no viewport associated with the document, return null and terminate these steps.
+    auto viewport_rect = this->viewport_rect();
+    CSSPixelPoint position { x, y };
+    // FIXME: This should account for the size of the scroll bar.
+    if (x < 0 || y < 0 || position.x() > viewport_rect.width() || position.y() > viewport_rect.height())
+        return nullptr;
+
+    // Ensure the layout tree exists prior to hit testing.
+    update_layout();
+
+    // 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.
+    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);
+        }
+    }
+
+    // 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)
+        return document_root_element;
+
+    // 4. Return null.
+    return nullptr;
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/DOM/Document.h

@@ -560,6 +560,8 @@ public:
     void add_form_associated_element_with_form_attribute(HTML::FormAssociatedElement&);
     void remove_form_associated_element_with_form_attribute(HTML::FormAssociatedElement&);
 
+    Element const* element_from_point(double x, double y);
+
 protected:
     virtual void initialize(JS::Realm&) override;
     virtual void visit_edges(Cell::Visitor&) override;

+ 3 - 0
Userland/Libraries/LibWeb/DOM/Document.idl

@@ -116,6 +116,9 @@ interface Document : Node {
 
     // https://www.w3.org/TR/web-animations-1/#extensions-to-the-document-interface
     readonly attribute DocumentTimeline timeline;
+
+    // https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
+    Element? elementFromPoint(double x, double y);
 };
 
 dictionary ElementCreationOptions {