diff --git a/Tests/LibWeb/Text/input/DOM/Element-from-point.html b/Tests/LibWeb/Text/input/DOM/Element-from-point.html
new file mode 100644
index 0000000000..827350b346
--- /dev/null
+++ b/Tests/LibWeb/Text/input/DOM/Element-from-point.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp
index 0082073eef..72dab3a7ad 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.cpp
+++ b/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
(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(); document_root_element)
+ return document_root_element;
+
+ // 4. Return null.
+ return nullptr;
+}
+
}
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h
index 778ecb4b99..315dc64d1a 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.h
+++ b/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;
diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl
index 75812cd320..287cc822c8 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.idl
+++ b/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 {