2020-12-06 19:51:55 +01:00
/*
* Copyright ( c ) 2020 , the SerenityOS developers .
2022-01-30 23:35:51 +00:00
* Copyright ( c ) 2022 , Luke Wilde < lukew @ serenityos . org >
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2022 - 2023 , Andreas Kling < andreas @ ladybird . org >
2025-01-09 09:10:05 +01:00
* Copyright ( c ) 2024 - 2025 , Jelle Raaijmakers < jelle @ ladybird . org >
2025-07-03 21:28:49 +02:00
* Copyright ( c ) 2024 - 2025 , Aliaksandr Kalenik < kalenik . aliaksandr @ gmail . com >
2020-12-06 19:51:55 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-12-06 19:51:55 +01:00
*/
2022-09-25 16:15:49 -06:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/RangePrototype.h>
2022-03-21 18:58:00 +01:00
# include <LibWeb/DOM/Comment.h>
2020-12-06 19:51:55 +01:00
# include <LibWeb/DOM/Document.h>
2022-03-21 18:06:28 +01:00
# include <LibWeb/DOM/DocumentFragment.h>
2022-01-31 18:05:54 +00:00
# include <LibWeb/DOM/DocumentType.h>
2023-03-10 14:56:44 +01:00
# include <LibWeb/DOM/ElementFactory.h>
2024-02-25 04:20:37 +01:00
# include <LibWeb/DOM/Event.h>
2020-12-06 19:51:55 +01:00
# include <LibWeb/DOM/Node.h>
2022-03-21 18:58:00 +01:00
# include <LibWeb/DOM/ProcessingInstruction.h>
2020-12-06 19:51:55 +01:00
# include <LibWeb/DOM/Range.h>
2024-10-31 21:59:19 +01:00
# include <LibWeb/DOM/SelectionchangeEventDispatching.h>
2022-03-21 16:29:19 +01:00
# include <LibWeb/DOM/Text.h>
2022-12-09 18:50:42 +00:00
# include <LibWeb/Geometry/DOMRect.h>
2024-01-19 13:55:37 +01:00
# include <LibWeb/Geometry/DOMRectList.h>
2023-03-10 14:56:44 +01:00
# include <LibWeb/HTML/HTMLHtmlElement.h>
2022-03-07 23:08:26 +01:00
# include <LibWeb/HTML/Window.h>
2023-03-10 14:56:44 +01:00
# include <LibWeb/Namespace.h>
2024-03-18 07:42:38 +01:00
# include <LibWeb/Painting/ViewportPaintable.h>
2020-12-06 19:51:55 +01:00
namespace Web : : DOM {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( Range ) ;
2023-11-19 19:47:52 +01:00
2022-03-21 20:05:25 +01:00
HashTable < Range * > & Range : : live_ranges ( )
{
static HashTable < Range * > ranges ;
return ranges ;
}
2024-11-15 04:01:23 +13:00
GC : : Ref < Range > Range : : create ( HTML : : Window & window )
2021-02-21 23:41:54 +01:00
{
2025-05-14 12:56:03 +02:00
return create ( window . associated_document ( ) ) ;
2021-02-21 23:41:54 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < Range > Range : : create ( Document & document )
2021-02-21 23:41:54 +01:00
{
2022-09-25 16:15:49 -06:00
auto & realm = document . realm ( ) ;
2024-11-14 05:50:17 +13:00
return realm . create < Range > ( document ) ;
2021-02-21 23:41:54 +01:00
}
2024-11-30 10:32:32 +01:00
GC : : Ref < Range > Range : : create ( GC : : Ref < Node > start_container , WebIDL : : UnsignedLong start_offset , GC : : Ref < Node > end_container , WebIDL : : UnsignedLong end_offset )
2021-02-21 23:41:54 +01:00
{
2024-11-30 10:32:32 +01:00
auto & realm = start_container - > realm ( ) ;
2024-11-14 05:50:17 +13:00
return realm . create < Range > ( start_container , start_offset , end_container , end_offset ) ;
2021-02-21 23:41:54 +01:00
}
2022-01-30 23:35:51 +00:00
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Range > > Range : : construct_impl ( JS : : Realm & realm )
2021-02-21 23:41:54 +01:00
{
2025-01-21 09:12:05 -05:00
auto & window = as < HTML : : Window > ( realm . global_object ( ) ) ;
2025-05-14 12:56:03 +02:00
return create ( window ) ;
2021-02-21 23:41:54 +01:00
}
Range : : Range ( Document & document )
: Range ( document , 0 , document , 0 )
2020-12-06 19:51:55 +01:00
{
}
2024-11-30 10:32:32 +01:00
Range : : Range ( GC : : Ref < Node > start_container , WebIDL : : UnsignedLong start_offset , GC : : Ref < Node > end_container , WebIDL : : UnsignedLong end_offset )
2022-01-30 23:35:51 +00:00
: AbstractRange ( start_container , start_offset , end_container , end_offset )
{
2025-05-14 15:41:32 +02:00
VERIFY ( start_offset < = start_container - > length ( ) ) ;
VERIFY ( end_offset < = end_container - > length ( ) ) ;
2022-03-21 20:05:25 +01:00
live_ranges ( ) . set ( this ) ;
}
Range : : ~ Range ( )
{
live_ranges ( ) . remove ( this ) ;
2022-01-30 23:35:51 +00:00
}
2023-08-07 08:41:28 +02:00
void Range : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( Range ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:28:20 -05:00
}
2023-01-11 19:48:53 +01:00
void Range : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_associated_selection ) ;
}
2024-11-15 04:01:23 +13:00
void Range : : set_associated_selection ( Badge < Selection : : Selection > , GC : : Ptr < Selection : : Selection > selection )
2023-01-11 19:48:53 +01:00
{
m_associated_selection = selection ;
update_associated_selection ( ) ;
}
void Range : : update_associated_selection ( )
{
2025-07-03 21:28:49 +02:00
if ( ! m_associated_selection )
return ;
2024-10-09 13:50:06 +02:00
auto & document = m_start_container - > document ( ) ;
if ( auto * viewport = document . paintable ( ) ) {
2024-09-16 19:38:14 +08:00
viewport - > recompute_selection_states ( * this ) ;
2024-03-18 07:42:38 +01:00
viewport - > set_needs_display ( ) ;
2023-01-11 19:48:53 +01:00
}
2024-02-25 04:20:37 +01:00
2024-10-09 13:50:06 +02:00
// https://w3c.github.io/selection-api/#selectionchange-event
// When the selection is dissociated with its range, associated with a new range, or the
// associated range's boundary point is mutated either by the user or the content script, the
// user agent must schedule a selectionchange event on document.
2024-10-31 21:59:19 +01:00
schedule_a_selectionchange_event ( document , document ) ;
2024-10-09 13:50:06 +02:00
}
2022-01-31 18:05:54 +00:00
// https://dom.spec.whatwg.org/#concept-range-root
2024-11-30 10:32:32 +01:00
GC : : Ref < Node > Range : : root ( ) const
2022-01-31 18:05:54 +00:00
{
// The root of a live range is the root of its start node.
return m_start_container - > root ( ) ;
}
// https://dom.spec.whatwg.org/#concept-range-bp-position
2024-12-18 13:30:19 +01:00
RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point ( BoundaryPoint a , BoundaryPoint b )
2022-01-31 18:05:54 +00:00
{
// 1. Assert: nodeA and nodeB have the same root.
2024-12-02 14:41:07 +00:00
// NOTE: Nodes may not share the same root if they belong to different shadow trees,
// so we assert that they share the same shadow-including root instead.
2024-12-18 13:30:19 +01:00
VERIFY ( & a . node - > shadow_including_root ( ) = = & b . node - > shadow_including_root ( ) ) ;
2022-01-31 18:05:54 +00:00
// 2. If nodeA is nodeB, then return equal if offsetA is offsetB, before if offsetA is less than offsetB, and after if offsetA is greater than offsetB.
2024-12-18 13:30:19 +01:00
if ( a . node = = b . node ) {
if ( a . offset = = b . offset )
2022-01-31 18:05:54 +00:00
return RelativeBoundaryPointPosition : : Equal ;
2024-12-18 13:30:19 +01:00
if ( a . offset < b . offset )
2022-01-31 18:05:54 +00:00
return RelativeBoundaryPointPosition : : Before ;
return RelativeBoundaryPointPosition : : After ;
}
// 3. If nodeA is following nodeB, then if the position of (nodeB, offsetB) relative to (nodeA, offsetA) is before, return after, and if it is after, return before.
2024-12-18 13:30:19 +01:00
if ( a . node - > is_following ( b . node ) ) {
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point ( b , a ) ;
2022-01-31 18:05:54 +00:00
if ( relative_position = = RelativeBoundaryPointPosition : : Before )
return RelativeBoundaryPointPosition : : After ;
if ( relative_position = = RelativeBoundaryPointPosition : : After )
return RelativeBoundaryPointPosition : : Before ;
}
// 4. If nodeA is an ancestor of nodeB:
2024-12-18 13:30:19 +01:00
if ( a . node - > is_ancestor_of ( b . node ) ) {
2022-01-31 18:05:54 +00:00
// 1. Let child be nodeB.
2024-12-18 13:30:19 +01:00
GC : : Ref < Node const > child = b . node ;
2022-01-31 18:05:54 +00:00
// 2. While child is not a child of nodeA, set child to its parent.
2024-12-18 13:30:19 +01:00
while ( ! a . node - > is_parent_of ( child ) ) {
2022-01-31 18:05:54 +00:00
auto * parent = child - > parent ( ) ;
VERIFY ( parent ) ;
2022-12-14 13:43:57 +01:00
child = * parent ;
2022-01-31 18:05:54 +00:00
}
// 3. If child’ s index is less than offsetA, then return after.
2024-12-18 13:30:19 +01:00
if ( child - > index ( ) < a . offset )
2022-01-31 18:05:54 +00:00
return RelativeBoundaryPointPosition : : After ;
}
// 5. Return before.
return RelativeBoundaryPointPosition : : Before ;
}
2025-05-14 12:56:03 +02:00
// https://dom.spec.whatwg.org/#concept-range-bp-set
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start_or_end ( GC : : Ref < Node > node , u32 offset , StartOrEnd start_or_end )
2022-01-31 18:05:54 +00:00
{
// To set the start or end of a range to a boundary point (node, offset), run these steps:
// 1. If node is a doctype, then throw an "InvalidNodeTypeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( is < DocumentType > ( * node ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _string ) ;
2022-01-31 18:05:54 +00:00
// 2. If offset is greater than node’ s length, then throw an "IndexSizeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( offset > node - > length ( ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : IndexSizeError : : create ( realm ( ) , MUST ( String : : formatted ( " Node does not contain a child at offset {} " , offset ) ) ) ;
2022-01-31 18:05:54 +00:00
// 3. Let bp be the boundary point (node, offset).
if ( start_or_end = = StartOrEnd : : Start ) {
// -> If these steps were invoked as "set the start"
// 1. If range’ s root is not equal to node’ s root, or if bp is after the range’ s end, set range’ s end to bp.
2024-12-18 13:30:19 +01:00
if ( root ( ) . ptr ( ) ! = & node - > root ( ) | | position_of_boundary_point_relative_to_other_boundary_point ( { node , offset } , end ( ) ) = = RelativeBoundaryPointPosition : : After ) {
2022-12-14 13:43:57 +01:00
m_end_container = node ;
2022-01-31 18:05:54 +00:00
m_end_offset = offset ;
}
// 2. Set range’ s start to bp.
2022-12-14 13:43:57 +01:00
m_start_container = node ;
2022-01-31 18:05:54 +00:00
m_start_offset = offset ;
} else {
// -> If these steps were invoked as "set the end"
VERIFY ( start_or_end = = StartOrEnd : : End ) ;
// 1. If range’ s root is not equal to node’ s root, or if bp is before the range’ s start, set range’ s start to bp.
2024-12-18 13:30:19 +01:00
if ( root ( ) . ptr ( ) ! = & node - > root ( ) | | position_of_boundary_point_relative_to_other_boundary_point ( { node , offset } , start ( ) ) = = RelativeBoundaryPointPosition : : Before ) {
2022-12-14 13:43:57 +01:00
m_start_container = node ;
2022-01-31 18:05:54 +00:00
m_start_offset = offset ;
}
// 2. Set range’ s end to bp.
2022-12-14 13:43:57 +01:00
m_end_container = node ;
2022-01-31 18:05:54 +00:00
m_end_offset = offset ;
}
2023-01-11 19:48:53 +01:00
update_associated_selection ( ) ;
2022-01-31 18:05:54 +00:00
return { } ;
}
2025-05-14 12:56:03 +02:00
// https://dom.spec.whatwg.org/#dom-range-setstart
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start ( GC : : Ref < Node > node , WebIDL : : UnsignedLong offset )
2022-01-31 18:05:54 +00:00
{
// The setStart(node, offset) method steps are to set the start of this to boundary point (node, offset).
return set_start_or_end ( node , offset , StartOrEnd : : Start ) ;
}
2025-05-14 12:56:03 +02:00
// https://dom.spec.whatwg.org/#dom-range-setend
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_end ( GC : : Ref < Node > node , WebIDL : : UnsignedLong offset )
2022-01-31 18:05:54 +00:00
{
// The setEnd(node, offset) method steps are to set the end of this to boundary point (node, offset).
return set_start_or_end ( node , offset , StartOrEnd : : End ) ;
}
2022-01-31 17:35:03 +00:00
// https://dom.spec.whatwg.org/#dom-range-setstartbefore
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start_before ( GC : : Ref < Node > node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
2024-11-30 10:32:32 +01:00
auto * parent = node - > parent ( ) ;
2022-01-31 17:35:03 +00:00
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the start of this to boundary point (parent, node’ s index).
2024-11-30 10:32:32 +01:00
return set_start_or_end ( * parent , node - > index ( ) , StartOrEnd : : Start ) ;
2022-01-31 17:35:03 +00:00
}
// https://dom.spec.whatwg.org/#dom-range-setstartafter
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start_after ( GC : : Ref < Node > node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
2024-11-30 10:32:32 +01:00
auto * parent = node - > parent ( ) ;
2022-01-31 17:35:03 +00:00
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the start of this to boundary point (parent, node’ s index plus 1).
2024-11-30 10:32:32 +01:00
return set_start_or_end ( * parent , node - > index ( ) + 1 , StartOrEnd : : Start ) ;
2022-01-31 17:35:03 +00:00
}
// https://dom.spec.whatwg.org/#dom-range-setendbefore
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_end_before ( GC : : Ref < Node > node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
2024-11-30 10:32:32 +01:00
auto * parent = node - > parent ( ) ;
2022-01-31 17:35:03 +00:00
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the end of this to boundary point (parent, node’ s index).
2024-11-30 10:32:32 +01:00
return set_start_or_end ( * parent , node - > index ( ) , StartOrEnd : : End ) ;
2022-01-31 17:35:03 +00:00
}
// https://dom.spec.whatwg.org/#dom-range-setendafter
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : set_end_after ( GC : : Ref < Node > node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
2024-11-30 10:32:32 +01:00
auto * parent = node - > parent ( ) ;
2022-01-31 17:35:03 +00:00
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the end of this to boundary point (parent, node’ s index plus 1).
2024-11-30 10:32:32 +01:00
return set_start_or_end ( * parent , node - > index ( ) + 1 , StartOrEnd : : End ) ;
2022-01-31 17:35:03 +00:00
}
2022-01-31 18:13:15 +00:00
// https://dom.spec.whatwg.org/#dom-range-compareboundarypoints
2024-01-04 09:14:41 +13:00
WebIDL : : ExceptionOr < WebIDL : : Short > Range : : compare_boundary_points ( WebIDL : : UnsignedShort how , Range const & source_range ) const
2022-01-31 18:13:15 +00:00
{
// 1. If how is not one of
// - START_TO_START,
// - START_TO_END,
// - END_TO_END, and
// - END_TO_START,
// then throw a "NotSupportedError" DOMException.
if ( how ! = HowToCompareBoundaryPoints : : START_TO_START & & how ! = HowToCompareBoundaryPoints : : START_TO_END & & how ! = HowToCompareBoundaryPoints : : END_TO_END & & how ! = HowToCompareBoundaryPoints : : END_TO_START )
2023-09-06 16:03:01 +12:00
return WebIDL : : NotSupportedError : : create ( realm ( ) , MUST ( String : : formatted ( " Expected 'how' to be one of START_TO_START (0), START_TO_END (1), END_TO_END (2) or END_TO_START (3), got {} " , how ) ) ) ;
2022-01-31 18:13:15 +00:00
// 2. If this’ s root is not the same as sourceRange’ s root, then throw a "WrongDocumentError" DOMException.
2024-11-30 10:32:32 +01:00
if ( root ( ) ! = source_range . root ( ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : WrongDocumentError : : create ( realm ( ) , " This range is not in the same tree as the source range. " _string ) ;
2022-01-31 18:13:15 +00:00
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > this_point_node ;
2022-01-31 18:13:15 +00:00
u32 this_point_offset = 0 ;
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > other_point_node ;
2022-01-31 18:13:15 +00:00
u32 other_point_offset = 0 ;
// 3. If how is:
switch ( how ) {
case HowToCompareBoundaryPoints : : START_TO_START :
// -> START_TO_START:
// Let this point be this’ s start. Let other point be sourceRange’ s start.
this_point_node = m_start_container ;
this_point_offset = m_start_offset ;
other_point_node = source_range . m_start_container ;
other_point_offset = source_range . m_start_offset ;
break ;
case HowToCompareBoundaryPoints : : START_TO_END :
// -> START_TO_END:
// Let this point be this’ s end. Let other point be sourceRange’ s start.
this_point_node = m_end_container ;
this_point_offset = m_end_offset ;
other_point_node = source_range . m_start_container ;
other_point_offset = source_range . m_start_offset ;
break ;
case HowToCompareBoundaryPoints : : END_TO_END :
// -> END_TO_END:
// Let this point be this’ s end. Let other point be sourceRange’ s end.
this_point_node = m_end_container ;
this_point_offset = m_end_offset ;
other_point_node = source_range . m_end_container ;
other_point_offset = source_range . m_end_offset ;
break ;
case HowToCompareBoundaryPoints : : END_TO_START :
// -> END_TO_START:
// Let this point be this’ s start. Let other point be sourceRange’ s end.
this_point_node = m_start_container ;
this_point_offset = m_start_offset ;
other_point_node = source_range . m_end_container ;
other_point_offset = source_range . m_end_offset ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
VERIFY ( this_point_node ) ;
VERIFY ( other_point_node ) ;
// 4. If the position of this point relative to other point is
2024-12-18 13:30:19 +01:00
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point ( { * this_point_node , this_point_offset } , { * other_point_node , other_point_offset } ) ;
2022-01-31 18:13:15 +00:00
switch ( relative_position ) {
case RelativeBoundaryPointPosition : : Before :
// -> before
// Return − 1.
return - 1 ;
case RelativeBoundaryPointPosition : : Equal :
// -> equal
// Return 0.
return 0 ;
case RelativeBoundaryPointPosition : : After :
// -> after
// Return 1.
return 1 ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2022-01-31 18:33:41 +00:00
// https://dom.spec.whatwg.org/#concept-range-select
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : select ( GC : : Ref < Node > node )
2022-01-31 18:33:41 +00:00
{
// 1. Let parent be node’ s parent.
2024-11-30 10:32:32 +01:00
auto * parent = node - > parent ( ) ;
2022-01-31 18:33:41 +00:00
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _string ) ;
2022-01-31 18:33:41 +00:00
// 3. Let index be node’ s index.
2024-11-30 10:32:32 +01:00
auto index = node - > index ( ) ;
2022-01-31 18:33:41 +00:00
// 4. Set range’ s start to boundary point (parent, index).
2022-12-14 13:43:57 +01:00
m_start_container = * parent ;
2022-01-31 18:33:41 +00:00
m_start_offset = index ;
// 5. Set range’ s end to boundary point (parent, index plus 1).
2022-12-14 13:43:57 +01:00
m_end_container = * parent ;
2022-01-31 18:33:41 +00:00
m_end_offset = index + 1 ;
2023-01-11 19:48:53 +01:00
update_associated_selection ( ) ;
2022-01-31 18:33:41 +00:00
return { } ;
}
// https://dom.spec.whatwg.org/#dom-range-selectnode
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : select_node ( GC : : Ref < Node > node )
2022-01-31 18:33:41 +00:00
{
// The selectNode(node) method steps are to select node within this.
return select ( node ) ;
}
2022-01-31 18:42:36 +00:00
// https://dom.spec.whatwg.org/#dom-range-collapse
void Range : : collapse ( bool to_start )
{
// The collapse(toStart) method steps are to, if toStart is true, set end to start; otherwise set start to end.
if ( to_start ) {
m_end_container = m_start_container ;
m_end_offset = m_start_offset ;
2023-01-11 19:48:53 +01:00
} else {
m_start_container = m_end_container ;
m_start_offset = m_end_offset ;
2022-01-31 18:42:36 +00:00
}
2023-01-11 19:48:53 +01:00
update_associated_selection ( ) ;
2022-01-31 18:42:36 +00:00
}
2022-01-31 18:51:46 +00:00
// https://dom.spec.whatwg.org/#dom-range-selectnodecontents
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < void > Range : : select_node_contents ( GC : : Ref < Node > node )
2022-01-31 18:51:46 +00:00
{
// 1. If node is a doctype, throw an "InvalidNodeTypeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( is < DocumentType > ( * node ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _string ) ;
2022-01-31 18:51:46 +00:00
// 2. Let length be the length of node.
2024-11-30 10:32:32 +01:00
auto length = node - > length ( ) ;
2022-01-31 18:51:46 +00:00
// 3. Set start to the boundary point (node, 0).
2022-12-14 13:43:57 +01:00
m_start_container = node ;
2022-01-31 18:51:46 +00:00
m_start_offset = 0 ;
// 4. Set end to the boundary point (node, length).
2022-12-14 13:43:57 +01:00
m_end_container = node ;
2022-01-31 18:51:46 +00:00
m_end_offset = length ;
2023-01-11 19:48:53 +01:00
update_associated_selection ( ) ;
2022-01-31 18:51:46 +00:00
return { } ;
}
2024-11-15 04:01:23 +13:00
GC : : Ref < Range > Range : : clone_range ( ) const
2020-12-06 19:51:55 +01:00
{
2024-11-14 05:50:17 +13:00
return shape ( ) . realm ( ) . create < Range > ( const_cast < Node & > ( * m_start_container ) , m_start_offset , const_cast < Node & > ( * m_end_container ) , m_end_offset ) ;
2020-12-06 19:51:55 +01:00
}
2022-02-25 20:45:03 +01:00
// https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > Range : : common_ancestor_container ( ) const
2022-02-25 20:45:03 +01:00
{
// 1. Let container be start node.
auto container = m_start_container ;
// 2. While container is not an inclusive ancestor of end node, let container be container’ s parent.
while ( ! container - > is_inclusive_ancestor_of ( m_end_container ) ) {
VERIFY ( container - > parent ( ) ) ;
2022-12-14 13:43:57 +01:00
container = * container - > parent ( ) ;
2022-02-25 20:45:03 +01:00
}
// 3. Return container.
return container ;
}
2022-01-31 19:10:12 +00:00
// https://dom.spec.whatwg.org/#dom-range-intersectsnode
2024-11-30 10:32:32 +01:00
bool Range : : intersects_node ( GC : : Ref < Node > node ) const
2022-01-31 19:10:12 +00:00
{
// 1. If node’ s root is different from this’ s root, return false.
2024-11-30 10:32:32 +01:00
if ( & node - > root ( ) ! = root ( ) . ptr ( ) )
2022-01-31 19:10:12 +00:00
return false ;
// 2. Let parent be node’ s parent.
2024-11-30 10:32:32 +01:00
auto * parent = node - > parent ( ) ;
2022-01-31 19:10:12 +00:00
// 3. If parent is null, return true.
if ( ! parent )
return true ;
// 4. Let offset be node’ s index.
2024-12-18 13:30:19 +01:00
WebIDL : : UnsignedLong offset = node - > index ( ) ;
2022-01-31 19:10:12 +00:00
// 5. If (parent, offset) is before end and (parent, offset plus 1) is after start, return true
2024-12-18 13:30:19 +01:00
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point ( { * parent , offset } , end ( ) ) ;
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point ( { * parent , offset + 1 } , start ( ) ) ;
2022-01-31 19:10:12 +00:00
if ( relative_position_to_end = = RelativeBoundaryPointPosition : : Before & & relative_position_to_start = = RelativeBoundaryPointPosition : : After )
return true ;
// 6. Return false.
return false ;
}
2022-02-01 18:45:34 +00:00
// https://dom.spec.whatwg.org/#dom-range-ispointinrange
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < bool > Range : : is_point_in_range ( GC : : Ref < Node > node , WebIDL : : UnsignedLong offset ) const
2022-02-01 18:45:34 +00:00
{
// 1. If node’ s root is different from this’ s root, return false.
2024-11-30 10:32:32 +01:00
if ( & node - > root ( ) ! = root ( ) . ptr ( ) )
2022-02-01 18:45:34 +00:00
return false ;
// 2. If node is a doctype, then throw an "InvalidNodeTypeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( is < DocumentType > ( * node ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _string ) ;
2022-02-01 18:45:34 +00:00
// 3. If offset is greater than node’ s length, then throw an "IndexSizeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( offset > node - > length ( ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : IndexSizeError : : create ( realm ( ) , MUST ( String : : formatted ( " Node does not contain a child at offset {} " , offset ) ) ) ;
2022-02-01 18:45:34 +00:00
// 4. If (node, offset) is before start or after end, return false.
2024-12-18 13:30:19 +01:00
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point ( { node , offset } , start ( ) ) ;
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point ( { node , offset } , end ( ) ) ;
2022-02-01 18:45:34 +00:00
if ( relative_position_to_start = = RelativeBoundaryPointPosition : : Before | | relative_position_to_end = = RelativeBoundaryPointPosition : : After )
return false ;
// 5. Return true.
return true ;
}
2022-02-01 18:53:58 +00:00
// https://dom.spec.whatwg.org/#dom-range-comparepoint
2024-11-30 10:32:32 +01:00
WebIDL : : ExceptionOr < WebIDL : : Short > Range : : compare_point ( GC : : Ref < Node > node , WebIDL : : UnsignedLong offset ) const
2022-02-01 18:53:58 +00:00
{
// 1. If node’ s root is different from this’ s root, then throw a "WrongDocumentError" DOMException.
2024-11-30 10:32:32 +01:00
if ( & node - > root ( ) ! = root ( ) . ptr ( ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : WrongDocumentError : : create ( realm ( ) , " Given node is not in the same document as the range. " _string ) ;
2022-02-01 18:53:58 +00:00
// 2. If node is a doctype, then throw an "InvalidNodeTypeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( is < DocumentType > ( * node ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _string ) ;
2022-02-01 18:53:58 +00:00
// 3. If offset is greater than node’ s length, then throw an "IndexSizeError" DOMException.
2024-11-30 10:32:32 +01:00
if ( offset > node - > length ( ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : IndexSizeError : : create ( realm ( ) , MUST ( String : : formatted ( " Node does not contain a child at offset {} " , offset ) ) ) ;
2022-02-01 18:53:58 +00:00
// 4. If (node, offset) is before start, return − 1.
2024-12-18 13:30:19 +01:00
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point ( { node , offset } , start ( ) ) ;
2022-02-01 18:53:58 +00:00
if ( relative_position_to_start = = RelativeBoundaryPointPosition : : Before )
return - 1 ;
// 5. If (node, offset) is after end, return 1.
2024-12-18 13:30:19 +01:00
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point ( { node , offset } , end ( ) ) ;
2022-02-01 18:53:58 +00:00
if ( relative_position_to_end = = RelativeBoundaryPointPosition : : After )
return 1 ;
// 6. Return 0.
return 0 ;
}
2022-03-21 16:29:19 +01:00
// https://dom.spec.whatwg.org/#dom-range-stringifier
2023-08-26 16:46:33 +12:00
String Range : : to_string ( ) const
2022-03-21 16:29:19 +01:00
{
// 1. Let s be the empty string.
StringBuilder builder ;
// 2. If this’ s start node is this’ s end node and it is a Text node,
// then return the substring of that Text node’ s data beginning at this’ s start offset and ending at this’ s end offset.
2024-01-04 10:27:25 +13:00
if ( start_container ( ) = = end_container ( ) & & is < Text > ( * start_container ( ) ) ) {
auto const & text = static_cast < Text const & > ( * start_container ( ) ) ;
return MUST ( text . substring_data ( start_offset ( ) , end_offset ( ) - start_offset ( ) ) ) ;
}
2022-03-21 16:29:19 +01:00
// 3. If this’ s start node is a Text node, then append the substring of that node’ s data from this’ s start offset until the end to s.
2024-01-04 10:27:25 +13:00
if ( is < Text > ( * start_container ( ) ) ) {
auto const & text = static_cast < Text const & > ( * start_container ( ) ) ;
builder . append ( MUST ( text . substring_data ( start_offset ( ) , text . length_in_utf16_code_units ( ) - start_offset ( ) ) ) ) ;
}
2022-03-21 16:29:19 +01:00
// 4. Append the concatenation of the data of all Text nodes that are contained in this, in tree order, to s.
2025-01-09 10:20:07 +01:00
for_each_contained ( [ & ] ( GC : : Ref < DOM : : Node > node ) {
if ( is < Text > ( * node ) )
2022-03-21 16:29:19 +01:00
builder . append ( static_cast < Text const & > ( * node ) . data ( ) ) ;
2025-01-09 10:20:07 +01:00
return IterationDecision : : Continue ;
} ) ;
2022-03-21 16:29:19 +01:00
// 5. If this’ s end node is a Text node, then append the substring of that node’ s data from its start until this’ s end offset to s.
2024-01-04 10:27:25 +13:00
if ( is < Text > ( * end_container ( ) ) ) {
auto const & text = static_cast < Text const & > ( * end_container ( ) ) ;
builder . append ( MUST ( text . substring_data ( 0 , end_offset ( ) ) ) ) ;
}
2022-03-21 16:29:19 +01:00
// 6. Return s.
2023-08-26 16:46:33 +12:00
return MUST ( builder . to_string ( ) ) ;
2022-03-21 16:29:19 +01:00
}
2022-03-21 18:06:28 +01:00
// https://dom.spec.whatwg.org/#dom-range-extractcontents
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < DocumentFragment > > Range : : extract_contents ( )
2022-03-21 18:06:28 +01:00
{
return extract ( ) ;
}
// https://dom.spec.whatwg.org/#concept-range-extract
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < DocumentFragment > > Range : : extract ( )
2022-03-21 18:06:28 +01:00
{
// 1. Let fragment be a new DocumentFragment node whose node document is range’ s start node’ s node document.
2024-11-14 05:50:17 +13:00
auto fragment = realm ( ) . create < DOM : : DocumentFragment > ( const_cast < Document & > ( start_container ( ) - > document ( ) ) ) ;
2022-03-21 18:06:28 +01:00
// 2. If range is collapsed, then return fragment.
if ( collapsed ( ) )
2022-12-14 17:40:33 +00:00
return fragment ;
2022-03-21 18:06:28 +01:00
// 3. Let original start node, original start offset, original end node, and original end offset
// be range’ s start node, start offset, end node, and end offset, respectively.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > original_start_node = m_start_container ;
2022-03-21 18:06:28 +01:00
auto original_start_offset = m_start_offset ;
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > original_end_node = m_end_container ;
2022-03-21 18:06:28 +01:00
auto original_end_offset = m_end_offset ;
// 4. If original start node is original end node and it is a CharacterData node, then:
if ( original_start_node . ptr ( ) = = original_end_node . ptr ( ) & & is < CharacterData > ( * original_start_node ) ) {
// 1. Let clone be a clone of original start node.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( original_start_node - > clone_node ( ) ) ;
2022-03-21 18:06:28 +01:00
// 2. Set the data of clone to the result of substringing data with node original start node,
// offset original start offset, and count original end offset minus original start offset.
2022-03-22 07:49:05 -04:00
auto result = TRY ( static_cast < CharacterData const & > ( * original_start_node ) . substring_data ( original_start_offset , original_end_offset - original_start_offset ) ) ;
2025-01-21 09:12:05 -05:00
as < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
2022-03-21 18:06:28 +01:00
// 3. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-21 18:06:28 +01:00
// 4. Replace data with node original start node, offset original start offset, count original end offset minus original start offset, and data the empty string.
2023-09-07 21:36:05 +12:00
TRY ( static_cast < CharacterData & > ( * original_start_node ) . replace_data ( original_start_offset , original_end_offset - original_start_offset , String { } ) ) ;
2022-03-21 18:06:28 +01:00
// 5. Return fragment.
2022-12-14 17:40:33 +00:00
return fragment ;
2022-03-21 18:06:28 +01:00
}
// 5. Let common ancestor be original start node.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > common_ancestor = original_start_node ;
2022-03-21 18:06:28 +01:00
// 6. While common ancestor is not an inclusive ancestor of original end node, set common ancestor to its own parent.
while ( ! common_ancestor - > is_inclusive_ancestor_of ( original_end_node ) )
2022-12-14 13:43:57 +01:00
common_ancestor = * common_ancestor - > parent_node ( ) ;
2022-03-21 18:06:28 +01:00
// 7. Let first partially contained child be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > first_partially_contained_child ;
2022-03-21 18:06:28 +01:00
// 8. If original start node is not an inclusive ancestor of original end node,
// set first partially contained child to the first child of common ancestor that is partially contained in range.
if ( ! original_start_node - > is_inclusive_ancestor_of ( original_end_node ) ) {
for ( auto * child = common_ancestor - > first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
if ( partially_contains_node ( * child ) ) {
first_partially_contained_child = child ;
break ;
}
}
}
// 9. Let last partially contained child be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > last_partially_contained_child ;
2022-03-21 18:06:28 +01:00
// 10. If original end node is not an inclusive ancestor of original start node,
// set last partially contained child to the last child of common ancestor that is partially contained in range.
if ( ! original_end_node - > is_inclusive_ancestor_of ( original_start_node ) ) {
for ( auto * child = common_ancestor - > last_child ( ) ; child ; child = child - > previous_sibling ( ) ) {
if ( partially_contains_node ( * child ) ) {
last_partially_contained_child = child ;
break ;
}
}
}
// 11. Let contained children be a list of all children of common ancestor that are contained in range, in tree order.
2024-11-15 04:01:23 +13:00
Vector < GC : : Ref < Node > > contained_children ;
2023-02-25 10:44:51 -07:00
for ( Node * node = common_ancestor - > first_child ( ) ; node ; node = node - > next_sibling ( ) ) {
2022-03-21 18:06:28 +01:00
if ( contains_node ( * node ) )
contained_children . append ( * node ) ;
}
// 12. If any member of contained children is a doctype, then throw a "HierarchyRequestError" DOMException.
for ( auto const & child : contained_children ) {
if ( is < DocumentType > ( * child ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Contained child is a DocumentType " _string ) ;
2022-03-21 18:06:28 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > new_node ;
2022-03-21 18:06:28 +01:00
size_t new_offset = 0 ;
// 13. If original start node is an inclusive ancestor of original end node, set new node to original start node and new offset to original start offset.
if ( original_start_node - > is_inclusive_ancestor_of ( original_end_node ) ) {
new_node = original_start_node ;
new_offset = original_start_offset ;
}
// 14. Otherwise:
else {
// 1. Let reference node equal original start node.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > reference_node = original_start_node ;
2022-03-21 18:06:28 +01:00
// 2. While reference node’ s parent is not null and is not an inclusive ancestor of original end node, set reference node to its parent.
while ( reference_node - > parent_node ( ) & & ! reference_node - > parent_node ( ) - > is_inclusive_ancestor_of ( original_end_node ) )
reference_node = reference_node - > parent_node ( ) ;
// 3. Set new node to the parent of reference node, and new offset to one plus reference node’ s index.
new_node = reference_node - > parent_node ( ) ;
new_offset = 1 + reference_node - > index ( ) ;
}
// 15. If first partially contained child is a CharacterData node, then:
if ( first_partially_contained_child & & is < CharacterData > ( * first_partially_contained_child ) ) {
// 1. Let clone be a clone of original start node.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( original_start_node - > clone_node ( ) ) ;
2022-03-21 18:06:28 +01:00
2022-03-21 21:01:47 +01:00
// 2. Set the data of clone to the result of substringing data with node original start node, offset original start offset,
// and count original start node’ s length minus original start offset.
2022-03-22 07:49:05 -04:00
auto result = TRY ( static_cast < CharacterData const & > ( * original_start_node ) . substring_data ( original_start_offset , original_start_node - > length ( ) - original_start_offset ) ) ;
2025-01-21 09:12:05 -05:00
as < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
2022-03-21 18:06:28 +01:00
// 3. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-21 18:06:28 +01:00
// 4. Replace data with node original start node, offset original start offset, count original start node’ s length minus original start offset, and data the empty string.
2023-09-07 21:36:05 +12:00
TRY ( static_cast < CharacterData & > ( * original_start_node ) . replace_data ( original_start_offset , original_start_node - > length ( ) - original_start_offset , String { } ) ) ;
2022-03-21 18:06:28 +01:00
}
// 16. Otherwise, if first partially contained child is not null:
else if ( first_partially_contained_child ) {
// 1. Let clone be a clone of first partially contained child.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( first_partially_contained_child - > clone_node ( ) ) ;
2022-03-21 18:06:28 +01:00
// 2. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-21 18:06:28 +01:00
// 3. Let subrange be a new live range whose start is (original start node, original start offset) and whose end is (first partially contained child, first partially contained child’ s length).
2023-08-13 13:05:26 +02:00
auto subrange = Range : : create ( original_start_node , original_start_offset , * first_partially_contained_child , first_partially_contained_child - > length ( ) ) ;
2022-03-21 18:06:28 +01:00
// 4. Let subfragment be the result of extracting subrange.
2022-03-22 07:49:05 -04:00
auto subfragment = TRY ( subrange - > extract ( ) ) ;
2022-03-21 18:06:28 +01:00
// 5. Append subfragment to clone.
2022-10-30 17:50:04 +00:00
TRY ( clone - > append_child ( subfragment ) ) ;
2022-03-21 18:06:28 +01:00
}
// 17. For each contained child in contained children, append contained child to fragment.
for ( auto & contained_child : contained_children ) {
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( contained_child ) ) ;
2022-03-21 18:06:28 +01:00
}
// 18. If last partially contained child is a CharacterData node, then:
if ( last_partially_contained_child & & is < CharacterData > ( * last_partially_contained_child ) ) {
// 1. Let clone be a clone of original end node.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( original_end_node - > clone_node ( ) ) ;
2022-03-21 18:06:28 +01:00
// 2. Set the data of clone to the result of substringing data with node original end node, offset 0, and count original end offset.
2022-03-22 07:49:05 -04:00
auto result = TRY ( static_cast < CharacterData const & > ( * original_end_node ) . substring_data ( 0 , original_end_offset ) ) ;
2025-01-21 09:12:05 -05:00
as < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
2022-03-21 18:06:28 +01:00
// 3. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-21 18:06:28 +01:00
// 4. Replace data with node original end node, offset 0, count original end offset, and data the empty string.
2025-01-21 09:12:05 -05:00
TRY ( as < CharacterData > ( * original_end_node ) . replace_data ( 0 , original_end_offset , String { } ) ) ;
2022-03-21 18:06:28 +01:00
}
// 19. Otherwise, if last partially contained child is not null:
else if ( last_partially_contained_child ) {
// 1. Let clone be a clone of last partially contained child.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( last_partially_contained_child - > clone_node ( ) ) ;
2022-03-21 18:06:28 +01:00
// 2. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-21 18:06:28 +01:00
// 3. Let subrange be a new live range whose start is (last partially contained child, 0) and whose end is (original end node, original end offset).
2023-08-13 13:05:26 +02:00
auto subrange = Range : : create ( * last_partially_contained_child , 0 , original_end_node , original_end_offset ) ;
2022-03-21 18:06:28 +01:00
// 4. Let subfragment be the result of extracting subrange.
2022-03-22 07:49:05 -04:00
auto subfragment = TRY ( subrange - > extract ( ) ) ;
2022-03-21 18:06:28 +01:00
// 5. Append subfragment to clone.
2022-10-30 17:50:04 +00:00
TRY ( clone - > append_child ( subfragment ) ) ;
2022-03-21 18:06:28 +01:00
}
// 20. Set range’ s start and end to (new node, new offset).
2022-10-30 17:50:04 +00:00
TRY ( set_start ( * new_node , new_offset ) ) ;
TRY ( set_end ( * new_node , new_offset ) ) ;
2022-03-21 18:06:28 +01:00
// 21. Return fragment.
2022-12-14 17:40:33 +00:00
return fragment ;
2022-03-21 18:06:28 +01:00
}
// https://dom.spec.whatwg.org/#contained
2024-11-30 10:32:32 +01:00
bool Range : : contains_node ( GC : : Ref < Node > node ) const
2022-03-21 18:06:28 +01:00
{
// A node node is contained in a live range range if node’ s root is range’ s root,
2024-11-30 10:32:32 +01:00
if ( & node - > root ( ) ! = root ( ) . ptr ( ) )
2022-03-21 18:06:28 +01:00
return false ;
// and (node, 0) is after range’ s start,
2024-12-18 13:30:19 +01:00
if ( position_of_boundary_point_relative_to_other_boundary_point ( { node , 0 } , start ( ) ) ! = RelativeBoundaryPointPosition : : After )
2022-03-21 18:06:28 +01:00
return false ;
// and (node, node’ s length) is before range’ s end.
2024-12-18 13:30:19 +01:00
if ( position_of_boundary_point_relative_to_other_boundary_point ( { node , static_cast < WebIDL : : UnsignedLong > ( node - > length ( ) ) } , end ( ) ) ! = RelativeBoundaryPointPosition : : Before )
2022-03-21 18:06:28 +01:00
return false ;
return true ;
}
// https://dom.spec.whatwg.org/#partially-contained
2024-11-30 10:32:32 +01:00
bool Range : : partially_contains_node ( GC : : Ref < Node > node ) const
2022-03-21 18:06:28 +01:00
{
2025-01-09 09:10:05 +01:00
// A node is partially contained in a live range if it’ s an inclusive ancestor of the live range’ s start node but
// not its end node, or vice versa.
return node - > is_inclusive_ancestor_of ( m_start_container ) ! = node - > is_inclusive_ancestor_of ( m_end_container ) ;
2022-03-21 18:06:28 +01:00
}
2022-03-21 18:58:00 +01:00
// https://dom.spec.whatwg.org/#dom-range-insertnode
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > Range : : insert_node ( GC : : Ref < Node > node )
2022-03-21 18:58:00 +01:00
{
return insert ( node ) ;
}
// https://dom.spec.whatwg.org/#concept-range-insert
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > Range : : insert ( GC : : Ref < Node > node )
2022-03-21 18:58:00 +01:00
{
// 1. If range’ s start node is a ProcessingInstruction or Comment node, is a Text node whose parent is null, or is node, then throw a "HierarchyRequestError" DOMException.
if ( ( is < ProcessingInstruction > ( * m_start_container ) | | is < Comment > ( * m_start_container ) )
| | ( is < Text > ( * m_start_container ) & & ! m_start_container - > parent_node ( ) )
2022-08-28 13:42:07 +02:00
| | m_start_container . ptr ( ) = = node . ptr ( ) ) {
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Range has inappropriate start node for insertion " _string ) ;
2022-03-21 18:58:00 +01:00
}
// 2. Let referenceNode be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > reference_node ;
2022-03-21 18:58:00 +01:00
// 3. If range’ s start node is a Text node, set referenceNode to that Text node.
if ( is < Text > ( * m_start_container ) ) {
reference_node = m_start_container ;
}
// 4. Otherwise, set referenceNode to the child of start node whose index is start offset, and null if there is no such child.
else {
reference_node = m_start_container - > child_at_index ( m_start_offset ) ;
}
// 5. Let parent be range’ s start node if referenceNode is null, and referenceNode’ s parent otherwise.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > parent ;
2022-03-21 18:58:00 +01:00
if ( ! reference_node )
parent = m_start_container ;
else
parent = reference_node - > parent ( ) ;
// 6. Ensure pre-insertion validity of node into parent before referenceNode.
2022-03-22 07:49:05 -04:00
TRY ( parent - > ensure_pre_insertion_validity ( node , reference_node ) ) ;
2022-03-21 18:58:00 +01:00
// 7. If range’ s start node is a Text node, set referenceNode to the result of splitting it with offset range’ s start offset.
2022-03-22 07:49:05 -04:00
if ( is < Text > ( * m_start_container ) )
reference_node = TRY ( static_cast < Text & > ( * m_start_container ) . split_text ( m_start_offset ) ) ;
2022-03-21 18:58:00 +01:00
// 8. If node is referenceNode, set referenceNode to its next sibling.
if ( node = = reference_node )
reference_node = reference_node - > next_sibling ( ) ;
// 9. If node’ s parent is non-null, then remove node.
if ( node - > parent ( ) )
node - > remove ( ) ;
// 10. Let newOffset be parent’ s length if referenceNode is null, and referenceNode’ s index otherwise.
size_t new_offset = 0 ;
if ( ! reference_node )
new_offset = parent - > length ( ) ;
else
new_offset = reference_node - > index ( ) ;
// 11. Increase newOffset by node’ s length if node is a DocumentFragment node, and one otherwise.
if ( is < DocumentFragment > ( * node ) )
new_offset + = node - > length ( ) ;
else
new_offset + = 1 ;
// 12. Pre-insert node into parent before referenceNode.
2022-03-22 13:20:06 +00:00
( void ) TRY ( parent - > pre_insert ( node , reference_node ) ) ;
2022-03-21 18:58:00 +01:00
// 13. If range is collapsed, then set range’ s end to (parent, newOffset).
if ( collapsed ( ) )
2022-10-30 17:50:04 +00:00
TRY ( set_end ( * parent , new_offset ) ) ;
2022-03-21 18:58:00 +01:00
return { } ;
}
2022-03-21 23:28:46 +01:00
// https://dom.spec.whatwg.org/#dom-range-surroundcontents
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > Range : : surround_contents ( GC : : Ref < Node > new_parent )
2022-03-21 23:28:46 +01:00
{
// 1. If a non-Text node is partially contained in this, then throw an "InvalidStateError" DOMException.
Node * start_non_text_node = start_container ( ) ;
if ( is < Text > ( * start_non_text_node ) )
start_non_text_node = start_non_text_node - > parent_node ( ) ;
Node * end_non_text_node = end_container ( ) ;
if ( is < Text > ( * end_non_text_node ) )
end_non_text_node = end_non_text_node - > parent_node ( ) ;
if ( start_non_text_node ! = end_non_text_node )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Non-Text node is partially contained in range. " _string ) ;
2022-03-21 23:28:46 +01:00
// 2. If newParent is a Document, DocumentType, or DocumentFragment node, then throw an "InvalidNodeTypeError" DOMException.
if ( is < Document > ( * new_parent ) | | is < DocumentType > ( * new_parent ) | | is < DocumentFragment > ( * new_parent ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Invalid parent node type " _string ) ;
2022-03-21 23:28:46 +01:00
// 3. Let fragment be the result of extracting this.
2022-03-22 07:49:05 -04:00
auto fragment = TRY ( extract ( ) ) ;
2022-03-21 23:28:46 +01:00
// 4. If newParent has children, then replace all with null within newParent.
if ( new_parent - > has_children ( ) )
new_parent - > replace_all ( nullptr ) ;
// 5. Insert newParent into this.
2022-03-22 07:49:05 -04:00
TRY ( insert ( new_parent ) ) ;
2022-03-21 23:28:46 +01:00
// 6. Append fragment to newParent.
2022-03-22 13:20:06 +00:00
( void ) TRY ( new_parent - > append_child ( fragment ) ) ;
2022-03-21 23:28:46 +01:00
// 7. Select newParent within this.
return select ( * new_parent ) ;
}
2022-03-22 20:03:09 +01:00
// https://dom.spec.whatwg.org/#dom-range-clonecontents
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < DocumentFragment > > Range : : clone_contents ( )
2022-03-22 20:03:09 +01:00
{
return clone_the_contents ( ) ;
}
// https://dom.spec.whatwg.org/#concept-range-clone
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < DocumentFragment > > Range : : clone_the_contents ( )
2022-03-22 20:03:09 +01:00
{
// 1. Let fragment be a new DocumentFragment node whose node document is range’ s start node’ s node document.
2024-11-14 05:50:17 +13:00
auto fragment = realm ( ) . create < DOM : : DocumentFragment > ( const_cast < Document & > ( start_container ( ) - > document ( ) ) ) ;
2022-03-22 20:03:09 +01:00
// 2. If range is collapsed, then return fragment.
if ( collapsed ( ) )
2022-12-14 17:40:33 +00:00
return fragment ;
2022-03-22 20:03:09 +01:00
// 3. Let original start node, original start offset, original end node, and original end offset
// be range’ s start node, start offset, end node, and end offset, respectively.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > original_start_node = m_start_container ;
2022-03-22 20:03:09 +01:00
auto original_start_offset = m_start_offset ;
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > original_end_node = m_end_container ;
2022-03-22 20:03:09 +01:00
auto original_end_offset = m_end_offset ;
// 4. If original start node is original end node and it is a CharacterData node, then:
if ( original_start_node . ptr ( ) = = original_end_node . ptr ( ) & & is < CharacterData > ( * original_start_node ) ) {
// 1. Let clone be a clone of original start node.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( original_start_node - > clone_node ( ) ) ;
2022-03-22 20:03:09 +01:00
// 2. Set the data of clone to the result of substringing data with node original start node,
// offset original start offset, and count original end offset minus original start offset.
auto result = TRY ( static_cast < CharacterData const & > ( * original_start_node ) . substring_data ( original_start_offset , original_end_offset - original_start_offset ) ) ;
2025-01-21 09:12:05 -05:00
as < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
2022-03-22 20:03:09 +01:00
// 3. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-22 20:03:09 +01:00
// 4. Return fragment.
2022-12-14 17:40:33 +00:00
return fragment ;
2022-03-22 20:03:09 +01:00
}
// 5. Let common ancestor be original start node.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > common_ancestor = original_start_node ;
2022-03-22 20:03:09 +01:00
// 6. While common ancestor is not an inclusive ancestor of original end node, set common ancestor to its own parent.
while ( ! common_ancestor - > is_inclusive_ancestor_of ( original_end_node ) )
2022-12-14 13:43:57 +01:00
common_ancestor = * common_ancestor - > parent_node ( ) ;
2022-03-22 20:03:09 +01:00
// 7. Let first partially contained child be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > first_partially_contained_child ;
2022-03-22 20:03:09 +01:00
// 8. If original start node is not an inclusive ancestor of original end node,
// set first partially contained child to the first child of common ancestor that is partially contained in range.
if ( ! original_start_node - > is_inclusive_ancestor_of ( original_end_node ) ) {
for ( auto * child = common_ancestor - > first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
if ( partially_contains_node ( * child ) ) {
first_partially_contained_child = child ;
break ;
}
}
}
// 9. Let last partially contained child be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > last_partially_contained_child ;
2022-03-22 20:03:09 +01:00
// 10. If original end node is not an inclusive ancestor of original start node,
// set last partially contained child to the last child of common ancestor that is partially contained in range.
if ( ! original_end_node - > is_inclusive_ancestor_of ( original_start_node ) ) {
for ( auto * child = common_ancestor - > last_child ( ) ; child ; child = child - > previous_sibling ( ) ) {
if ( partially_contains_node ( * child ) ) {
last_partially_contained_child = child ;
break ;
}
}
}
// 11. Let contained children be a list of all children of common ancestor that are contained in range, in tree order.
2024-11-15 04:01:23 +13:00
Vector < GC : : Ref < Node > > contained_children ;
2023-02-25 10:44:51 -07:00
for ( Node * node = common_ancestor - > first_child ( ) ; node ; node = node - > next_sibling ( ) ) {
2022-03-22 20:03:09 +01:00
if ( contains_node ( * node ) )
contained_children . append ( * node ) ;
}
// 12. If any member of contained children is a doctype, then throw a "HierarchyRequestError" DOMException.
for ( auto const & child : contained_children ) {
if ( is < DocumentType > ( * child ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Contained child is a DocumentType " _string ) ;
2022-03-22 20:03:09 +01:00
}
// 13. If first partially contained child is a CharacterData node, then:
if ( first_partially_contained_child & & is < CharacterData > ( * first_partially_contained_child ) ) {
// 1. Let clone be a clone of original start node.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( original_start_node - > clone_node ( ) ) ;
2022-03-22 20:03:09 +01:00
// 2. Set the data of clone to the result of substringing data with node original start node, offset original start offset,
// and count original start node’ s length minus original start offset.
auto result = TRY ( static_cast < CharacterData const & > ( * original_start_node ) . substring_data ( original_start_offset , original_start_node - > length ( ) - original_start_offset ) ) ;
2025-01-21 09:12:05 -05:00
as < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
2022-03-22 20:03:09 +01:00
// 3. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-22 20:03:09 +01:00
}
// 14. Otherwise, if first partially contained child is not null:
else if ( first_partially_contained_child ) {
// 1. Let clone be a clone of first partially contained child.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( first_partially_contained_child - > clone_node ( ) ) ;
2022-03-22 20:03:09 +01:00
// 2. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-22 20:03:09 +01:00
// 3. Let subrange be a new live range whose start is (original start node, original start offset) and whose end is (first partially contained child, first partially contained child’ s length).
2023-08-13 13:05:26 +02:00
auto subrange = Range : : create ( original_start_node , original_start_offset , * first_partially_contained_child , first_partially_contained_child - > length ( ) ) ;
2022-03-22 20:03:09 +01:00
// 4. Let subfragment be the result of cloning the contents of subrange.
auto subfragment = TRY ( subrange - > clone_the_contents ( ) ) ;
// 5. Append subfragment to clone.
2022-10-30 17:50:04 +00:00
TRY ( clone - > append_child ( subfragment ) ) ;
2022-03-22 20:03:09 +01:00
}
// 15. For each contained child in contained children.
for ( auto & contained_child : contained_children ) {
// 1. Let clone be a clone of contained child with the clone children flag set.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( contained_child - > clone_node ( nullptr , true ) ) ;
2022-03-22 20:03:09 +01:00
// 2. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( move ( clone ) ) ) ;
2022-03-22 20:03:09 +01:00
}
// 16. If last partially contained child is a CharacterData node, then:
if ( last_partially_contained_child & & is < CharacterData > ( * last_partially_contained_child ) ) {
// 1. Let clone be a clone of original end node.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( original_end_node - > clone_node ( ) ) ;
2022-03-22 20:03:09 +01:00
// 2. Set the data of clone to the result of substringing data with node original end node, offset 0, and count original end offset.
auto result = TRY ( static_cast < CharacterData const & > ( * original_end_node ) . substring_data ( 0 , original_end_offset ) ) ;
2025-01-21 09:12:05 -05:00
as < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
2022-03-22 20:03:09 +01:00
// 3. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-22 20:03:09 +01:00
}
// 17. Otherwise, if last partially contained child is not null:
else if ( last_partially_contained_child ) {
// 1. Let clone be a clone of last partially contained child.
2024-06-25 11:06:41 +02:00
auto clone = TRY ( last_partially_contained_child - > clone_node ( ) ) ;
2022-03-22 20:03:09 +01:00
// 2. Append clone to fragment.
2022-10-30 17:50:04 +00:00
TRY ( fragment - > append_child ( clone ) ) ;
2022-03-22 20:03:09 +01:00
// 3. Let subrange be a new live range whose start is (last partially contained child, 0) and whose end is (original end node, original end offset).
2023-08-13 13:05:26 +02:00
auto subrange = Range : : create ( * last_partially_contained_child , 0 , original_end_node , original_end_offset ) ;
2022-03-22 20:03:09 +01:00
// 4. Let subfragment be the result of cloning the contents of subrange.
auto subfragment = TRY ( subrange - > clone_the_contents ( ) ) ;
// 5. Append subfragment to clone.
2022-10-30 17:50:04 +00:00
TRY ( clone - > append_child ( subfragment ) ) ;
2022-03-22 20:03:09 +01:00
}
// 18. Return fragment.
2022-12-14 17:40:33 +00:00
return fragment ;
2022-03-22 20:03:09 +01:00
}
2022-03-22 20:17:52 +01:00
// https://dom.spec.whatwg.org/#dom-range-deletecontents
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : delete_contents ( )
2022-03-22 20:17:52 +01:00
{
// 1. If this is collapsed, then return.
if ( collapsed ( ) )
return { } ;
// 2. Let original start node, original start offset, original end node, and original end offset be this’ s start node, start offset, end node, and end offset, respectively.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > original_start_node = m_start_container ;
2022-03-22 20:17:52 +01:00
auto original_start_offset = m_start_offset ;
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > original_end_node = m_end_container ;
2022-03-22 20:17:52 +01:00
auto original_end_offset = m_end_offset ;
// 3. If original start node is original end node and it is a CharacterData node, then replace data with node original start node, offset original start offset,
// count original end offset minus original start offset, and data the empty string, and then return.
if ( original_start_node . ptr ( ) = = original_end_node . ptr ( ) & & is < CharacterData > ( * original_start_node ) ) {
2023-09-07 21:36:05 +12:00
TRY ( static_cast < CharacterData & > ( * original_start_node ) . replace_data ( original_start_offset , original_end_offset - original_start_offset , String { } ) ) ;
2022-03-22 20:17:52 +01:00
return { } ;
}
// 4. Let nodes to remove be a list of all the nodes that are contained in this, in tree order, omitting any node whose parent is also contained in this.
2024-12-26 14:32:52 +01:00
GC : : RootVector < Node * > nodes_to_remove ( heap ( ) ) ;
2024-11-30 10:32:32 +01:00
for ( GC : : Ptr < Node > node = start_container ( ) ; node ! = end_container ( ) - > next_sibling ( ) ; node = node - > next_in_pre_order ( ) ) {
2022-03-22 20:17:52 +01:00
if ( contains_node ( * node ) & & ( ! node - > parent_node ( ) | | ! contains_node ( * node - > parent_node ( ) ) ) )
2024-11-30 10:32:32 +01:00
nodes_to_remove . append ( node ) ;
2022-03-22 20:17:52 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > new_node ;
2022-03-22 20:17:52 +01:00
size_t new_offset = 0 ;
// 5. If original start node is an inclusive ancestor of original end node, set new node to original start node and new offset to original start offset.
if ( original_start_node - > is_inclusive_ancestor_of ( original_end_node ) ) {
new_node = original_start_node ;
new_offset = original_start_offset ;
}
// 6. Otherwise
else {
// 1. Let reference node equal original start node.
auto reference_node = original_start_node ;
// 2. While reference node’ s parent is not null and is not an inclusive ancestor of original end node, set reference node to its parent.
while ( reference_node - > parent_node ( ) & & ! reference_node - > parent_node ( ) - > is_inclusive_ancestor_of ( original_end_node ) )
2022-12-14 13:43:57 +01:00
reference_node = * reference_node - > parent_node ( ) ;
2022-03-22 20:17:52 +01:00
// 3. Set new node to the parent of reference node, and new offset to one plus the index of reference node.
new_node = reference_node - > parent_node ( ) ;
new_offset = 1 + reference_node - > index ( ) ;
}
// 7. If original start node is a CharacterData node, then replace data with node original start node, offset original start offset, count original start node’ s length minus original start offset, data the empty string.
if ( is < CharacterData > ( * original_start_node ) )
2023-09-07 21:36:05 +12:00
TRY ( static_cast < CharacterData & > ( * original_start_node ) . replace_data ( original_start_offset , original_start_node - > length ( ) - original_start_offset , String { } ) ) ;
2022-03-22 20:17:52 +01:00
// 8. For each node in nodes to remove, in tree order, remove node.
for ( auto & node : nodes_to_remove )
node - > remove ( ) ;
// 9. If original end node is a CharacterData node, then replace data with node original end node, offset 0, count original end offset and data the empty string.
if ( is < CharacterData > ( * original_end_node ) )
2023-09-07 21:36:05 +12:00
TRY ( static_cast < CharacterData & > ( * original_end_node ) . replace_data ( 0 , original_end_offset , String { } ) ) ;
2022-03-22 20:17:52 +01:00
// 10. Set start and end to (new node, new offset).
2022-10-30 17:50:04 +00:00
TRY ( set_start ( * new_node , new_offset ) ) ;
TRY ( set_end ( * new_node , new_offset ) ) ;
2022-03-22 20:17:52 +01:00
return { } ;
}
2024-01-19 13:55:37 +01:00
// https://drafts.csswg.org/cssom-view/#dom-element-getclientrects
2024-09-16 19:38:14 +08:00
// https://drafts.csswg.org/cssom-view/#extensions-to-the-range-interface
2024-11-15 04:01:23 +13:00
GC : : Ref < Geometry : : DOMRectList > Range : : get_client_rects ( )
2024-01-19 13:55:37 +01:00
{
2024-09-16 19:38:14 +08:00
// 1. return an empty DOMRectList object if the range is not in the document
if ( ! start_container ( ) - > document ( ) . navigable ( ) )
return Geometry : : DOMRectList : : create ( realm ( ) , { } ) ;
2025-03-05 20:50:05 +01:00
start_container ( ) - > document ( ) . update_layout ( DOM : : UpdateLayoutReason : : RangeGetClientRects ) ;
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Geometry : : DOMRect > > rects ;
2024-09-16 19:38:14 +08:00
// FIXME: take Range collapsed into consideration
// 2. Iterate the node included in Range
auto start_node = start_container ( ) ;
2024-11-30 10:32:32 +01:00
if ( ! is < DOM : : Text > ( * start_node ) )
start_node = * start_node - > child_at_index ( m_start_offset ) ;
2024-09-16 19:38:14 +08:00
auto end_node = end_container ( ) ;
2024-11-30 10:32:32 +01:00
if ( ! is < DOM : : Text > ( * end_node ) ) {
2024-09-16 19:38:14 +08:00
// end offset shouldn't be 0
if ( m_end_offset = = 0 )
return Geometry : : DOMRectList : : create ( realm ( ) , { } ) ;
2024-11-30 10:32:32 +01:00
end_node = * end_node - > child_at_index ( m_end_offset - 1 ) ;
2024-09-16 19:38:14 +08:00
}
2024-11-30 10:32:32 +01:00
for ( GC : : Ptr < Node > node = start_node ; node & & node ! = end_node - > next_in_pre_order ( ) ; node = node - > next_in_pre_order ( ) ) {
2025-07-03 21:28:49 +02:00
auto selection_state = Painting : : Paintable : : SelectionState : : Full ;
if ( node = = start_node & & node = = end_node ) {
selection_state = Painting : : Paintable : : SelectionState : : StartAndEnd ;
} else if ( node = = start_node ) {
selection_state = Painting : : Paintable : : SelectionState : : Start ;
} else if ( node = = end_node ) {
selection_state = Painting : : Paintable : : SelectionState : : End ;
}
2024-09-16 19:38:14 +08:00
auto node_type = static_cast < NodeType > ( node - > node_type ( ) ) ;
if ( node_type = = NodeType : : ELEMENT_NODE ) {
// 1. For each element selected by the range, whose parent is not selected by the range, include the border
// areas returned by invoking getClientRects() on the element.
if ( contains_node ( * node ) & & ! contains_node ( * node - > parent ( ) ) ) {
auto const & element = static_cast < DOM : : Element const & > ( * node ) ;
2025-03-21 19:38:12 -05:00
auto const element_rects = element . get_client_rects ( ) ;
for ( auto & rect : element_rects ) {
rects . append ( MUST ( Geometry : : DOMRect : : construct_impl ( realm ( ) , static_cast < double > ( rect . x ( ) ) , static_cast < double > ( rect . y ( ) ) , static_cast < double > ( rect . width ( ) ) , static_cast < double > ( rect . height ( ) ) ) ) ) ;
2024-09-16 19:38:14 +08:00
}
}
} else if ( node_type = = NodeType : : TEXT_NODE ) {
// 2. For each Text node selected or partially selected by the range (including when the boundary-points
// are identical), include scaled DOMRect object (for the part that is selected, not the whole line box).
auto const & text = static_cast < DOM : : Text const & > ( * node ) ;
auto const * paintable = text . paintable ( ) ;
if ( paintable ) {
auto const * containing_block = paintable - > containing_block ( ) ;
if ( is < Painting : : PaintableWithLines > ( * containing_block ) ) {
auto const & paintable_lines = static_cast < Painting : : PaintableWithLines const & > ( * containing_block ) ;
auto fragments = paintable_lines . fragments ( ) ;
for ( auto frag = fragments . begin ( ) ; frag ! = fragments . end ( ) ; frag + + ) {
2025-07-03 21:28:49 +02:00
auto rect = frag - > range_rect ( selection_state , start_offset ( ) , end_offset ( ) ) ;
2024-09-16 19:38:14 +08:00
if ( rect . is_empty ( ) )
continue ;
rects . append ( Geometry : : DOMRect : : create ( realm ( ) ,
Gfx : : FloatRect ( rect ) ) ) ;
}
} else {
dbgln ( " FIXME: Failed to get client rects for node {} " , node - > debug_description ( ) ) ;
}
}
}
}
return Geometry : : DOMRectList : : create ( realm ( ) , move ( rects ) ) ;
2024-01-19 13:55:37 +01:00
}
2022-12-09 18:50:42 +00:00
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-range-getboundingclientrect
2024-11-15 04:01:23 +13:00
GC : : Ref < Geometry : : DOMRect > Range : : get_bounding_client_rect ( )
2022-12-09 18:50:42 +00:00
{
2024-09-16 19:38:14 +08:00
// 1. Let list be the result of invoking getClientRects() on element.
auto list = get_client_rects ( ) ;
// 2. If the list is empty return a DOMRect object whose x, y, width and height members are zero.
if ( list - > length ( ) = = 0 )
return Geometry : : DOMRect : : construct_impl ( realm ( ) , 0 , 0 , 0 , 0 ) . release_value_but_fixme_should_propagate_errors ( ) ;
// 3. If all rectangles in list have zero width or height, return the first rectangle in list.
auto all_rectangle_has_zero_width_or_height = true ;
for ( auto i = 0u ; i < list - > length ( ) ; + + i ) {
auto const & rect = list - > item ( i ) ;
if ( rect - > width ( ) ! = 0 & & rect - > height ( ) ! = 0 ) {
all_rectangle_has_zero_width_or_height = false ;
break ;
}
}
if ( all_rectangle_has_zero_width_or_height )
2024-11-15 04:01:23 +13:00
return GC : : Ref { * const_cast < Geometry : : DOMRect * > ( list - > item ( 0 ) ) } ;
2024-09-16 19:38:14 +08:00
// 4. Otherwise, return a DOMRect object describing the smallest rectangle that includes all of the rectangles in
// list of which the height or width is not zero.
auto const * first_rect = list - > item ( 0 ) ;
auto bounding_rect = Gfx : : Rect { first_rect - > x ( ) , first_rect - > y ( ) , first_rect - > width ( ) , first_rect - > height ( ) } ;
for ( auto i = 1u ; i < list - > length ( ) ; + + i ) {
auto const & rect = list - > item ( i ) ;
if ( rect - > width ( ) = = 0 | | rect - > height ( ) = = 0 )
continue ;
2025-01-15 13:51:39 +01:00
bounding_rect . unite ( { rect - > x ( ) , rect - > y ( ) , rect - > width ( ) , rect - > height ( ) } ) ;
2024-09-16 19:38:14 +08:00
}
return Geometry : : DOMRect : : create ( realm ( ) , bounding_rect . to_type < float > ( ) ) ;
2022-12-09 18:50:42 +00:00
}
2024-06-25 19:51:18 +01:00
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-range-createcontextualfragment
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < DocumentFragment > > Range : : create_contextual_fragment ( String const & string )
2023-03-10 14:56:44 +01:00
{
2024-11-08 11:14:33 +00:00
// FIXME: 1. Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, string, "Range createContextualFragment", and "script".
2024-06-25 19:51:18 +01:00
// 2. Let node be this's start node.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > node = * start_container ( ) ;
2023-03-10 14:56:44 +01:00
2024-06-25 19:51:18 +01:00
// 3. Let element be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Element > element = nullptr ;
2024-06-25 19:51:18 +01:00
auto node_type = static_cast < NodeType > ( node - > node_type ( ) ) ;
// 4. If node implements Element, set element to node.
if ( node_type = = NodeType : : ELEMENT_NODE )
2023-03-10 14:56:44 +01:00
element = static_cast < DOM : : Element & > ( * node ) ;
2024-06-25 19:51:18 +01:00
// 5. Otherwise, if node implements Text or Comment node, set element to node's parent element.
else if ( first_is_one_of ( node_type , NodeType : : TEXT_NODE , NodeType : : COMMENT_NODE ) )
2023-03-10 14:56:44 +01:00
element = node - > parent_element ( ) ;
2024-06-25 19:51:18 +01:00
// 6. If either element is null or all of the following are true:
2023-03-10 14:56:44 +01:00
// - element's node document is an HTML document,
2024-06-25 19:51:18 +01:00
// - element's local name is "html"; and
2023-03-10 14:56:44 +01:00
// - element's namespace is the HTML namespace;
if ( ! element | | is < HTML : : HTMLHtmlElement > ( * element ) ) {
2024-06-25 19:51:18 +01:00
// then set element to the result of creating an element given this's node document,
2024-12-18 17:01:03 +00:00
// "body", and the HTML namespace.
2023-11-04 18:42:04 +01:00
element = TRY ( DOM : : create_element ( node - > document ( ) , HTML : : TagNames : : body , Namespace : : HTML ) ) ;
2023-03-10 14:56:44 +01:00
}
2024-06-25 19:51:18 +01:00
// 7. Let fragment node be the result of invoking the fragment parsing algorithm steps with element and compliantString. FIXME: Use compliantString.
auto fragment_node = TRY ( element - > parse_fragment ( string ) ) ;
2023-03-10 14:56:44 +01:00
2024-06-25 19:51:18 +01:00
// 8. For each script of fragment node's script element descendants:
2023-03-10 14:56:44 +01:00
fragment_node - > for_each_in_subtree_of_type < HTML : : HTMLScriptElement > ( [ & ] ( HTML : : HTMLScriptElement & script_element ) {
2024-06-25 19:51:18 +01:00
// 8.1 Set scripts already started to false.
2023-03-10 14:56:44 +01:00
script_element . unmark_as_already_started ( { } ) ;
2024-06-25 19:51:18 +01:00
// 8.2 Set scripts parser document to null.
2023-03-10 14:56:44 +01:00
script_element . unmark_as_parser_inserted ( { } ) ;
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2023-03-10 14:56:44 +01:00
} ) ;
2024-06-25 19:51:18 +01:00
// 5. Return fragment node.
2023-03-10 14:56:44 +01:00
return fragment_node ;
}
2020-12-06 19:51:55 +01:00
}