From 5c26e86354457cb44106943ec9c17183e542b8fd Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 30 May 2026 09:57:03 +0200 Subject: [PATCH] LibWeb: Expose document caretPositionFromPoint Add the CSSOM View CaretPosition interface and expose the Document API. Use retained hit-test data to populate offsetNode, offset, and getClientRect(). Update IDL coverage and the window property baseline. Add a text test. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/DOM/CaretPosition.cpp | 50 +++++++++++++++++ Libraries/LibWeb/DOM/CaretPosition.h | 42 ++++++++++++++ Libraries/LibWeb/DOM/CaretPosition.idl | 7 +++ Libraries/LibWeb/DOM/Document.cpp | 55 +++++++++++++++++++ Libraries/LibWeb/DOM/Document.h | 2 + Libraries/LibWeb/DOM/Document.idl | 5 ++ Libraries/LibWeb/Forward.h | 1 + .../LibWeb/Painting/HitTestDisplayList.cpp | 26 +++++++-- Libraries/LibWeb/Painting/HitTestResult.h | 1 + Libraries/LibWeb/idl_files.cmake | 1 + .../Text/expected/all-window-properties.txt | 1 + ...caret-position-from-point-shadow-roots.txt | 6 ++ .../document-caret-position-from-point.txt | 6 ++ .../wpt-import/css/cssom-view/idlharness.txt | 38 ++++++------- ...aret-position-from-point-shadow-roots.html | 54 ++++++++++++++++++ .../document-caret-position-from-point.html | 32 +++++++++++ 17 files changed, 305 insertions(+), 23 deletions(-) create mode 100644 Libraries/LibWeb/DOM/CaretPosition.cpp create mode 100644 Libraries/LibWeb/DOM/CaretPosition.h create mode 100644 Libraries/LibWeb/DOM/CaretPosition.idl create mode 100644 Tests/LibWeb/Text/expected/document-caret-position-from-point-shadow-roots.txt create mode 100644 Tests/LibWeb/Text/expected/document-caret-position-from-point.txt create mode 100644 Tests/LibWeb/Text/input/document-caret-position-from-point-shadow-roots.html create mode 100644 Tests/LibWeb/Text/input/document-caret-position-from-point.html diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 5a762b9d92..255ee151cd 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -334,6 +334,7 @@ set(SOURCES DOM/AnchorNameMap.cpp DOM/Attr.cpp DOM/CDATASection.cpp + DOM/CaretPosition.cpp DOM/CharacterData.cpp DOM/Comment.cpp DOM/CustomEvent.cpp diff --git a/Libraries/LibWeb/DOM/CaretPosition.cpp b/Libraries/LibWeb/DOM/CaretPosition.cpp new file mode 100644 index 0000000000..025a8d8d8b --- /dev/null +++ b/Libraries/LibWeb/DOM/CaretPosition.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::DOM { + +GC_DEFINE_ALLOCATOR(CaretPosition); + +GC::Ref CaretPosition::create(JS::Realm& realm, GC::Ref offset_node, WebIDL::UnsignedLong offset, Optional client_rect) +{ + return realm.create(realm, offset_node, offset, move(client_rect)); +} + +CaretPosition::CaretPosition(JS::Realm& realm, GC::Ref offset_node, WebIDL::UnsignedLong offset, Optional client_rect) + : Bindings::PlatformObject(realm) + , m_offset_node(offset_node) + , m_offset(offset) + , m_client_rect(move(client_rect)) +{ +} + +CaretPosition::~CaretPosition() = default; + +void CaretPosition::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(CaretPosition); + Base::initialize(realm); +} + +void CaretPosition::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_offset_node); +} + +GC::Ptr CaretPosition::get_client_rect() const +{ + if (!m_client_rect.has_value()) + return nullptr; + return Geometry::DOMRect::create(realm(), *m_client_rect); +} + +} diff --git a/Libraries/LibWeb/DOM/CaretPosition.h b/Libraries/LibWeb/DOM/CaretPosition.h new file mode 100644 index 0000000000..031677f04f --- /dev/null +++ b/Libraries/LibWeb/DOM/CaretPosition.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::DOM { + +class CaretPosition final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(CaretPosition, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(CaretPosition); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, GC::Ref offset_node, WebIDL::UnsignedLong offset, Optional client_rect); + + virtual ~CaretPosition() override; + + GC::Ref offset_node() const { return m_offset_node; } + WebIDL::UnsignedLong offset() const { return m_offset; } + + GC::Ptr get_client_rect() const; + +private: + CaretPosition(JS::Realm&, GC::Ref offset_node, WebIDL::UnsignedLong offset, Optional client_rect); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + GC::Ref m_offset_node; + WebIDL::UnsignedLong m_offset { 0 }; + Optional m_client_rect; +}; + +} diff --git a/Libraries/LibWeb/DOM/CaretPosition.idl b/Libraries/LibWeb/DOM/CaretPosition.idl new file mode 100644 index 0000000000..c3915233e7 --- /dev/null +++ b/Libraries/LibWeb/DOM/CaretPosition.idl @@ -0,0 +1,7 @@ +// https://drafts.csswg.org/cssom-view/#caretposition +[Exposed=Window] +interface CaretPosition { + readonly attribute Node offsetNode; + readonly attribute unsigned long offset; + [NewObject] DOMRect? getClientRect(); +}; diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index c6dc7f9b70..bbef5a6ee4 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -82,6 +82,7 @@ #include #include #include +#include #include #include #include @@ -7206,6 +7207,60 @@ GC::RootVector> Document::elements_from_point(double x, double return sequence; } +static bool shadow_root_is_allowed_for_caret_position(ShadowRoot const& shadow_root, Bindings::CaretPositionFromPointOptions const& options) +{ + for (auto const& allowed_shadow_root : options.shadow_roots) { + if (shadow_root.is_shadow_including_inclusive_ancestor_of(allowed_shadow_root)) + return true; + } + return false; +} + +// https://drafts.csswg.org/cssom-view/#dom-document-caretpositionfrompoint +GC::Ptr Document::caret_position_from_point(double x, double y, Bindings::CaretPositionFromPointOptions const& options) +{ + // 1. If there is no viewport associated with the document, return null. + // 2. 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), + // return null. + 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(UpdateLayoutReason::DocumentCaretPositionFromPoint); + + // 3. If at the coordinates x,y in the viewport no text insertion point indicator would have been inserted when + // applying the transforms that apply to the descendants of the viewport, return null. + auto caret_position = caret_position_from_point(position); + if (!caret_position.has_value()) + return nullptr; + + // FIXME: 4. If at the coordinates x,y in the viewport a text insertion point indicator would have been inserted + // in a text entry widget which is also a replaced element, when applying the transforms that apply to + // the descendants of the viewport, return a caret position for the text entry widget. + + // 5. Otherwise, retarget shadow tree positions whose roots are not allowed by options.shadowRoots. + auto start_node = caret_position->boundary.node; + auto start_offset = caret_position->boundary.offset; + auto* shadow_root = as_if(start_node->root()); + while (shadow_root && !shadow_root_is_allowed_for_caret_position(*shadow_root, options)) { + auto* host = shadow_root->host(); + auto* host_parent = host->parent(); + if (!host_parent) + return nullptr; + start_offset = host->index(); + start_node = *host_parent; + shadow_root = as_if(start_node->root()); + } + + return CaretPosition::create(realm(), start_node, start_offset, caret_position->debug_rect.map([](auto const& rect) { + return rect.template to_type(); + })); +} + // https://drafts.csswg.org/cssom-view/#dom-document-scrollingelement GC::Ptr Document::scrolling_element() const { diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index f2c88027b2..6ceacbb8e4 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -83,6 +83,7 @@ enum class InvalidateLayoutTreeReason { X(Debugging) \ X(DocumentElementFromPoint) \ X(DocumentElementsFromPoint) \ + X(DocumentCaretPositionFromPoint) \ X(DocumentFindMatchingText) \ X(DocumentSetDesignMode) \ X(DumpDisplayList) \ @@ -981,6 +982,7 @@ public: Painting::HitTestDisplayList const* ensure_hit_test_display_list(); Optional hit_test(CSSPixelPoint, Painting::HitTestType); Optional caret_position_from_point(CSSPixelPoint); + GC::Ptr caret_position_from_point(double x, double y, Bindings::CaretPositionFromPointOptions const&); TraversalDecision hit_test_all(CSSPixelPoint, Function const&); void set_needs_to_record_display_list(); diff --git a/Libraries/LibWeb/DOM/Document.idl b/Libraries/LibWeb/DOM/Document.idl index 7bca1208cd..7e3b590470 100644 --- a/Libraries/LibWeb/DOM/Document.idl +++ b/Libraries/LibWeb/DOM/Document.idl @@ -109,6 +109,7 @@ interface Document : Node { // https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface Element? elementFromPoint(double x, double y); sequence elementsFromPoint(double x, double y); + CaretPosition? caretPositionFromPoint(double x, double y, optional CaretPositionFromPointOptions options = {}); readonly attribute Element? scrollingElement; // https://w3c.github.io/editing/docs/execCommand/ @@ -134,6 +135,10 @@ interface Document : Node { undefined exitPointerLock(); }; +dictionary CaretPositionFromPointOptions { + sequence shadowRoots = []; +}; + // https://dom.spec.whatwg.org/#dictdef-elementcreationoptions dictionary ElementCreationOptions { CustomElementRegistry? customElementRegistry; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index f3cc3466f3..215778fc2f 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -531,6 +531,7 @@ class AccessibilityTreeNode; class AnchorNameMap; class Attr; class CDATASection; +class CaretPosition; class CharacterData; class Comment; class CustomEvent; diff --git a/Libraries/LibWeb/Painting/HitTestDisplayList.cpp b/Libraries/LibWeb/Painting/HitTestDisplayList.cpp index a95dbd589f..1068fd9e05 100644 --- a/Libraries/LibWeb/Painting/HitTestDisplayList.cpp +++ b/Libraries/LibWeb/Painting/HitTestDisplayList.cpp @@ -488,6 +488,7 @@ Optional HitTestDisplayList::caret_position_for_item(Item const& return CaretPosition { .paintable = item.paintable, .boundary = { const_cast(*fragment_dom_node), static_cast(index_in_node) }, + .debug_rect = fragment.range_rect(Paintable::SelectionState::StartAndEnd, index_in_node, index_in_node), }; } case ItemKind::EmptyEditable: { @@ -497,6 +498,7 @@ Optional HitTestDisplayList::caret_position_for_item(Item const& return CaretPosition { .paintable = item.paintable, .boundary = { *dom_node, 0 }, + .debug_rect = item.caret_rect, }; } case ItemKind::Box: { @@ -521,6 +523,7 @@ Optional HitTestDisplayList::caret_position_for_item(Item const& .paintable = item.paintable, .boundary = point_is_before_box ? before_boundary : after_boundary, .secondary_boundary = point_is_before_box ? after_boundary : before_boundary, + .debug_rect = item.caret_rect, }; } case ItemKind::ChromeWidget: @@ -538,6 +541,7 @@ Optional HitTestDisplayList::caret_position_for_hit_container(Ite return CaretPosition { .paintable = item.paintable, .boundary = { const_cast(*dom_node), 0 }, + .debug_rect = item.caret_rect, }; } @@ -699,8 +703,12 @@ Optional HitTestDisplayList::caret_position_from_point(CSSPixelPo }; if (topmost_item_index.has_value() && (!topmost_hit_item_index.has_value() || topmost_caret_item_matches_hit_item())) { VERIFY(topmost_item_local_point.has_value()); - if (auto caret_position = caret_position_for_item(m_items[*topmost_item_index], *topmost_item_local_point); caret_position.has_value()) + if (auto caret_position = caret_position_for_item(m_items[*topmost_item_index], *topmost_item_local_point); caret_position.has_value()) { + auto const& item = m_items[*topmost_item_index]; + if (caret_position->debug_rect.has_value()) + caret_position->debug_rect = viewport_rect_for_item(item, *caret_position->debug_rect, viewport_paintable, device_pixels_per_css_pixel); return caret_position; + } } // If the point is over a non-caret item, only consider caret lines inside that item's event-dispatch node first. @@ -814,21 +822,31 @@ Optional HitTestDisplayList::caret_position_from_point(CSSPixelPo } if (!closest_line.index.has_value()) { - if (topmost_hit_item_index.has_value()) - return caret_position_for_hit_container(m_items[*topmost_hit_item_index]); + if (topmost_hit_item_index.has_value()) { + auto const& item = m_items[*topmost_hit_item_index]; + auto caret_position = caret_position_for_hit_container(item); + if (caret_position.has_value() && caret_position->debug_rect.has_value()) + caret_position->debug_rect = viewport_rect_for_item(item, *caret_position->debug_rect, viewport_paintable, device_pixels_per_css_pixel); + return caret_position; + } return {}; } VERIFY(closest_line.local_point.has_value()); auto caret_position = caret_position_for_line(m_caret_lines[*closest_line.index], *closest_line.local_point); if (!caret_position.has_value()) return {}; + if (caret_position->debug_rect.has_value()) + caret_position->debug_rect = viewport_rect_for_item(m_items[m_caret_item_indices[m_caret_lines[*closest_line.index].first_caret_item_index]], *caret_position->debug_rect, viewport_paintable, device_pixels_per_css_pixel); if (topmost_hit_item_index.has_value()) { auto const& topmost_hit_item = m_items[*topmost_hit_item_index]; if (auto const* topmost_hit_dom_node = event_dispatch_dom_node_for_item(topmost_hit_item); topmost_hit_dom_node && !topmost_hit_dom_node->is_inclusive_ancestor_of(*caret_position->boundary.node)) { if (item_can_produce_caret_position(topmost_hit_item) && item_is_direct_caret_target(topmost_hit_item)) { VERIFY(topmost_hit_item_local_point.has_value()); - return caret_position_for_item(topmost_hit_item, *topmost_hit_item_local_point); + auto caret_position_for_topmost_hit_item = caret_position_for_item(topmost_hit_item, *topmost_hit_item_local_point); + if (caret_position_for_topmost_hit_item.has_value() && caret_position_for_topmost_hit_item->debug_rect.has_value()) + caret_position_for_topmost_hit_item->debug_rect = viewport_rect_for_item(topmost_hit_item, *caret_position_for_topmost_hit_item->debug_rect, viewport_paintable, device_pixels_per_css_pixel); + return caret_position_for_topmost_hit_item; } return {}; } diff --git a/Libraries/LibWeb/Painting/HitTestResult.h b/Libraries/LibWeb/Painting/HitTestResult.h index e155b3d065..55b5d8dcee 100644 --- a/Libraries/LibWeb/Painting/HitTestResult.h +++ b/Libraries/LibWeb/Painting/HitTestResult.h @@ -39,6 +39,7 @@ struct CaretPosition { NonnullRefPtr paintable; DOM::BoundaryPoint boundary; Optional secondary_boundary {}; + Optional debug_rect {}; }; enum class HitTestType : u8 { diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index e6146cf26a..b01ce073f9 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -109,6 +109,7 @@ libweb_js_bindings(DOM/AbortSignal) libweb_js_bindings(DOM/AbstractRange) libweb_js_bindings(DOM/Attr) libweb_js_bindings(DOM/CDATASection) +libweb_js_bindings(DOM/CaretPosition) libweb_js_bindings(DOM/CharacterData) libweb_js_bindings(DOM/ChildNode) libweb_js_bindings(DOM/Comment) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 21c6670401..34a66c5808 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -100,6 +100,7 @@ CacheStorage CanvasGradient CanvasPattern CanvasRenderingContext2D +CaretPosition ChannelMergerNode ChannelSplitterNode CharacterData diff --git a/Tests/LibWeb/Text/expected/document-caret-position-from-point-shadow-roots.txt b/Tests/LibWeb/Text/expected/document-caret-position-from-point-shadow-roots.txt new file mode 100644 index 0000000000..5813bb41b3 --- /dev/null +++ b/Tests/LibWeb/Text/expected/document-caret-position-from-point-shadow-roots.txt @@ -0,0 +1,6 @@ +without shadowRoots retargets to host parent: true +without shadowRoots retargets to host index: true +unrelated shadowRoot still retargets to host parent: true +unrelated shadowRoot still retargets to host index: true +allowed shadowRoot exposes text node: true +allowed shadowRoot exposes text offset: true diff --git a/Tests/LibWeb/Text/expected/document-caret-position-from-point.txt b/Tests/LibWeb/Text/expected/document-caret-position-from-point.txt new file mode 100644 index 0000000000..8d69ab627c --- /dev/null +++ b/Tests/LibWeb/Text/expected/document-caret-position-from-point.txt @@ -0,0 +1,6 @@ +negative x returns null: true +negative y returns null: true +caret position exists: true +offset node is text node: true +offset is valid for text node: true +client rect exists: true diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom-view/idlharness.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom-view/idlharness.txt index c4ae55acb6..4854ba8c73 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom-view/idlharness.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom-view/idlharness.txt @@ -2,8 +2,8 @@ Harness status: OK Found 400 tests -326 Pass -74 Fail +343 Pass +57 Fail Pass idl_test setup Pass idl_test validation Pass Partial interface Window: original interface defined @@ -106,20 +106,20 @@ Pass Screen interface: screen must inherit property "width" with the proper type Pass Screen interface: screen must inherit property "height" with the proper type Pass Screen interface: screen must inherit property "colorDepth" with the proper type Pass Screen interface: screen must inherit property "pixelDepth" with the proper type -Fail CaretPosition interface: existence and properties of interface object -Fail CaretPosition interface object length -Fail CaretPosition interface object name -Fail CaretPosition interface: existence and properties of interface prototype object -Fail CaretPosition interface: existence and properties of interface prototype object's "constructor" property -Fail CaretPosition interface: existence and properties of interface prototype object's @@unscopables property -Fail CaretPosition interface: attribute offsetNode -Fail CaretPosition interface: attribute offset -Fail CaretPosition interface: operation getClientRect() -Fail CaretPosition must be primary interface of document.caretPositionFromPoint(5, 5) -Fail Stringification of document.caretPositionFromPoint(5, 5) -Fail CaretPosition interface: document.caretPositionFromPoint(5, 5) must inherit property "offsetNode" with the proper type -Fail CaretPosition interface: document.caretPositionFromPoint(5, 5) must inherit property "offset" with the proper type -Fail CaretPosition interface: document.caretPositionFromPoint(5, 5) must inherit property "getClientRect()" with the proper type +Pass CaretPosition interface: existence and properties of interface object +Pass CaretPosition interface object length +Pass CaretPosition interface object name +Pass CaretPosition interface: existence and properties of interface prototype object +Pass CaretPosition interface: existence and properties of interface prototype object's "constructor" property +Pass CaretPosition interface: existence and properties of interface prototype object's @@unscopables property +Pass CaretPosition interface: attribute offsetNode +Pass CaretPosition interface: attribute offset +Pass CaretPosition interface: operation getClientRect() +Pass CaretPosition must be primary interface of document.caretPositionFromPoint(5, 5) +Pass Stringification of document.caretPositionFromPoint(5, 5) +Pass CaretPosition interface: document.caretPositionFromPoint(5, 5) must inherit property "offsetNode" with the proper type +Pass CaretPosition interface: document.caretPositionFromPoint(5, 5) must inherit property "offset" with the proper type +Pass CaretPosition interface: document.caretPositionFromPoint(5, 5) must inherit property "getClientRect()" with the proper type Pass VisualViewport interface: existence and properties of interface object Pass VisualViewport interface object length Pass VisualViewport interface object name @@ -309,7 +309,7 @@ Pass Window interface: window must inherit property "outerHeight" with the prope Pass Window interface: window must inherit property "devicePixelRatio" with the proper type Pass Document interface: operation elementFromPoint(double, double) Pass Document interface: operation elementsFromPoint(double, double) -Fail Document interface: operation caretPositionFromPoint(double, double, optional CaretPositionFromPointOptions) +Pass Document interface: operation caretPositionFromPoint(double, double, optional CaretPositionFromPointOptions) Pass Document interface: attribute scrollingElement Fail Document interface: operation getBoxQuads(optional BoxQuadOptions) Fail Document interface: operation convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions) @@ -319,8 +319,8 @@ Pass Document interface: document must inherit property "elementFromPoint(double Pass Document interface: calling elementFromPoint(double, double) on document with too few arguments must throw TypeError Pass Document interface: document must inherit property "elementsFromPoint(double, double)" with the proper type Pass Document interface: calling elementsFromPoint(double, double) on document with too few arguments must throw TypeError -Fail Document interface: document must inherit property "caretPositionFromPoint(double, double, optional CaretPositionFromPointOptions)" with the proper type -Fail Document interface: calling caretPositionFromPoint(double, double, optional CaretPositionFromPointOptions) on document with too few arguments must throw TypeError +Pass Document interface: document must inherit property "caretPositionFromPoint(double, double, optional CaretPositionFromPointOptions)" with the proper type +Pass Document interface: calling caretPositionFromPoint(double, double, optional CaretPositionFromPointOptions) on document with too few arguments must throw TypeError Pass Document interface: document must inherit property "scrollingElement" with the proper type Fail Document interface: document must inherit property "getBoxQuads(optional BoxQuadOptions)" with the proper type Fail Document interface: calling getBoxQuads(optional BoxQuadOptions) on document with too few arguments must throw TypeError diff --git a/Tests/LibWeb/Text/input/document-caret-position-from-point-shadow-roots.html b/Tests/LibWeb/Text/input/document-caret-position-from-point-shadow-roots.html new file mode 100644 index 0000000000..11dd8c2d1b --- /dev/null +++ b/Tests/LibWeb/Text/input/document-caret-position-from-point-shadow-roots.html @@ -0,0 +1,54 @@ + + + +
+ diff --git a/Tests/LibWeb/Text/input/document-caret-position-from-point.html b/Tests/LibWeb/Text/input/document-caret-position-from-point.html new file mode 100644 index 0000000000..2e01eda473 --- /dev/null +++ b/Tests/LibWeb/Text/input/document-caret-position-from-point.html @@ -0,0 +1,32 @@ + + + +
hello world
+