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 >
2023-03-10 14:56:44 +01:00
* Copyright ( c ) 2022 - 2023 , Andreas Kling < kling @ serenityos . org >
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>
2022-03-21 16:29:19 +01:00
# include <LibWeb/DOM/Text.h>
2023-03-10 14:56:44 +01:00
# include <LibWeb/DOMParsing/InnerHTML.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-02-25 11:04:29 +01:00
# include <LibWeb/Layout/Viewport.h>
2023-03-10 14:56:44 +01:00
# include <LibWeb/Namespace.h>
2024-01-14 13:46:52 +01:00
# include <LibWeb/Painting/Paintable.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 {
2023-11-19 19:47:52 +01:00
JS_DEFINE_ALLOCATOR ( Range ) ;
2022-03-21 20:05:25 +01:00
HashTable < Range * > & Range : : live_ranges ( )
{
static HashTable < Range * > ranges ;
return ranges ;
}
2023-08-13 13:05:26 +02:00
JS : : NonnullGCPtr < Range > Range : : create ( HTML : : Window & window )
2021-02-21 23:41:54 +01:00
{
2021-09-09 13:55:31 +02:00
return Range : : create ( window . associated_document ( ) ) ;
2021-02-21 23:41:54 +01:00
}
2023-08-13 13:05:26 +02:00
JS : : NonnullGCPtr < 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 ( ) ;
2023-08-13 13:05:26 +02:00
return realm . heap ( ) . allocate < Range > ( realm , document ) ;
2021-02-21 23:41:54 +01:00
}
2024-01-04 09:14:41 +13:00
JS : : NonnullGCPtr < Range > Range : : create ( Node & start_container , WebIDL : : UnsignedLong start_offset , Node & end_container , WebIDL : : UnsignedLong end_offset )
2021-02-21 23:41:54 +01:00
{
2022-09-25 16:15:49 -06:00
auto & realm = start_container . realm ( ) ;
2023-08-13 13:05:26 +02:00
return realm . heap ( ) . allocate < Range > ( realm , start_container , start_offset , end_container , end_offset ) ;
2021-02-21 23:41:54 +01:00
}
2022-01-30 23:35:51 +00:00
2023-02-15 07:26:32 +01:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < Range > > Range : : construct_impl ( JS : : Realm & realm )
2021-02-21 23:41:54 +01:00
{
2022-09-25 16:15:49 -06:00
auto & window = verify_cast < HTML : : Window > ( realm . global_object ( ) ) ;
2022-09-21 17:45:18 +01:00
return Range : : 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-01-04 09:14:41 +13:00
Range : : Range ( Node & start_container , WebIDL : : UnsignedLong start_offset , Node & end_container , WebIDL : : UnsignedLong end_offset )
2022-01-30 23:35:51 +00:00
: AbstractRange ( start_container , start_offset , end_container , end_offset )
{
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
{
2023-08-07 08:41:28 +02:00
Base : : initialize ( realm ) ;
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( Range ) ;
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 ) ;
}
void Range : : set_associated_selection ( Badge < Selection : : Selection > , JS : : GCPtr < Selection : : Selection > selection )
{
m_associated_selection = selection ;
update_associated_selection ( ) ;
}
void Range : : update_associated_selection ( )
{
2024-06-23 20:23:59 +01:00
if ( auto * viewport = m_start_container - > document ( ) . paintable ( ) ) {
2024-03-18 07:42:38 +01:00
viewport - > recompute_selection_states ( ) ;
viewport - > set_needs_display ( ) ;
2023-01-11 19:48:53 +01:00
}
2024-02-25 04:20:37 +01:00
2024-06-23 20:23:59 +01:00
if ( ! m_associated_selection )
return ;
2024-02-25 04:20:37 +01: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 queue a task on the user interaction
// task source to fire an event named selectionchange, which does not bubble and is not cancelable, at the document
// associated with the selection.
auto document = m_associated_selection - > document ( ) ;
2024-04-16 22:04:01 +02:00
queue_global_task ( HTML : : Task : : Source : : UserInteraction , relevant_global_object ( * document ) , JS : : create_heap_function ( document - > heap ( ) , [ document ] {
2024-02-25 04:20:37 +01:00
EventInit event_init ;
event_init . bubbles = false ;
event_init . cancelable = false ;
auto event = DOM : : Event : : create ( document - > realm ( ) , HTML : : EventNames : : selectionchange , event_init ) ;
document - > dispatch_event ( event ) ;
2024-04-16 22:04:01 +02:00
} ) ) ;
2023-01-11 19:48:53 +01:00
}
2022-01-31 18:05:54 +00:00
// https://dom.spec.whatwg.org/#concept-range-root
Node & Range : : root ( )
{
// The root of a live range is the root of its start node.
return m_start_container - > root ( ) ;
}
Node const & Range : : root ( ) const
{
return m_start_container - > root ( ) ;
}
// https://dom.spec.whatwg.org/#concept-range-bp-position
2022-10-11 16:10:32 +02:00
RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point ( Node const & node_a , u32 offset_a , Node const & node_b , u32 offset_b )
2022-01-31 18:05:54 +00:00
{
// 1. Assert: nodeA and nodeB have the same root.
VERIFY ( & node_a . root ( ) = = & node_b . root ( ) ) ;
// 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.
if ( & node_a = = & node_b ) {
if ( offset_a = = offset_b )
return RelativeBoundaryPointPosition : : Equal ;
if ( offset_a < offset_b )
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.
if ( node_a . is_following ( node_b ) ) {
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point ( node_b , offset_b , node_a , offset_a ) ;
if ( relative_position = = RelativeBoundaryPointPosition : : Before )
return RelativeBoundaryPointPosition : : After ;
if ( relative_position = = RelativeBoundaryPointPosition : : After )
return RelativeBoundaryPointPosition : : Before ;
}
// 4. If nodeA is an ancestor of nodeB:
if ( node_a . is_ancestor_of ( node_b ) ) {
// 1. Let child be nodeB.
2023-02-25 10:44:51 -07:00
JS : : NonnullGCPtr < Node const > child = node_b ;
2022-01-31 18:05:54 +00:00
// 2. While child is not a child of nodeA, set child to its parent.
while ( ! node_a . is_parent_of ( child ) ) {
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.
if ( child - > index ( ) < offset_a )
return RelativeBoundaryPointPosition : : After ;
}
// 5. Return before.
return RelativeBoundaryPointPosition : : Before ;
}
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start_or_end ( 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.
if ( is < DocumentType > ( node ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _fly_string ) ;
2022-01-31 18:05:54 +00:00
// 2. If offset is greater than node’ s length, then throw an "IndexSizeError" DOMException.
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.
if ( & root ( ) ! = & node . root ( ) | | position_of_boundary_point_relative_to_other_boundary_point ( node , offset , m_end_container , m_end_offset ) = = 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.
if ( & root ( ) ! = & node . root ( ) | | position_of_boundary_point_relative_to_other_boundary_point ( node , offset , m_start_container , m_start_offset ) = = 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 { } ;
}
// https://dom.spec.whatwg.org/#concept-range-bp-set
2024-01-04 09:14:41 +13:00
WebIDL : : ExceptionOr < void > Range : : set_start ( 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 ) ;
}
2024-01-04 09:14:41 +13:00
WebIDL : : ExceptionOr < void > Range : : set_end ( 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
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start_before ( Node & node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
auto * parent = node . parent ( ) ;
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _fly_string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the start of this to boundary point (parent, node’ s index).
return set_start_or_end ( * parent , node . index ( ) , StartOrEnd : : Start ) ;
}
// https://dom.spec.whatwg.org/#dom-range-setstartafter
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : set_start_after ( Node & node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
auto * parent = node . parent ( ) ;
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _fly_string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the start of this to boundary point (parent, node’ s index plus 1).
return set_start_or_end ( * parent , node . index ( ) + 1 , StartOrEnd : : Start ) ;
}
// https://dom.spec.whatwg.org/#dom-range-setendbefore
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : set_end_before ( Node & node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
auto * parent = node . parent ( ) ;
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _fly_string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the end of this to boundary point (parent, node’ s index).
return set_start_or_end ( * parent , node . index ( ) , StartOrEnd : : End ) ;
}
// https://dom.spec.whatwg.org/#dom-range-setendafter
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : set_end_after ( Node & node )
2022-01-31 17:35:03 +00:00
{
// 1. Let parent be node’ s parent.
auto * parent = node . parent ( ) ;
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _fly_string ) ;
2022-01-31 17:35:03 +00:00
// 3. Set the end of this to boundary point (parent, node’ s index plus 1).
return set_start_or_end ( * parent , node . index ( ) + 1 , StartOrEnd : : End ) ;
}
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.
if ( & root ( ) ! = & source_range . root ( ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : WrongDocumentError : : create ( realm ( ) , " This range is not in the same tree as the source range. " _fly_string ) ;
2022-01-31 18:13:15 +00:00
2022-08-28 13:42:07 +02:00
JS : : GCPtr < Node > this_point_node ;
2022-01-31 18:13:15 +00:00
u32 this_point_offset = 0 ;
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point ( * this_point_node , this_point_offset , * other_point_node , other_point_offset ) ;
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
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : select ( Node & node )
2022-01-31 18:33:41 +00:00
{
// 1. Let parent be node’ s parent.
auto * parent = node . parent ( ) ;
// 2. If parent is null, then throw an "InvalidNodeTypeError" DOMException.
if ( ! parent )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Given node has no parent. " _fly_string ) ;
2022-01-31 18:33:41 +00:00
// 3. Let index be node’ s index.
auto index = node . index ( ) ;
// 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
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : select_node ( 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
2023-02-25 10:44:51 -07:00
WebIDL : : ExceptionOr < void > Range : : select_node_contents ( Node & node )
2022-01-31 18:51:46 +00:00
{
// 1. If node is a doctype, throw an "InvalidNodeTypeError" DOMException.
if ( is < DocumentType > ( node ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _fly_string ) ;
2022-01-31 18:51:46 +00:00
// 2. Let length be the length of node.
auto length = node . length ( ) ;
// 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 { } ;
}
2022-08-09 01:06:47 +02:00
JS : : NonnullGCPtr < Range > Range : : clone_range ( ) const
2020-12-06 19:51:55 +01:00
{
2023-08-13 13:05:26 +02:00
return heap ( ) . allocate < Range > ( shape ( ) . realm ( ) , 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-08-09 01:06:47 +02:00
JS : : NonnullGCPtr < Range > Range : : inverted ( ) const
2020-12-06 19:51:55 +01:00
{
2023-08-13 13:05:26 +02:00
return heap ( ) . allocate < Range > ( shape ( ) . realm ( ) , const_cast < Node & > ( * m_end_container ) , m_end_offset , const_cast < Node & > ( * m_start_container ) , m_start_offset ) ;
2020-12-06 19:51:55 +01:00
}
2022-08-09 01:06:47 +02:00
JS : : NonnullGCPtr < Range > Range : : normalized ( ) const
2020-12-06 19:51:55 +01:00
{
if ( m_start_container . ptr ( ) = = m_end_container . ptr ( ) ) {
if ( m_start_offset < = m_end_offset )
return clone_range ( ) ;
return inverted ( ) ;
}
if ( m_start_container - > is_before ( m_end_container ) )
return clone_range ( ) ;
return inverted ( ) ;
}
2022-02-25 20:45:03 +01:00
// https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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
bool Range : : intersects_node ( Node const & node ) const
{
// 1. If node’ s root is different from this’ s root, return false.
if ( & node . root ( ) ! = & root ( ) )
return false ;
// 2. Let parent be node’ s parent.
auto * parent = node . parent ( ) ;
// 3. If parent is null, return true.
if ( ! parent )
return true ;
// 4. Let offset be node’ s index.
auto offset = node . index ( ) ;
// 5. If (parent, offset) is before end and (parent, offset plus 1) is after start, return true
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point ( * parent , offset , m_end_container , m_end_offset ) ;
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point ( * parent , offset + 1 , m_start_container , m_start_offset ) ;
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-01-04 09:14:41 +13:00
WebIDL : : ExceptionOr < bool > Range : : is_point_in_range ( Node const & 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.
if ( & node . root ( ) ! = & root ( ) )
return false ;
// 2. If node is a doctype, then throw an "InvalidNodeTypeError" DOMException.
if ( is < DocumentType > ( node ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _fly_string ) ;
2022-02-01 18:45:34 +00:00
// 3. If offset is greater than node’ s length, then throw an "IndexSizeError" DOMException.
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.
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point ( node , offset , m_start_container , m_start_offset ) ;
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point ( node , offset , m_end_container , m_end_offset ) ;
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-01-04 09:14:41 +13:00
WebIDL : : ExceptionOr < WebIDL : : Short > Range : : compare_point ( Node const & 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.
if ( & node . root ( ) ! = & root ( ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : WrongDocumentError : : create ( realm ( ) , " Given node is not in the same document as the range. " _fly_string ) ;
2022-02-01 18:53:58 +00:00
// 2. If node is a doctype, then throw an "InvalidNodeTypeError" DOMException.
if ( is < DocumentType > ( node ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Node cannot be a DocumentType. " _fly_string ) ;
2022-02-01 18:53:58 +00:00
// 3. If offset is greater than node’ s length, then throw an "IndexSizeError" DOMException.
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.
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point ( node , offset , m_start_container , m_start_offset ) ;
if ( relative_position_to_start = = RelativeBoundaryPointPosition : : Before )
return - 1 ;
// 5. If (node, offset) is after end, return 1.
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point ( node , offset , m_end_container , m_end_offset ) ;
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.
2022-03-21 19:11:03 +01:00
for ( Node const * node = start_container ( ) ; node ! = end_container ( ) - > next_sibling ( ) ; node = node - > next_in_pre_order ( ) ) {
if ( is < Text > ( * node ) & & contains_node ( * node ) )
2022-03-21 16:29:19 +01:00
builder . append ( static_cast < Text const & > ( * node ) . data ( ) ) ;
}
// 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
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < DocumentFragment > > Range : : extract_contents ( )
2022-03-21 18:06:28 +01:00
{
return extract ( ) ;
}
// https://dom.spec.whatwg.org/#concept-range-extract
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < 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.
2023-08-13 13:05:26 +02:00
auto fragment = heap ( ) . allocate < DOM : : DocumentFragment > ( realm ( ) , 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.
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < Node > original_start_node = m_start_container ;
2022-03-21 18:06:28 +01:00
auto original_start_offset = m_start_offset ;
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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.
auto clone = original_start_node - > clone_node ( ) ;
// 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 ) ) ;
verify_cast < 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.
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
Vector < JS : : NonnullGCPtr < 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 ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Contained child is a DocumentType " _fly_string ) ;
2022-03-21 18:06:28 +01:00
}
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
auto clone = original_start_node - > clone_node ( ) ;
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 ) ) ;
verify_cast < 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.
auto clone = first_partially_contained_child - > clone_node ( ) ;
// 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.
auto clone = original_end_node - > clone_node ( ) ;
// 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 ) ) ;
verify_cast < 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.
2023-09-07 21:36:05 +12:00
TRY ( verify_cast < 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.
auto clone = last_partially_contained_child - > clone_node ( ) ;
// 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
bool Range : : contains_node ( Node const & node ) const
{
// A node node is contained in a live range range if node’ s root is range’ s root,
if ( & node . root ( ) ! = & root ( ) )
return false ;
// and (node, 0) is after range’ s start,
if ( position_of_boundary_point_relative_to_other_boundary_point ( node , 0 , m_start_container , m_start_offset ) ! = RelativeBoundaryPointPosition : : After )
return false ;
// and (node, node’ s length) is before range’ s end.
if ( position_of_boundary_point_relative_to_other_boundary_point ( node , node . length ( ) , m_end_container , m_end_offset ) ! = RelativeBoundaryPointPosition : : Before )
return false ;
return true ;
}
// https://dom.spec.whatwg.org/#partially-contained
bool Range : : partially_contains_node ( Node const & node ) const
{
// 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.
if ( node . is_inclusive_ancestor_of ( m_start_container ) & & & node ! = m_end_container . ptr ( ) )
return true ;
if ( node . is_inclusive_ancestor_of ( m_end_container ) & & & node ! = m_start_container . ptr ( ) )
return true ;
return false ;
}
2022-03-21 18:58:00 +01:00
// https://dom.spec.whatwg.org/#dom-range-insertnode
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : insert_node ( JS : : NonnullGCPtr < Node > node )
2022-03-21 18:58:00 +01:00
{
return insert ( node ) ;
}
// https://dom.spec.whatwg.org/#concept-range-insert
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : insert ( JS : : NonnullGCPtr < 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 ( ) ) {
2023-09-06 16:03:01 +12:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Range has inappropriate start node for insertion " _fly_string ) ;
2022-03-21 18:58:00 +01:00
}
// 2. Let referenceNode be null.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > Range : : surround_contents ( JS : : NonnullGCPtr < 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 )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Non-Text node is partially contained in range. " _fly_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 ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : InvalidNodeTypeError : : create ( realm ( ) , " Invalid parent node type " _fly_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
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < DocumentFragment > > Range : : clone_contents ( )
2022-03-22 20:03:09 +01:00
{
return clone_the_contents ( ) ;
}
// https://dom.spec.whatwg.org/#concept-range-clone
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < 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.
2023-08-13 13:05:26 +02:00
auto fragment = heap ( ) . allocate < DOM : : DocumentFragment > ( realm ( ) , 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.
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < Node > original_start_node = m_start_container ;
2022-03-22 20:03:09 +01:00
auto original_start_offset = m_start_offset ;
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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.
auto clone = original_start_node - > clone_node ( ) ;
// 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 ) ) ;
verify_cast < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
// 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.
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
Vector < JS : : NonnullGCPtr < 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 ) )
2023-09-06 16:03:01 +12:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Contained child is a DocumentType " _fly_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.
auto clone = original_start_node - > clone_node ( ) ;
// 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 ) ) ;
verify_cast < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
// 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.
auto clone = first_partially_contained_child - > clone_node ( ) ;
// 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.
auto clone = contained_child - > clone_node ( nullptr , true ) ;
// 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.
auto clone = original_end_node - > clone_node ( ) ;
// 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 ) ) ;
verify_cast < CharacterData > ( * clone ) . set_data ( move ( result ) ) ;
// 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.
auto clone = last_partially_contained_child - > clone_node ( ) ;
// 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.
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < Node > original_start_node = m_start_container ;
2022-03-22 20:17:52 +01:00
auto original_start_offset = m_start_offset ;
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : MarkedVector < Node * > nodes_to_remove ( heap ( ) ) ;
2024-04-20 12:50:14 +02:00
for ( Node const * 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 ( ) ) ) )
2022-08-28 13:42:07 +02:00
nodes_to_remove . append ( const_cast < Node * > ( node ) ) ;
2022-03-22 20:17:52 +01:00
}
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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
JS : : NonnullGCPtr < Geometry : : DOMRectList > Range : : get_client_rects ( ) const
{
dbgln ( " (STUBBED) Range::get_client_rects() " ) ;
return Geometry : : DOMRectList : : create ( realm ( ) , { } ) ;
}
2022-12-09 18:50:42 +00:00
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-range-getboundingclientrect
JS : : NonnullGCPtr < Geometry : : DOMRect > Range : : get_bounding_client_rect ( ) const
{
dbgln ( " (STUBBED) Range::get_bounding_client_rect() " ) ;
2023-02-19 18:21:27 +01:00
return Geometry : : DOMRect : : construct_impl ( realm ( ) , 0 , 0 , 0 , 0 ) . release_value_but_fixme_should_propagate_errors ( ) ;
2022-12-09 18:50:42 +00:00
}
2023-03-10 14:56:44 +01:00
// https://w3c.github.io/DOM-Parsing/#dom-range-createcontextualfragment
2023-08-26 16:46:33 +12:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < DocumentFragment > > Range : : create_contextual_fragment ( String const & fragment )
2023-03-10 14:56:44 +01:00
{
// 1. Let node be the context object's start node.
JS : : NonnullGCPtr < Node > node = * start_container ( ) ;
// Let element be as follows, depending on node's interface:
JS : : GCPtr < Element > element ;
switch ( static_cast < NodeType > ( node - > node_type ( ) ) ) {
case NodeType : : DOCUMENT_NODE :
case NodeType : : DOCUMENT_FRAGMENT_NODE :
element = nullptr ;
break ;
case NodeType : : ELEMENT_NODE :
element = static_cast < DOM : : Element & > ( * node ) ;
break ;
case NodeType : : TEXT_NODE :
case NodeType : : COMMENT_NODE :
element = node - > parent_element ( ) ;
break ;
case NodeType : : DOCUMENT_TYPE_NODE :
case NodeType : : PROCESSING_INSTRUCTION_NODE :
// [DOM4] prevents this case.
VERIFY_NOT_REACHED ( ) ;
default :
VERIFY_NOT_REACHED ( ) ;
}
// 2. If either element is null or the following are all true:
// - element's node document is an HTML document,
// - element's local name is "html", and
// - element's namespace is the HTML namespace;
if ( ! element | | is < HTML : : HTMLHtmlElement > ( * element ) ) {
// let element be a new Element with
// - "body" as its local name,
// - The HTML namespace as its namespace, and
// - The context object's node document as its node document.
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
}
// 3. Let fragment node be the result of invoking the fragment parsing algorithm with fragment as markup, and element as the context element.
2024-04-05 09:26:03 +02:00
auto fragment_node = TRY ( DOMParsing : : parse_fragment ( fragment , * element ) ) ;
2023-03-10 14:56:44 +01:00
// 4. Unmark all scripts in fragment node as "already started" and as "parser-inserted".
fragment_node - > for_each_in_subtree_of_type < HTML : : HTMLScriptElement > ( [ & ] ( HTML : : HTMLScriptElement & script_element ) {
script_element . unmark_as_already_started ( { } ) ;
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
} ) ;
// 5. Return the value of fragment node.
return fragment_node ;
}
2024-03-12 13:08:10 +01:00
void Range : : increase_start_offset ( Badge < Node > , WebIDL : : UnsignedLong count )
{
m_start_offset + = count ;
}
void Range : : increase_end_offset ( Badge < Node > , WebIDL : : UnsignedLong count )
{
m_end_offset + = count ;
}
void Range : : decrease_start_offset ( Badge < Node > , WebIDL : : UnsignedLong count )
{
m_start_offset - = count ;
}
void Range : : decrease_end_offset ( Badge < Node > , WebIDL : : UnsignedLong count )
{
m_end_offset - = count ;
}
2020-12-06 19:51:55 +01:00
}