2020-01-18 09:38:21 +01:00
/*
2024-10-20 10:37:44 +02:00
* Copyright ( c ) 2018 - 2024 , Andreas Kling < andreas @ ladybird . org >
2022-03-22 12:39:16 +00:00
* Copyright ( c ) 2021 - 2022 , Linus Groh < linusg @ serenityos . org >
2021-05-07 00:52:23 +01:00
* Copyright ( c ) 2021 , Luke Wilde < lukew @ serenityos . org >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2023-02-05 11:21:59 -06:00
# include <AK/HashTable.h>
2019-09-29 16:22:15 +02:00
# include <AK/StringBuilder.h>
2024-11-15 04:01:23 +13:00
# include <LibGC/DeferGC.h>
2021-06-27 21:48:34 +02:00
# include <LibJS/Runtime/FunctionObject.h>
2023-02-05 11:21:59 -06:00
# include <LibRegex/Regex.h>
2024-10-05 15:33:34 +13:00
# include <LibURL/Origin.h>
2022-07-11 16:39:14 +01:00
# include <LibWeb/Bindings/MainThreadVM.h>
2022-08-28 13:42:07 +02:00
# include <LibWeb/Bindings/NodePrototype.h>
2023-05-08 06:49:38 +02:00
# include <LibWeb/DOM/Attr.h>
2024-02-18 14:27:25 +00:00
# include <LibWeb/DOM/CDATASection.h>
2021-04-06 19:34:49 +01:00
# include <LibWeb/DOM/Comment.h>
# include <LibWeb/DOM/DocumentType.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Element.h>
2021-07-05 05:28:29 +01:00
# include <LibWeb/DOM/ElementFactory.h>
2020-03-21 18:17:18 +01:00
# include <LibWeb/DOM/Event.h>
2020-09-06 14:28:41 +02:00
# include <LibWeb/DOM/EventDispatcher.h>
2022-02-16 20:43:24 +01:00
# include <LibWeb/DOM/IDLEventListener.h>
2021-10-02 20:37:45 +01:00
# include <LibWeb/DOM/LiveNodeList.h>
2022-07-11 16:39:14 +01:00
# include <LibWeb/DOM/MutationType.h>
2024-07-14 22:50:38 +01:00
# include <LibWeb/DOM/NamedNodeMap.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Node.h>
2022-08-28 13:42:07 +02:00
# include <LibWeb/DOM/NodeIterator.h>
2021-04-06 19:34:49 +01:00
# include <LibWeb/DOM/ProcessingInstruction.h>
2022-08-28 13:42:07 +02:00
# include <LibWeb/DOM/Range.h>
2020-11-21 18:32:39 +00:00
# include <LibWeb/DOM/ShadowRoot.h>
2022-07-11 16:39:14 +01:00
# include <LibWeb/DOM/StaticNodeList.h>
2024-11-19 15:16:14 +01:00
# include <LibWeb/DOM/XMLDocument.h>
2023-03-29 23:46:18 +01:00
# include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
2020-08-25 22:18:32 +04:30
# include <LibWeb/HTML/HTMLAnchorElement.h>
2024-11-19 15:16:14 +01:00
# include <LibWeb/HTML/HTMLDocument.h>
2024-11-19 21:03:48 +09:00
# include <LibWeb/HTML/HTMLFieldSetElement.h>
2024-11-07 20:33:43 +09:00
# include <LibWeb/HTML/HTMLImageElement.h>
2024-10-25 10:47:06 +09:00
# include <LibWeb/HTML/HTMLInputElement.h>
2024-11-19 21:03:48 +09:00
# include <LibWeb/HTML/HTMLLegendElement.h>
2024-10-25 10:47:06 +09:00
# include <LibWeb/HTML/HTMLSelectElement.h>
2023-09-05 15:13:37 -04:00
# include <LibWeb/HTML/HTMLSlotElement.h>
2022-12-11 10:56:37 -06:00
# include <LibWeb/HTML/HTMLStyleElement.h>
2024-11-19 21:03:48 +09:00
# include <LibWeb/HTML/HTMLTableElement.h>
2023-04-23 00:21:59 +03:00
# include <LibWeb/HTML/Navigable.h>
2022-12-12 12:20:02 +01:00
# include <LibWeb/HTML/NavigableContainer.h>
2021-09-25 23:15:48 +02:00
# include <LibWeb/HTML/Parser/HTMLParser.h>
2023-02-05 11:21:59 -06:00
# include <LibWeb/Infra/CharacterTypes.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/Node.h>
# include <LibWeb/Layout/TextNode.h>
2023-02-25 11:04:29 +01:00
# include <LibWeb/Layout/Viewport.h>
2024-07-14 22:50:38 +01:00
# include <LibWeb/Namespace.h>
2023-08-19 12:00:42 +02:00
# include <LibWeb/Painting/Paintable.h>
2024-01-13 12:33:06 +01:00
# include <LibWeb/Painting/PaintableBox.h>
2024-11-21 05:03:28 +09:00
# include <LibWeb/SVG/SVGTitleElement.h>
# include <LibWeb/XLink/AttributeNames.h>
2019-06-15 18:55:47 +02:00
2020-07-26 19:37:56 +02:00
namespace Web : : DOM {
2020-03-07 10:27:02 +01:00
2024-10-20 10:37:44 +02:00
static UniqueNodeID s_next_unique_id ;
static HashMap < UniqueNodeID , Node * > s_node_directory ;
2021-08-30 15:52:08 +01:00
2024-10-20 10:37:44 +02:00
static UniqueNodeID allocate_unique_id ( Node * node )
2021-08-30 15:52:08 +01:00
{
2024-10-20 10:37:44 +02:00
auto id = s_next_unique_id ;
+ + s_next_unique_id ;
2021-08-30 15:52:08 +01:00
s_node_directory . set ( id , node ) ;
return id ;
}
2024-10-20 10:37:44 +02:00
static void deallocate_unique_id ( UniqueNodeID node_id )
2021-08-30 15:52:08 +01:00
{
if ( ! s_node_directory . remove ( node_id ) )
VERIFY_NOT_REACHED ( ) ;
}
2024-10-20 10:37:44 +02:00
Node * Node : : from_unique_id ( UniqueNodeID unique_id )
2021-08-30 15:52:08 +01:00
{
2023-11-02 14:30:00 +01:00
return s_node_directory . get ( unique_id ) . value_or ( nullptr ) ;
2021-08-30 15:52:08 +01:00
}
2022-08-28 13:42:07 +02:00
Node : : Node ( JS : : Realm & realm , Document & document , NodeType type )
: EventTarget ( realm )
2020-09-20 19:22:44 +02:00
, m_document ( & document )
2019-09-29 11:43:07 +02:00
, m_type ( type )
2023-11-02 14:30:00 +01:00
, m_unique_id ( allocate_unique_id ( this ) )
2019-06-15 18:55:47 +02:00
{
2022-08-28 13:42:07 +02:00
}
Node : : Node ( Document & document , NodeType type )
: Node ( document . realm ( ) , document , type )
{
2019-06-15 18:55:47 +02:00
}
2022-10-20 19:30:29 +02:00
Node : : ~ Node ( ) = default ;
void Node : : finalize ( )
2019-06-15 18:55:47 +02:00
{
2022-10-20 19:45:17 +02:00
Base : : finalize ( ) ;
2023-11-02 14:30:00 +01:00
deallocate_unique_id ( m_unique_id ) ;
2019-06-15 18:55:47 +02:00
}
2019-09-25 12:17:29 +03:00
2022-08-28 13:42:07 +02:00
void Node : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2023-11-19 16:18:00 +13:00
visitor . visit ( m_document ) ;
visitor . visit ( m_parent ) ;
visitor . visit ( m_first_child ) ;
visitor . visit ( m_last_child ) ;
visitor . visit ( m_next_sibling ) ;
visitor . visit ( m_previous_sibling ) ;
2022-09-21 13:49:31 +02:00
visitor . visit ( m_child_nodes ) ;
2022-09-01 17:59:48 +02:00
2022-10-17 14:41:50 +02:00
visitor . visit ( m_layout_node ) ;
2023-08-19 12:00:42 +02:00
visitor . visit ( m_paintable ) ;
2022-10-17 14:41:50 +02:00
2023-11-18 11:22:51 +01:00
if ( m_registered_observer_list ) {
2024-04-15 13:58:21 +02:00
visitor . visit ( * m_registered_observer_list ) ;
2023-11-18 11:22:51 +01:00
}
2022-08-28 13:42:07 +02:00
}
2022-04-12 13:20:13 -03:00
// https://dom.spec.whatwg.org/#dom-node-baseuri
2023-09-17 10:51:43 +12:00
String Node : : base_uri ( ) const
2022-04-12 13:20:13 -03:00
{
2022-06-19 16:02:48 +01:00
// Return this’ s node document’ s document base URL, serialized.
2024-12-03 22:31:33 +13:00
return document ( ) . base_url ( ) . to_string ( ) ;
2022-04-12 13:20:13 -03:00
}
2020-07-28 18:20:36 +02:00
const HTML : : HTMLAnchorElement * Node : : enclosing_link_element ( ) const
2019-09-29 11:59:38 +02:00
{
2019-10-19 21:21:29 +02:00
for ( auto * node = this ; node ; node = node - > parent ( ) ) {
2022-03-02 15:15:39 +01:00
if ( ! is < HTML : : HTMLAnchorElement > ( * node ) )
continue ;
auto const & anchor_element = static_cast < HTML : : HTMLAnchorElement const & > ( * node ) ;
if ( anchor_element . has_attribute ( HTML : : AttributeNames : : href ) )
return & anchor_element ;
2019-10-19 21:21:29 +02:00
}
return nullptr ;
2019-09-29 11:59:38 +02:00
}
2019-09-29 12:24:36 +02:00
2020-07-28 18:20:36 +02:00
const HTML : : HTMLElement * Node : : enclosing_html_element ( ) const
2019-09-29 12:24:36 +02:00
{
2020-07-28 18:20:36 +02:00
return first_ancestor_of_type < HTML : : HTMLElement > ( ) ;
2019-09-29 12:24:36 +02:00
}
2019-09-29 16:22:15 +02:00
2023-10-08 11:42:00 +13:00
const HTML : : HTMLElement * Node : : enclosing_html_element_with_attribute ( FlyString const & attribute ) const
2021-03-30 12:06:06 -04:00
{
for ( auto * node = this ; node ; node = node - > parent ( ) ) {
2021-06-24 19:53:42 +02:00
if ( is < HTML : : HTMLElement > ( * node ) & & verify_cast < HTML : : HTMLElement > ( * node ) . has_attribute ( attribute ) )
return verify_cast < HTML : : HTMLElement > ( node ) ;
2021-03-30 12:06:06 -04:00
}
return nullptr ;
}
2024-11-09 23:45:09 +09:00
Optional < String > Node : : alternative_text ( ) const
{
return { } ;
}
2021-09-06 00:21:59 +01:00
// https://dom.spec.whatwg.org/#concept-descendant-text-content
2023-09-17 10:51:43 +12:00
String Node : : descendant_text_content ( ) const
2019-09-29 16:22:15 +02:00
{
StringBuilder builder ;
2021-09-06 00:21:59 +01:00
for_each_in_subtree_of_type < Text > ( [ & ] ( auto & text_node ) {
builder . append ( text_node . data ( ) ) ;
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2021-09-06 00:21:59 +01:00
} ) ;
2024-01-23 22:33:12 +01:00
return builder . to_string_without_validation ( ) ;
2019-09-29 16:22:15 +02:00
}
2019-10-06 19:54:50 +02:00
2021-09-06 00:21:59 +01:00
// https://dom.spec.whatwg.org/#dom-node-textcontent
2023-09-17 10:51:43 +12:00
Optional < String > Node : : text_content ( ) const
2020-08-17 12:36:00 -04:00
{
2022-04-20 00:57:06 +02:00
// The textContent getter steps are to return the following, switching on the interface this implements:
2022-09-18 01:07:05 +02:00
2022-04-20 00:57:06 +02:00
// If DocumentFragment or Element, return the descendant text content of this.
2021-09-06 00:21:59 +01:00
if ( is < DocumentFragment > ( this ) | | is < Element > ( this ) )
return descendant_text_content ( ) ;
2022-09-18 01:07:05 +02:00
// If CharacterData, return this’ s data.
if ( is < CharacterData > ( this ) )
2023-09-07 21:36:05 +12:00
return static_cast < CharacterData const & > ( * this ) . data ( ) ;
2022-09-18 01:07:05 +02:00
// If Attr node, return this's value.
if ( is < Attr > ( * this ) )
2023-09-10 16:06:58 +12:00
return static_cast < Attr const & > ( * this ) . value ( ) ;
2021-09-06 00:21:59 +01:00
2022-04-20 00:57:06 +02:00
// Otherwise, return null
2021-09-06 00:21:59 +01:00
return { } ;
}
// https://dom.spec.whatwg.org/#ref-for-dom-node-textcontent%E2%91%A0
2023-09-17 10:51:43 +12:00
void Node : : set_text_content ( Optional < String > const & maybe_content )
2021-09-06 00:21:59 +01:00
{
2022-04-20 00:57:06 +02:00
// The textContent setter steps are to, if the given value is null, act as if it was the empty string instead,
// and then do as described below, switching on the interface this implements:
2023-12-03 08:24:04 +13:00
auto content = maybe_content . value_or ( String { } ) ;
2022-04-20 00:57:06 +02:00
// If DocumentFragment or Element, string replace all with the given value within this.
2021-09-06 00:21:59 +01:00
if ( is < DocumentFragment > ( this ) | | is < Element > ( this ) ) {
string_replace_all ( content ) ;
2022-09-18 01:07:05 +02:00
}
// If CharacterData, replace data with node this, offset 0, count this’ s length, and data the given value.
else if ( is < CharacterData > ( this ) ) {
2021-09-06 00:21:59 +01:00
auto * character_data_node = verify_cast < CharacterData > ( this ) ;
2023-12-03 08:24:04 +13:00
character_data_node - > set_data ( content ) ;
2021-09-06 00:21:59 +01:00
2022-04-20 00:57:06 +02:00
// FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant.
// Do note that this will make this function able to throw an exception.
2022-09-18 01:07:05 +02:00
}
// If Attr, set an existing attribute value with this and the given value.
if ( is < Attr > ( * this ) ) {
2023-12-03 08:24:04 +13:00
static_cast < Attr & > ( * this ) . set_value ( content ) ;
2020-08-17 12:36:00 -04:00
}
2022-04-20 00:57:06 +02:00
// Otherwise, do nothing.
2024-04-14 10:24:44 +02:00
if ( is_connected ( ) ) {
2024-10-27 13:56:28 +01:00
invalidate_style ( StyleInvalidationReason : : NodeSetTextContent ) ;
2024-09-19 07:40:45 +02:00
document ( ) . invalidate_layout_tree ( ) ;
2024-04-14 10:24:44 +02:00
}
2024-03-19 15:39:45 +01:00
document ( ) . bump_dom_tree_version ( ) ;
2020-08-17 12:36:00 -04:00
}
2024-07-11 21:52:03 +01:00
// https://dom.spec.whatwg.org/#dom-node-normalize
WebIDL : : ExceptionOr < void > Node : : normalize ( )
{
auto contiguous_exclusive_text_nodes_excluding_self = [ ] ( Node & node ) {
// https://dom.spec.whatwg.org/#contiguous-exclusive-text-nodes
// The contiguous exclusive Text nodes of a node node are node, node’ s previous sibling exclusive Text node, if any,
// and its contiguous exclusive Text nodes, and node’ s next sibling exclusive Text node, if any,
// and its contiguous exclusive Text nodes, avoiding any duplicates.
// NOTE: The callers of this method require node itself to be excluded.
Vector < Text * > nodes ;
auto * current_node = node . previous_sibling ( ) ;
2024-11-20 11:31:59 +01:00
while ( current_node & & current_node - > is_exclusive_text ( ) ) {
2024-07-11 21:52:03 +01:00
nodes . append ( static_cast < Text * > ( current_node ) ) ;
current_node = current_node - > previous_sibling ( ) ;
}
// Reverse the order of the nodes so that they are in tree order.
nodes . reverse ( ) ;
current_node = node . next_sibling ( ) ;
2024-11-20 11:31:59 +01:00
while ( current_node & & current_node - > is_exclusive_text ( ) ) {
2024-07-11 21:52:03 +01:00
nodes . append ( static_cast < Text * > ( current_node ) ) ;
current_node = current_node - > next_sibling ( ) ;
}
return nodes ;
} ;
// The normalize() method steps are to run these steps for each descendant exclusive Text node node of this
Vector < Text & > descendant_exclusive_text_nodes ;
for_each_in_inclusive_subtree_of_type < Text > ( [ & ] ( Text const & node ) {
if ( ! node . is_cdata_section ( ) )
descendant_exclusive_text_nodes . append ( const_cast < Text & > ( node ) ) ;
return TraversalDecision : : Continue ;
} ) ;
for ( auto & node : descendant_exclusive_text_nodes ) {
// 1. Let length be node’ s length.
auto & character_data = static_cast < CharacterData & > ( node ) ;
auto length = character_data . length_in_utf16_code_units ( ) ;
// 2. If length is zero, then remove node and continue with the next exclusive Text node, if any.
if ( length = = 0 ) {
if ( node . parent ( ) )
node . remove ( ) ;
continue ;
}
// 3. Let data be the concatenation of the data of node’ s contiguous exclusive Text nodes (excluding itself), in tree order.
StringBuilder data ;
for ( auto const & text_node : contiguous_exclusive_text_nodes_excluding_self ( node ) )
data . append ( text_node - > data ( ) ) ;
// 4. Replace data with node node, offset length, count 0, and data data.
TRY ( character_data . replace_data ( length , 0 , MUST ( data . to_string ( ) ) ) ) ;
// 5. Let currentNode be node’ s next sibling.
auto * current_node = node . next_sibling ( ) ;
// 6. While currentNode is an exclusive Text node:
2024-11-20 11:31:59 +01:00
while ( current_node & & current_node - > is_exclusive_text ( ) ) {
2024-07-11 21:52:03 +01:00
// 1. For each live range whose start node is currentNode, add length to its start offset and set its start node to node.
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > start_container ( ) = = current_node )
TRY ( range - > set_start ( node , range - > start_offset ( ) + length ) ) ;
}
// 2. For each live range whose end node is currentNode, add length to its end offset and set its end node to node.
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > end_container ( ) = = current_node )
TRY ( range - > set_end ( node , range - > end_offset ( ) + length ) ) ;
}
// 3. For each live range whose start node is currentNode’ s parent and start offset is currentNode’ s index, set its start node to node and its start offset to length.
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > start_container ( ) = = current_node - > parent ( ) & & range - > start_offset ( ) = = current_node - > index ( ) )
TRY ( range - > set_start ( node , length ) ) ;
}
// 4. For each live range whose end node is currentNode’ s parent and end offset is currentNode’ s index, set its end node to node and its end offset to length.
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > end_container ( ) = = current_node - > parent ( ) & & range - > end_offset ( ) = = current_node - > index ( ) )
TRY ( range - > set_end ( node , length ) ) ;
}
// 5. Add currentNode’ s length to length.
length + = static_cast < Text & > ( * current_node ) . length ( ) ;
// 6. Set currentNode to its next sibling.
current_node = current_node - > next_sibling ( ) ;
}
// 7. Remove node’ s contiguous exclusive Text nodes (excluding itself), in tree order.
for ( auto const & text_node : contiguous_exclusive_text_nodes_excluding_self ( node ) )
text_node - > remove ( ) ;
}
return { } ;
}
2022-02-18 22:11:43 +00:00
// https://dom.spec.whatwg.org/#dom-node-nodevalue
2023-09-17 10:51:43 +12:00
Optional < String > Node : : node_value ( ) const
2022-02-18 22:11:43 +00:00
{
2022-04-20 00:57:06 +02:00
// The nodeValue getter steps are to return the following, switching on the interface this implements:
// If Attr, return this’ s value.
2022-09-18 01:03:58 +02:00
if ( is < Attr > ( this ) ) {
2023-09-10 16:06:58 +12:00
return verify_cast < Attr > ( this ) - > value ( ) ;
2022-02-18 22:11:43 +00:00
}
2022-04-20 00:57:06 +02:00
// If CharacterData, return this’ s data.
2022-02-18 22:11:43 +00:00
if ( is < CharacterData > ( this ) ) {
2023-09-07 21:36:05 +12:00
return verify_cast < CharacterData > ( this ) - > data ( ) ;
2022-02-18 22:11:43 +00:00
}
2022-04-20 00:57:06 +02:00
// Otherwise, return null.
2022-02-18 22:11:43 +00:00
return { } ;
}
// https://dom.spec.whatwg.org/#ref-for-dom-node-nodevalue%E2%91%A0
2023-09-17 10:51:43 +12:00
void Node : : set_node_value ( Optional < String > const & maybe_value )
2022-02-18 22:11:43 +00:00
{
2022-04-20 00:57:06 +02:00
// The nodeValue setter steps are to, if the given value is null, act as if it was the empty string instead,
// and then do as described below, switching on the interface this implements:
2023-09-17 10:51:43 +12:00
auto value = maybe_value . value_or ( String { } ) ;
2022-02-18 22:11:43 +00:00
2022-04-20 00:57:06 +02:00
// If Attr, set an existing attribute value with this and the given value.
2022-09-18 01:03:58 +02:00
if ( is < Attr > ( this ) ) {
2023-09-10 16:06:58 +12:00
verify_cast < Attr > ( this ) - > set_value ( move ( value ) ) ;
2022-02-18 22:11:43 +00:00
} else if ( is < CharacterData > ( this ) ) {
2022-04-20 00:57:06 +02:00
// If CharacterData, replace data with node this, offset 0, count this’ s length, and data the given value.
2023-09-07 21:36:05 +12:00
verify_cast < CharacterData > ( this ) - > set_data ( value ) ;
2022-02-18 22:11:43 +00:00
}
2022-04-20 00:57:06 +02:00
// Otherwise, do nothing.
2022-02-18 22:11:43 +00:00
}
2023-04-23 00:21:59 +03:00
// https://html.spec.whatwg.org/multipage/document-sequences.html#node-navigable
2024-11-15 04:01:23 +13:00
GC : : Ptr < HTML : : Navigable > Node : : navigable ( ) const
2023-04-23 00:21:59 +03:00
{
2024-08-02 20:41:16 +02:00
auto & document = const_cast < Document & > ( this - > document ( ) ) ;
if ( auto cached_navigable = document . cached_navigable ( ) ) {
if ( cached_navigable - > active_document ( ) = = & document )
return cached_navigable ;
}
2023-04-23 00:21:59 +03:00
// To get the node navigable of a node node, return the navigable whose active document is node's node document,
// or null if there is no such navigable.
2024-08-02 20:41:16 +02:00
auto navigable = HTML : : Navigable : : navigable_with_active_document ( document ) ;
document . set_cached_navigable ( navigable ) ;
return navigable ;
2023-04-23 00:21:59 +03:00
}
2024-09-04 10:01:08 +02:00
[[maybe_unused]] static StringView to_string ( StyleInvalidationReason reason )
2019-10-14 18:32:02 +02:00
{
2024-09-04 10:01:08 +02:00
# define __ENUMERATE_STYLE_INVALIDATION_REASON(reason) \
case StyleInvalidationReason : : reason : \
return # reason # # sv ;
switch ( reason ) {
ENUMERATE_STYLE_INVALIDATION_REASONS ( __ENUMERATE_STYLE_INVALIDATION_REASON )
default :
VERIFY_NOT_REACHED ( ) ;
}
}
void Node : : invalidate_style ( StyleInvalidationReason reason )
{
2024-09-22 13:25:21 +02:00
if ( is_character_data ( ) )
return ;
2024-10-27 13:56:28 +01:00
// FIXME: This is very not optimal! We should figure out a smaller set of elements to invalidate,
// but right now the :has() selector means we have to invalidate everything.
if ( ! is_document ( ) & & document ( ) . style_computer ( ) . has_has_selectors ( ) ) {
document ( ) . invalidate_style ( reason ) ;
return ;
}
2024-09-04 10:01:08 +02:00
if ( ! needs_style_update ( ) & & ! document ( ) . needs_full_style_update ( ) ) {
dbgln_if ( STYLE_INVALIDATION_DEBUG , " Invalidate style ({}): {} " , to_string ( reason ) , debug_description ( ) ) ;
}
2022-03-19 18:10:59 +01:00
if ( is_document ( ) ) {
auto & document = static_cast < DOM : : Document & > ( * this ) ;
document . set_needs_full_style_update ( true ) ;
document . schedule_style_update ( ) ;
return ;
}
2024-01-13 11:38:30 +01:00
// If the document is already marked for a full style update, there's no need to do anything here.
if ( document ( ) . needs_full_style_update ( ) ) {
return ;
}
2024-09-19 13:18:34 +02:00
// When invalidating style for a node, we actually invalidate:
// - the node itself
// - all of its descendants
// - all of its preceding siblings and their descendants (only on DOM insert/remove)
// - all of its subsequent siblings and their descendants
// FIXME: This is a lot of invalidation and we should implement more sophisticated invalidation to do less work!
auto invalidate_entire_subtree = [ & ] ( Node & subtree_root ) {
subtree_root . for_each_in_inclusive_subtree ( [ & ] ( Node & node ) {
node . m_needs_style_update = true ;
if ( node . has_children ( ) )
node . m_child_needs_style_update = true ;
if ( auto shadow_root = node . is_element ( ) ? static_cast < DOM : : Element & > ( node ) . shadow_root ( ) : nullptr ) {
node . m_child_needs_style_update = true ;
shadow_root - > m_needs_style_update = true ;
if ( shadow_root - > has_children ( ) )
shadow_root - > m_child_needs_style_update = true ;
}
return TraversalDecision : : Continue ;
} ) ;
} ;
invalidate_entire_subtree ( * this ) ;
if ( reason = = StyleInvalidationReason : : NodeInsertBefore | | reason = = StyleInvalidationReason : : NodeRemove ) {
for ( auto * sibling = previous_sibling ( ) ; sibling ; sibling = sibling - > previous_sibling ( ) ) {
if ( sibling - > is_element ( ) )
invalidate_entire_subtree ( * sibling ) ;
2022-03-14 21:40:34 +01:00
}
2024-09-19 13:18:34 +02:00
}
for ( auto * sibling = next_sibling ( ) ; sibling ; sibling = sibling - > next_sibling ( ) ) {
if ( sibling - > is_element ( ) )
invalidate_entire_subtree ( * sibling ) ;
}
2022-03-15 21:01:26 +01:00
for ( auto * ancestor = parent_or_shadow_host ( ) ; ancestor ; ancestor = ancestor - > parent_or_shadow_host ( ) )
2022-03-14 21:40:34 +01:00
ancestor - > m_child_needs_style_update = true ;
2019-10-19 18:57:02 +02:00
document ( ) . schedule_style_update ( ) ;
2019-10-14 18:32:02 +02:00
}
2019-10-19 21:21:29 +02:00
2023-12-03 08:24:04 +13:00
String Node : : child_text_content ( ) const
2020-05-24 21:59:24 +02:00
{
if ( ! is < ParentNode > ( * this ) )
2023-12-03 08:24:04 +13:00
return String { } ;
2020-05-24 21:59:24 +02:00
StringBuilder builder ;
2021-06-24 19:53:42 +02:00
verify_cast < ParentNode > ( * this ) . for_each_child ( [ & ] ( auto & child ) {
2023-09-17 10:51:43 +12:00
if ( is < Text > ( child ) ) {
auto maybe_content = verify_cast < Text > ( child ) . text_content ( ) ;
if ( maybe_content . has_value ( ) )
builder . append ( maybe_content . value ( ) ) ;
}
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2020-05-24 21:59:24 +02:00
} ) ;
2023-12-03 08:24:04 +13:00
return MUST ( builder . to_string ( ) ) ;
2020-05-24 21:59:24 +02:00
}
2021-09-02 19:27:42 +01:00
// https://dom.spec.whatwg.org/#concept-tree-root
Node & Node : : root ( )
2020-05-24 21:59:24 +02:00
{
2022-04-20 00:57:06 +02:00
// The root of an object is itself, if its parent is null, or else it is the root of its parent.
// The root of a tree is any object participating in that tree whose parent is null.
2020-11-21 18:32:39 +00:00
Node * root = this ;
2020-05-24 21:59:24 +02:00
while ( root - > parent ( ) )
root = root - > parent ( ) ;
2021-09-02 19:27:42 +01:00
return * root ;
2020-05-24 21:59:24 +02:00
}
2021-09-02 19:27:42 +01:00
// https://dom.spec.whatwg.org/#concept-shadow-including-root
Node & Node : : shadow_including_root ( )
2020-11-21 18:32:39 +00:00
{
2022-04-20 00:57:06 +02:00
// The shadow-including root of an object is its root’ s host’ s shadow-including root,
// if the object’ s root is a shadow root; otherwise its root.
2021-09-02 19:27:42 +01:00
auto & node_root = root ( ) ;
2024-03-23 15:46:08 +00:00
if ( is < ShadowRoot > ( node_root ) ) {
if ( auto * host = static_cast < ShadowRoot & > ( node_root ) . host ( ) ; host )
return host - > shadow_including_root ( ) ;
}
2020-11-21 18:32:39 +00:00
return node_root ;
}
2021-09-02 19:27:42 +01:00
// https://dom.spec.whatwg.org/#connected
2020-05-24 21:59:24 +02:00
bool Node : : is_connected ( ) const
{
2022-04-20 00:57:06 +02:00
// An element is connected if its shadow-including root is a document.
2021-09-02 19:27:42 +01:00
return shadow_including_root ( ) . is_document ( ) ;
2020-05-24 21:59:24 +02:00
}
2024-04-26 09:29:27 +02:00
// https://html.spec.whatwg.org/multipage/infrastructure.html#browsing-context-connected
bool Node : : is_browsing_context_connected ( ) const
{
// A node is browsing-context connected when it is connected and its shadow-including root's browsing context is non-null.
return is_connected ( ) & & shadow_including_root ( ) . document ( ) . browsing_context ( ) ;
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > Node : : ensure_pre_insertion_validity ( GC : : Ref < Node > node , GC : : Ptr < Node > child ) const
2020-06-21 01:00:58 +02:00
{
2022-04-20 00:57:06 +02:00
// 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
2021-04-06 19:34:49 +01:00
if ( ! is < Document > ( this ) & & ! is < DocumentFragment > ( this ) & & ! is < Element > ( this ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Can only insert into a document, document fragment or element " _string ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException.
2021-04-06 19:34:49 +01:00
if ( node - > is_host_including_inclusive_ancestor_of ( * this ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " New node is an ancestor of this node " _string ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 3. If child is non-null and its parent is not parent, then throw a "NotFoundError" DOMException.
2021-04-06 19:34:49 +01:00
if ( child & & child - > parent ( ) ! = this )
2024-10-12 20:56:21 +02:00
return WebIDL : : NotFoundError : : create ( realm ( ) , " This node is not the parent of the given child " _string ) ;
2021-04-06 19:34:49 +01:00
// FIXME: All the following "Invalid node type for insertion" messages could be more descriptive.
2022-04-20 00:57:06 +02:00
// 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
2024-02-18 14:27:25 +00:00
if ( ! is < DocumentFragment > ( * node ) & & ! is < DocumentType > ( * node ) & & ! is < Element > ( * node ) & & ! is < Text > ( * node ) & & ! is < Comment > ( * node ) & & ! is < ProcessingInstruction > ( * node ) & & ! is < CDATASection > ( * node ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
2021-04-06 19:34:49 +01:00
if ( ( is < Text > ( * node ) & & is < Document > ( this ) ) | | ( is < DocumentType > ( * node ) & & ! is < Document > ( this ) ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 6. If parent is a document, and any of the statements below, switched on the interface node implements, are true, then throw a "HierarchyRequestError" DOMException.
2021-04-06 19:34:49 +01:00
if ( is < Document > ( this ) ) {
2022-04-20 00:57:06 +02:00
// DocumentFragment
2021-04-06 19:34:49 +01:00
if ( is < DocumentFragment > ( * node ) ) {
2022-04-20 00:57:06 +02:00
// If node has more than one element child or has a Text node child.
// Otherwise, if node has one element child and either parent has an element child, child is a doctype, or child is non-null and a doctype is following child.
2021-06-24 19:53:42 +02:00
auto node_element_child_count = verify_cast < DocumentFragment > ( * node ) . child_element_count ( ) ;
2021-04-06 19:34:49 +01:00
if ( ( node_element_child_count > 1 | | node - > has_child_of_type < Text > ( ) )
2021-09-07 08:15:34 +01:00
| | ( node_element_child_count = = 1 & & ( has_child_of_type < Element > ( ) | | is < DocumentType > ( child . ptr ( ) ) | | ( child & & child - > has_following_node_of_type_in_tree_order < DocumentType > ( ) ) ) ) ) {
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-04-06 19:34:49 +01:00
}
} else if ( is < Element > ( * node ) ) {
2022-04-20 00:57:06 +02:00
// Element
// If parent has an element child, child is a doctype, or child is non-null and a doctype is following child.
2021-09-07 08:15:34 +01:00
if ( has_child_of_type < Element > ( ) | | is < DocumentType > ( child . ptr ( ) ) | | ( child & & child - > has_following_node_of_type_in_tree_order < DocumentType > ( ) ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-04-06 19:34:49 +01:00
} else if ( is < DocumentType > ( * node ) ) {
2022-04-20 00:57:06 +02:00
// DocumentType
// parent has a doctype child, child is non-null and an element is preceding child, or child is null and parent has an element child.
2021-09-07 08:15:34 +01:00
if ( has_child_of_type < DocumentType > ( ) | | ( child & & child - > has_preceding_node_of_type_in_tree_order < Element > ( ) ) | | ( ! child & & has_child_of_type < Element > ( ) ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-04-06 19:34:49 +01:00
}
}
return { } ;
2020-06-21 01:00:58 +02:00
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-insert
2024-11-15 04:01:23 +13:00
void Node : : insert_before ( GC : : Ref < Node > node , GC : : Ptr < Node > child , bool suppress_observers )
2021-01-28 08:57:37 +01:00
{
2022-04-20 00:57:06 +02:00
// 1. Let nodes be node’ s children, if node is a DocumentFragment node; otherwise « node ».
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Node > > nodes ;
2021-04-06 19:34:49 +01:00
if ( is < DocumentFragment > ( * node ) )
2022-07-11 16:39:14 +01:00
nodes = node - > children_as_vector ( ) ;
2021-04-06 19:34:49 +01:00
else
2024-11-15 04:01:23 +13:00
nodes . append ( GC : : make_root ( * node ) ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 2. Let count be nodes’ s size.
2021-04-06 19:34:49 +01:00
auto count = nodes . size ( ) ;
2022-04-20 00:57:06 +02:00
// 3. If count is 0, then return.
2021-04-06 19:34:49 +01:00
if ( count = = 0 )
return ;
2022-04-20 00:57:06 +02:00
// 4. If node is a DocumentFragment node, then:
2021-04-06 19:34:49 +01:00
if ( is < DocumentFragment > ( * node ) ) {
2022-04-20 19:48:48 +02:00
// 1. Remove its children with the suppress observers flag set.
2021-04-06 19:34:49 +01:00
node - > remove_all_children ( true ) ;
2022-04-20 00:57:06 +02:00
2022-07-11 16:39:14 +01:00
// 2. Queue a tree mutation record for node with « », nodes, null, and null.
2022-04-20 00:57:06 +02:00
// NOTE: This step intentionally does not pay attention to the suppress observers flag.
2023-05-17 19:47:16 +02:00
node - > queue_tree_mutation_record ( { } , nodes , nullptr , nullptr ) ;
2021-04-06 19:34:49 +01:00
}
2022-03-21 20:26:35 +01:00
// 5. If child is non-null, then:
2021-04-06 19:34:49 +01:00
if ( child ) {
2022-04-20 19:48:48 +02:00
// 1. For each live range whose start node is parent and start offset is greater than child’ s index, increase its start offset by count.
2022-03-21 20:26:35 +01:00
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > start_container ( ) = = this & & range - > start_offset ( ) > child - > index ( ) )
2024-03-12 13:08:10 +01:00
range - > increase_start_offset ( { } , count ) ;
2022-03-21 20:26:35 +01:00
}
2022-04-20 19:48:48 +02:00
// 2. For each live range whose end node is parent and end offset is greater than child’ s index, increase its end offset by count.
2022-03-21 20:26:35 +01:00
for ( auto & range : Range : : live_ranges ( ) ) {
2022-03-21 21:11:38 +01:00
if ( range - > end_container ( ) = = this & & range - > end_offset ( ) > child - > index ( ) )
2024-03-12 13:08:10 +01:00
range - > increase_end_offset ( { } , count ) ;
2022-03-21 20:26:35 +01:00
}
2021-04-06 19:34:49 +01:00
}
2022-07-11 16:39:14 +01:00
// 6. Let previousSibling be child’ s previous sibling or parent’ s last child if child is null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > previous_sibling ;
2022-07-11 16:39:14 +01:00
if ( child )
previous_sibling = child - > previous_sibling ( ) ;
else
previous_sibling = last_child ( ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 7. For each node in nodes, in tree order:
// FIXME: In tree order
for ( auto & node_to_insert : nodes ) {
2022-04-20 19:48:48 +02:00
// 1. Adopt node into parent’ s node document.
2022-08-28 13:42:07 +02:00
document ( ) . adopt_node ( * node_to_insert ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 19:48:48 +02:00
// 2. If child is null, then append node to parent’ s children.
2021-04-06 19:34:49 +01:00
if ( ! child )
2022-08-28 13:42:07 +02:00
append_child_impl ( * node_to_insert ) ;
2022-04-20 19:48:48 +02:00
// 3. Otherwise, insert node into parent’ s children before child’ s index.
2021-04-06 19:34:49 +01:00
else
2022-08-28 13:42:07 +02:00
insert_before_impl ( * node_to_insert , child ) ;
2021-04-06 19:34:49 +01:00
2023-09-05 15:13:37 -04:00
// 4. If parent is a shadow host whose shadow root’ s slot assignment is "named" and node is a slottable, then
// assign a slot for node.
if ( is_element ( ) ) {
auto & element = static_cast < DOM : : Element & > ( * this ) ;
auto is_named_shadow_host = element . is_shadow_host ( )
2024-06-25 11:28:58 +02:00
& & element . shadow_root ( ) - > slot_assignment ( ) = = Bindings : : SlotAssignmentMode : : Named ;
2023-09-05 15:13:37 -04:00
2023-11-03 14:22:09 -04:00
if ( is_named_shadow_host & & node_to_insert - > is_slottable ( ) )
assign_a_slot ( node_to_insert - > as_slottable ( ) ) ;
2023-09-05 15:13:37 -04:00
}
// 5. If parent’ s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run
// signal a slot change for parent.
if ( root ( ) . is_shadow_root ( ) & & is < HTML : : HTMLSlotElement > ( * this ) ) {
auto & slot = static_cast < HTML : : HTMLSlotElement & > ( * this ) ;
if ( slot . assigned_nodes_internal ( ) . is_empty ( ) )
signal_a_slot_change ( slot ) ;
}
// 6. Run assign slottables for a tree with node’ s root.
2023-11-03 14:22:09 -04:00
assign_slottables_for_a_tree ( node_to_insert - > root ( ) ) ;
2021-04-06 19:34:49 +01:00
2024-09-04 10:01:08 +02:00
node_to_insert - > invalidate_style ( StyleInvalidationReason : : NodeInsertBefore ) ;
2023-09-08 12:04:10 +02:00
2022-04-20 19:48:48 +02:00
// 7. For each shadow-including inclusive descendant inclusiveDescendant of node, in shadow-including tree order:
2023-03-29 23:46:18 +01:00
node_to_insert - > for_each_shadow_including_inclusive_descendant ( [ & ] ( Node & inclusive_descendant ) {
2022-04-20 19:48:48 +02:00
// 1. Run the insertion steps with inclusiveDescendant.
2021-04-06 19:34:49 +01:00
inclusive_descendant . inserted ( ) ;
2022-04-20 00:57:06 +02:00
2022-04-20 19:48:48 +02:00
// 2. If inclusiveDescendant is connected, then:
2023-03-29 23:46:18 +01:00
// NOTE: This is not specified here in the spec, but these steps can only be performed on an element.
if ( inclusive_descendant . is_connected ( ) & & is < DOM : : Element > ( inclusive_descendant ) ) {
auto & element = static_cast < DOM : : Element & > ( inclusive_descendant ) ;
// 1. If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant,
// callback name "connectedCallback", and an empty argument list.
if ( element . is_custom ( ) ) {
2024-11-15 04:01:23 +13:00
GC : : MarkedVector < JS : : Value > empty_arguments { vm ( ) . heap ( ) } ;
2023-03-29 23:46:18 +01:00
element . enqueue_a_custom_element_callback_reaction ( HTML : : CustomElementReactionNames : : connectedCallback , move ( empty_arguments ) ) ;
}
// 2. Otherwise, try to upgrade inclusiveDescendant.
// NOTE: If this successfully upgrades inclusiveDescendant, its connectedCallback will be enqueued automatically during
// the upgrade an element algorithm.
else {
element . try_to_upgrade ( ) ;
}
2021-04-06 19:34:49 +01:00
}
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2021-04-06 19:34:49 +01:00
} ) ;
}
2022-04-20 00:57:06 +02:00
// 8. If suppress observers flag is unset, then queue a tree mutation record for parent with nodes, « », previousSibling, and child.
2023-02-15 07:44:06 +01:00
if ( ! suppress_observers ) {
2023-05-17 19:47:16 +02:00
queue_tree_mutation_record ( move ( nodes ) , { } , previous_sibling . ptr ( ) , child . ptr ( ) ) ;
2023-02-15 07:44:06 +01:00
}
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 9. Run the children changed steps for parent.
2021-04-06 19:34:49 +01:00
children_changed ( ) ;
2022-03-14 21:39:33 +01:00
2024-04-14 10:24:44 +02:00
if ( is_connected ( ) ) {
// FIXME: This will need to become smarter when we implement the :has() selector.
2024-09-22 13:27:02 +02:00
invalidate_style ( StyleInvalidationReason : : ParentOfInsertedNode ) ;
2024-09-19 07:40:45 +02:00
document ( ) . invalidate_layout_tree ( ) ;
2024-04-14 10:24:44 +02:00
}
2024-03-19 15:39:45 +01:00
document ( ) . bump_dom_tree_version ( ) ;
2021-01-28 08:57:37 +01:00
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-pre-insert
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : pre_insert ( GC : : Ref < Node > node , GC : : Ptr < Node > child )
2020-06-21 16:45:21 +02:00
{
2022-04-20 00:57:06 +02:00
// 1. Ensure pre-insertion validity of node into parent before child.
2022-03-22 12:39:16 +00:00
TRY ( ensure_pre_insertion_validity ( node , child ) ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 2. Let referenceChild be child.
2021-04-06 19:34:49 +01:00
auto reference_child = child ;
2022-04-20 00:57:06 +02:00
// 3. If referenceChild is node, then set referenceChild to node’ s next sibling.
2021-04-06 19:34:49 +01:00
if ( reference_child = = node )
reference_child = node - > next_sibling ( ) ;
2022-04-20 00:57:06 +02:00
// 4. Insert node into parent before referenceChild.
2021-04-06 19:34:49 +01:00
insert_before ( node , reference_child ) ;
2022-04-20 00:57:06 +02:00
// 5. Return node.
2020-06-21 16:45:21 +02:00
return node ;
}
2022-02-21 22:21:59 +01:00
// https://dom.spec.whatwg.org/#dom-node-removechild
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : remove_child ( GC : : Ref < Node > child )
2022-02-21 22:21:59 +01:00
{
2022-04-20 00:57:06 +02:00
// The removeChild(child) method steps are to return the result of pre-removing child from this.
2022-02-21 22:21:59 +01:00
return pre_remove ( child ) ;
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-pre-remove
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : pre_remove ( GC : : Ref < Node > child )
2021-04-06 19:34:49 +01:00
{
2022-04-20 00:57:06 +02:00
// 1. If child’ s parent is not parent, then throw a "NotFoundError" DOMException.
2021-04-13 23:03:48 +04:30
if ( child - > parent ( ) ! = this )
2024-10-12 20:56:21 +02:00
return WebIDL : : NotFoundError : : create ( realm ( ) , " Child does not belong to this node " _string ) ;
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 2. Remove child.
2021-04-06 19:34:49 +01:00
child - > remove ( ) ;
2022-04-20 00:57:06 +02:00
// 3. Return child.
2021-04-06 19:34:49 +01:00
return child ;
}
// https://dom.spec.whatwg.org/#concept-node-append
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : append_child ( GC : : Ref < Node > node )
2021-04-06 19:34:49 +01:00
{
2022-04-20 00:57:06 +02:00
// To append a node to a parent, pre-insert node into parent before null.
2021-04-06 19:34:49 +01:00
return pre_insert ( node , nullptr ) ;
}
// https://dom.spec.whatwg.org/#concept-node-remove
void Node : : remove ( bool suppress_observers )
{
2022-04-20 00:57:06 +02:00
// 1. Let parent be node’ s parent
2022-08-28 13:42:07 +02:00
auto * parent = this - > parent ( ) ;
2022-04-20 00:57:06 +02:00
// 2. Assert: parent is non-null.
2021-04-06 19:34:49 +01:00
VERIFY ( parent ) ;
2022-03-21 20:26:35 +01:00
// 3. Let index be node’ s index.
auto index = this - > index ( ) ;
// 4. For each live range whose start node is an inclusive descendant of node, set its start to (parent, index).
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > start_container ( ) - > is_inclusive_descendant_of ( * this ) )
2022-10-30 17:50:04 +00:00
MUST ( range - > set_start ( * parent , index ) ) ;
2022-03-21 20:26:35 +01:00
}
// 5. For each live range whose end node is an inclusive descendant of node, set its end to (parent, index).
for ( auto & range : Range : : live_ranges ( ) ) {
if ( range - > end_container ( ) - > is_inclusive_descendant_of ( * this ) )
2022-10-30 17:50:04 +00:00
MUST ( range - > set_end ( * parent , index ) ) ;
2022-03-21 20:26:35 +01:00
}
2021-04-06 19:34:49 +01:00
2022-03-21 20:26:35 +01:00
// 6. For each live range whose start node is parent and start offset is greater than index, decrease its start offset by 1.
for ( auto & range : Range : : live_ranges ( ) ) {
2022-03-21 21:11:38 +01:00
if ( range - > start_container ( ) = = parent & & range - > start_offset ( ) > index )
2024-03-12 13:08:10 +01:00
range - > decrease_start_offset ( { } , 1 ) ;
2022-03-21 20:26:35 +01:00
}
// 7. For each live range whose end node is parent and end offset is greater than index, decrease its end offset by 1.
for ( auto & range : Range : : live_ranges ( ) ) {
2022-03-21 21:11:38 +01:00
if ( range - > end_container ( ) = = parent & & range - > end_offset ( ) > index )
2024-03-12 13:08:10 +01:00
range - > decrease_end_offset ( { } , 1 ) ;
2022-03-21 20:26:35 +01:00
}
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 8. For each NodeIterator object iterator whose root’ s node document is node’ s node document, run the NodeIterator pre-removing steps given node and iterator.
2022-03-09 16:38:44 +01:00
document ( ) . for_each_node_iterator ( [ & ] ( NodeIterator & node_iterator ) {
node_iterator . run_pre_removing_steps ( * this ) ;
} ) ;
2021-04-06 19:34:49 +01:00
2022-07-11 16:39:14 +01:00
// 9. Let oldPreviousSibling be node’ s previous sibling.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > old_previous_sibling = previous_sibling ( ) ;
2022-07-11 16:39:14 +01:00
// 10. Let oldNextSibling be node’ s next sibling.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > old_next_sibling = next_sibling ( ) ;
2021-04-06 19:34:49 +01:00
2024-11-06 17:24:39 +01:00
if ( is_connected ( ) ) {
// Since the tree structure is about to change, we need to invalidate both style and layout.
// In the future, we should find a way to only invalidate the parts that actually need it.
invalidate_style ( StyleInvalidationReason : : NodeRemove ) ;
// NOTE: If we didn't have a layout node before, rebuilding the layout tree isn't gonna give us one
// after we've been removed from the DOM.
if ( layout_node ( ) ) {
document ( ) . invalidate_layout_tree ( ) ;
}
}
2022-04-20 00:57:06 +02:00
// 11. Remove node from its parent’ s children.
2022-08-28 13:42:07 +02:00
parent - > remove_child_impl ( * this ) ;
2021-04-06 19:34:49 +01:00
2023-09-05 15:13:37 -04:00
// 12. If node is assigned, then run assign slottables for node’ s assigned slot.
if ( auto assigned_slot = assigned_slot_for_node ( * this ) )
assign_slottables ( * assigned_slot ) ;
2021-04-06 19:34:49 +01:00
2023-09-05 15:13:37 -04:00
// 13. If parent’ s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run
// signal a slot change for parent.
if ( parent - > root ( ) . is_shadow_root ( ) & & is < HTML : : HTMLSlotElement > ( parent ) ) {
auto & slot = static_cast < HTML : : HTMLSlotElement & > ( * parent ) ;
2021-04-06 19:34:49 +01:00
2023-09-05 15:13:37 -04:00
if ( slot . assigned_nodes_internal ( ) . is_empty ( ) )
signal_a_slot_change ( slot ) ;
}
// 14. If node has an inclusive descendant that is a slot, then:
auto has_descendent_slot = false ;
for_each_in_inclusive_subtree_of_type < HTML : : HTMLSlotElement > ( [ & ] ( auto const & ) {
has_descendent_slot = true ;
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Break ;
2023-09-05 15:13:37 -04:00
} ) ;
if ( has_descendent_slot ) {
// 1. Run assign slottables for a tree with parent’ s root.
assign_slottables_for_a_tree ( parent - > root ( ) ) ;
// 2. Run assign slottables for a tree with node.
assign_slottables_for_a_tree ( * this ) ;
}
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 15. Run the removing steps with node and parent.
2021-04-06 19:34:49 +01:00
removed_from ( parent ) ;
2023-03-29 23:46:18 +01:00
// 16. Let isParentConnected be parent’ s connected.
bool is_parent_connected = parent - > is_connected ( ) ;
// 17. If node is custom and isParentConnected is true, then enqueue a custom element callback reaction with node,
// callback name "disconnectedCallback", and an empty argument list.
// Spec Note: It is intentional for now that custom elements do not get parent passed.
// This might change in the future if there is a need.
if ( is < DOM : : Element > ( * this ) ) {
auto & element = static_cast < DOM : : Element & > ( * this ) ;
2021-04-06 19:34:49 +01:00
2023-03-29 23:46:18 +01:00
if ( element . is_custom ( ) & & is_parent_connected ) {
2024-11-15 04:01:23 +13:00
GC : : MarkedVector < JS : : Value > empty_arguments { vm ( ) . heap ( ) } ;
2023-03-29 23:46:18 +01:00
element . enqueue_a_custom_element_callback_reaction ( HTML : : CustomElementReactionNames : : disconnectedCallback , move ( empty_arguments ) ) ;
}
}
2021-04-06 19:34:49 +01:00
2022-04-20 00:57:06 +02:00
// 18. For each shadow-including descendant descendant of node, in shadow-including tree order, then:
2023-03-29 23:46:18 +01:00
for_each_shadow_including_descendant ( [ & ] ( Node & descendant ) {
2022-04-20 19:48:48 +02:00
// 1. Run the removing steps with descendant
2021-04-06 19:34:49 +01:00
descendant . removed_from ( nullptr ) ;
2023-03-29 23:46:18 +01:00
// 2. If descendant is custom and isParentConnected is true, then enqueue a custom element callback reaction with descendant,
// callback name "disconnectedCallback", and an empty argument list.
if ( is < DOM : : Element > ( descendant ) ) {
auto & element = static_cast < DOM : : Element & > ( descendant ) ;
if ( element . is_custom ( ) & & is_parent_connected ) {
2024-11-15 04:01:23 +13:00
GC : : MarkedVector < JS : : Value > empty_arguments { vm ( ) . heap ( ) } ;
2023-03-29 23:46:18 +01:00
element . enqueue_a_custom_element_callback_reaction ( HTML : : CustomElementReactionNames : : disconnectedCallback , move ( empty_arguments ) ) ;
}
}
2021-04-06 19:34:49 +01:00
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2021-04-06 19:34:49 +01:00
} ) ;
2022-07-17 18:14:22 +01:00
// 19. For each inclusive ancestor inclusiveAncestor of parent, and then for each registered of inclusiveAncestor’ s registered observer list,
// if registered’ s options["subtree"] is true, then append a new transient registered observer
// whose observer is registered’ s observer, options is registered’ s options, and source is registered to node’ s registered observer list.
for ( auto * inclusive_ancestor = parent ; inclusive_ancestor ; inclusive_ancestor = inclusive_ancestor - > parent ( ) ) {
2023-11-18 11:22:51 +01:00
if ( ! inclusive_ancestor - > m_registered_observer_list )
continue ;
for ( auto & registered : * inclusive_ancestor - > m_registered_observer_list ) {
2023-02-26 16:09:02 -07:00
if ( registered - > options ( ) . subtree ) {
auto transient_observer = TransientRegisteredObserver : : create ( registered - > observer ( ) , registered - > options ( ) , registered ) ;
2023-11-20 20:03:43 +01:00
add_registered_observer ( move ( transient_observer ) ) ;
2022-07-17 18:14:22 +01:00
}
}
}
2022-04-20 00:57:06 +02:00
// 20. If suppress observers flag is unset, then queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
2021-04-06 19:34:49 +01:00
if ( ! suppress_observers ) {
2023-05-17 19:47:16 +02:00
parent - > queue_tree_mutation_record ( { } , { * this } , old_previous_sibling . ptr ( ) , old_next_sibling . ptr ( ) ) ;
2021-04-06 19:34:49 +01:00
}
2022-04-20 00:57:06 +02:00
// 21. Run the children changed steps for parent.
2021-04-06 19:34:49 +01:00
parent - > children_changed ( ) ;
2022-03-09 17:54:50 +01:00
2024-03-19 15:39:45 +01:00
document ( ) . bump_dom_tree_version ( ) ;
2021-04-06 19:34:49 +01:00
}
2021-05-07 00:52:23 +01:00
// https://dom.spec.whatwg.org/#concept-node-replace
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : replace_child ( GC : : Ref < Node > node , GC : : Ref < Node > child )
2021-05-07 00:52:23 +01:00
{
2022-04-20 00:57:06 +02:00
// If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
2021-05-07 00:52:23 +01:00
if ( ! is < Document > ( this ) & & ! is < DocumentFragment > ( this ) & & ! is < Element > ( this ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Can only insert into a document, document fragment or element " _string ) ;
2021-05-07 00:52:23 +01:00
2022-04-20 00:57:06 +02:00
// 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException.
2021-05-07 00:52:23 +01:00
if ( node - > is_host_including_inclusive_ancestor_of ( * this ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " New node is an ancestor of this node " _string ) ;
2021-05-07 00:52:23 +01:00
2022-04-20 00:57:06 +02:00
// 3. If child’ s parent is not parent, then throw a "NotFoundError" DOMException.
2021-05-07 00:52:23 +01:00
if ( child - > parent ( ) ! = this )
2024-10-12 20:56:21 +02:00
return WebIDL : : NotFoundError : : create ( realm ( ) , " This node is not the parent of the given child " _string ) ;
2021-05-07 00:52:23 +01:00
// FIXME: All the following "Invalid node type for insertion" messages could be more descriptive.
2022-04-20 00:57:06 +02:00
// 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
2021-05-07 00:52:23 +01:00
if ( ! is < DocumentFragment > ( * node ) & & ! is < DocumentType > ( * node ) & & ! is < Element > ( * node ) & & ! is < Text > ( * node ) & & ! is < Comment > ( * node ) & & ! is < ProcessingInstruction > ( * node ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-05-07 00:52:23 +01:00
2022-04-20 00:57:06 +02:00
// 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
2021-05-07 00:52:23 +01:00
if ( ( is < Text > ( * node ) & & is < Document > ( this ) ) | | ( is < DocumentType > ( * node ) & & ! is < Document > ( this ) ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-05-07 00:52:23 +01:00
2022-04-20 00:57:06 +02:00
// If parent is a document, and any of the statements below, switched on the interface node implements, are true, then throw a "HierarchyRequestError" DOMException.
2021-05-07 00:52:23 +01:00
if ( is < Document > ( this ) ) {
2022-04-20 00:57:06 +02:00
// DocumentFragment
2021-05-07 00:52:23 +01:00
if ( is < DocumentFragment > ( * node ) ) {
2022-04-20 00:57:06 +02:00
// If node has more than one element child or has a Text node child.
// Otherwise, if node has one element child and either parent has an element child that is not child or a doctype is following child.
2021-06-24 19:53:42 +02:00
auto node_element_child_count = verify_cast < DocumentFragment > ( * node ) . child_element_count ( ) ;
2021-05-07 00:52:23 +01:00
if ( ( node_element_child_count > 1 | | node - > has_child_of_type < Text > ( ) )
2021-09-07 08:15:34 +01:00
| | ( node_element_child_count = = 1 & & ( first_child_of_type < Element > ( ) ! = child | | child - > has_following_node_of_type_in_tree_order < DocumentType > ( ) ) ) ) {
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-05-07 00:52:23 +01:00
}
} else if ( is < Element > ( * node ) ) {
2022-04-20 00:57:06 +02:00
// Element
// parent has an element child that is not child or a doctype is following child.
2021-09-07 08:15:34 +01:00
if ( first_child_of_type < Element > ( ) ! = child | | child - > has_following_node_of_type_in_tree_order < DocumentType > ( ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-05-07 00:52:23 +01:00
} else if ( is < DocumentType > ( * node ) ) {
2022-04-20 00:57:06 +02:00
// DocumentType
// parent has a doctype child that is not child, or an element is preceding child.
2024-11-20 13:00:56 +01:00
if ( first_child_of_type < DocumentType > ( ) ! = child | | child - > has_preceding_node_of_type_in_tree_order < Element > ( ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : HierarchyRequestError : : create ( realm ( ) , " Invalid node type for insertion " _string ) ;
2021-05-07 00:52:23 +01:00
}
}
2022-04-20 00:57:06 +02:00
// 7. Let referenceChild be child’ s next sibling.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > reference_child = child - > next_sibling ( ) ;
2022-04-20 00:57:06 +02:00
// 8. If referenceChild is node, then set referenceChild to node’ s next sibling.
2021-05-07 00:52:23 +01:00
if ( reference_child = = node )
reference_child = node - > next_sibling ( ) ;
2022-07-11 16:39:14 +01:00
// 9. Let previousSibling be child’ s previous sibling.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > previous_sibling = child - > previous_sibling ( ) ;
2022-07-11 16:39:14 +01:00
// 10. Let removedNodes be the empty set.
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Node > > removed_nodes ;
2021-05-07 00:52:23 +01:00
2022-04-20 00:57:06 +02:00
// 11. If child’ s parent is non-null, then:
// NOTE: The above can only be false if child is node.
2021-05-07 00:52:23 +01:00
if ( child - > parent ( ) ) {
2022-07-11 16:39:14 +01:00
// 1. Set removedNodes to « child ».
2024-11-15 04:01:23 +13:00
removed_nodes . append ( GC : : make_root ( * child ) ) ;
2022-04-20 00:57:06 +02:00
2022-04-20 19:48:48 +02:00
// 2. Remove child with the suppress observers flag set.
2021-05-07 00:52:23 +01:00
child - > remove ( true ) ;
}
2022-07-11 16:39:14 +01:00
// 12. Let nodes be node’ s children if node is a DocumentFragment node; otherwise « node ».
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Node > > nodes ;
2022-07-11 16:39:14 +01:00
if ( is < DocumentFragment > ( * node ) )
nodes = node - > children_as_vector ( ) ;
else
2024-11-15 04:01:23 +13:00
nodes . append ( GC : : make_root ( * node ) ) ;
2021-05-07 00:52:23 +01:00
2024-11-24 10:23:56 +01:00
// AD-HOC: Since removing the child may have executed arbitrary code, we have to verify
// the sanity of inserting `node` before `reference_child` again, as well as
// `child` not being reinserted elsewhere.
if ( ! reference_child | | ( reference_child - > parent ( ) = = this & & ! child - > parent_node ( ) ) ) {
// 13. Insert node into parent before referenceChild with the suppress observers flag set.
insert_before ( node , reference_child , true ) ;
}
2021-05-07 00:52:23 +01:00
2022-07-11 16:39:14 +01:00
// 14. Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
2023-05-17 19:47:16 +02:00
queue_tree_mutation_record ( move ( nodes ) , move ( removed_nodes ) , previous_sibling . ptr ( ) , reference_child . ptr ( ) ) ;
2021-05-07 00:52:23 +01:00
2022-04-20 00:57:06 +02:00
// 15. Return child.
2021-05-07 00:52:23 +01:00
return child ;
}
2021-04-14 01:25:10 +02:00
// https://dom.spec.whatwg.org/#concept-node-clone
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : clone_node ( Document * document , bool clone_children )
2021-04-14 01:25:10 +02:00
{
2022-04-20 00:57:06 +02:00
// 1. If document is not given, let document be node’ s node document.
2021-04-14 01:25:10 +02:00
if ( ! document )
2022-08-28 13:42:07 +02:00
document = m_document . ptr ( ) ;
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > copy ;
2022-04-20 00:57:06 +02:00
// 2. If node is an element, then:
2021-04-14 01:25:10 +02:00
if ( is < Element > ( this ) ) {
2022-04-20 19:48:48 +02:00
// 1. Let copy be the result of creating an element, given document, node’ s local name, node’ s namespace, node’ s namespace prefix, and node’ s is value, with the synchronous custom elements flag unset.
2021-06-24 19:53:42 +02:00
auto & element = * verify_cast < Element > ( this ) ;
2023-11-04 09:46:23 +01:00
auto element_copy = DOM : : create_element ( * document , element . local_name ( ) , element . namespace_uri ( ) , element . prefix ( ) , element . is_value ( ) , false ) . release_value_but_fixme_should_propagate_errors ( ) ;
2022-04-20 00:57:06 +02:00
2022-04-20 19:48:48 +02:00
// 2. For each attribute in node’ s attribute list:
2021-04-14 01:25:10 +02:00
element . for_each_attribute ( [ & ] ( auto & name , auto & value ) {
2022-04-20 19:48:48 +02:00
// 1. Let copyAttribute be a clone of attribute.
// 2. Append copyAttribute to copy.
2024-04-21 18:16:27 +02:00
element_copy - > append_attribute ( name , value ) ;
2021-04-14 01:25:10 +02:00
} ) ;
copy = move ( element_copy ) ;
2022-04-20 00:57:06 +02:00
2022-06-03 19:12:15 +03:00
}
// 3. Otherwise, let copy be a node that implements the same interfaces as node, and fulfills these additional requirements, switching on the interface node implements:
else if ( is < Document > ( this ) ) {
2022-04-20 00:57:06 +02:00
// Document
2021-06-24 19:53:42 +02:00
auto document_ = verify_cast < Document > ( this ) ;
2024-11-19 15:16:14 +01:00
auto document_copy = [ & ] - > GC : : Ref < Document > {
switch ( document_ - > document_type ( ) ) {
case Document : : Type : : XML :
return XMLDocument : : create ( realm ( ) , document_ - > url ( ) ) ;
case Document : : Type : : HTML :
return HTML : : HTMLDocument : : create ( realm ( ) , document_ - > url ( ) ) ;
default :
return Document : : create ( realm ( ) , document_ - > url ( ) ) ;
}
} ( ) ;
2022-04-20 00:57:06 +02:00
// Set copy’ s encoding, content type, URL, origin, type, and mode to those of node.
2021-04-14 01:25:10 +02:00
document_copy - > set_encoding ( document_ - > encoding ( ) ) ;
document_copy - > set_content_type ( document_ - > content_type ( ) ) ;
2022-04-20 00:57:06 +02:00
document_copy - > set_url ( document_ - > url ( ) ) ;
2021-04-14 01:25:10 +02:00
document_copy - > set_origin ( document_ - > origin ( ) ) ;
2022-07-03 21:44:00 +02:00
document_copy - > set_document_type ( document_ - > document_type ( ) ) ;
2022-04-20 00:57:06 +02:00
document_copy - > set_quirks_mode ( document_ - > mode ( ) ) ;
2021-04-14 01:25:10 +02:00
copy = move ( document_copy ) ;
} else if ( is < DocumentType > ( this ) ) {
2022-04-20 00:57:06 +02:00
// DocumentType
2021-06-24 19:53:42 +02:00
auto document_type = verify_cast < DocumentType > ( this ) ;
2024-11-14 05:50:17 +13:00
auto document_type_copy = realm ( ) . create < DocumentType > ( * document ) ;
2022-04-20 00:57:06 +02:00
// Set copy’ s name, public ID, and system ID to those of node.
2021-04-14 01:25:10 +02:00
document_type_copy - > set_name ( document_type - > name ( ) ) ;
document_type_copy - > set_public_id ( document_type - > public_id ( ) ) ;
document_type_copy - > set_system_id ( document_type - > system_id ( ) ) ;
copy = move ( document_type_copy ) ;
2022-09-18 01:03:58 +02:00
} else if ( is < Attr > ( this ) ) {
2022-04-20 00:57:06 +02:00
// Attr
// Set copy’ s namespace, namespace prefix, local name, and value to those of node.
2022-12-13 13:08:46 +01:00
auto & attr = static_cast < Attr & > ( * this ) ;
copy = attr . clone ( * document ) ;
2024-11-20 08:37:14 -05:00
} else if ( is < Text > ( this ) ) {
2022-04-20 00:57:06 +02:00
// Text
2024-07-23 21:08:46 +01:00
auto & text = static_cast < Text & > ( * this ) ;
2022-04-20 00:57:06 +02:00
// Set copy’ s data to that of node.
2024-11-20 08:37:14 -05:00
copy = [ & ] ( ) - > GC : : Ref < Text > {
switch ( type ( ) ) {
case NodeType : : TEXT_NODE :
return realm ( ) . create < Text > ( * document , text . data ( ) ) ;
case NodeType : : CDATA_SECTION_NODE :
return realm ( ) . create < CDATASection > ( * document , text . data ( ) ) ;
default :
VERIFY_NOT_REACHED ( ) ;
}
} ( ) ;
2021-04-14 01:25:10 +02:00
} else if ( is < Comment > ( this ) ) {
2022-04-20 00:57:06 +02:00
// Comment
2021-06-24 19:53:42 +02:00
auto comment = verify_cast < Comment > ( this ) ;
2022-04-20 00:57:06 +02:00
// Set copy’ s data to that of node.
2024-11-14 05:50:17 +13:00
auto comment_copy = realm ( ) . create < Comment > ( * document , comment - > data ( ) ) ;
2021-04-14 01:25:10 +02:00
copy = move ( comment_copy ) ;
} else if ( is < ProcessingInstruction > ( this ) ) {
2022-04-20 00:57:06 +02:00
// ProcessingInstruction
2021-06-24 19:53:42 +02:00
auto processing_instruction = verify_cast < ProcessingInstruction > ( this ) ;
2022-04-20 00:57:06 +02:00
// Set copy’ s target and data to those of node.
2024-11-14 05:50:17 +13:00
auto processing_instruction_copy = realm ( ) . create < ProcessingInstruction > ( * document , processing_instruction - > data ( ) , processing_instruction - > target ( ) ) ;
2022-08-28 13:42:07 +02:00
copy = processing_instruction_copy ;
2021-04-14 01:25:10 +02:00
}
2022-04-20 00:57:06 +02:00
// Otherwise, Do nothing.
2022-06-03 19:12:15 +03:00
else if ( is < DocumentFragment > ( this ) ) {
2024-11-14 05:50:17 +13:00
copy = realm ( ) . create < DocumentFragment > ( * document ) ;
2022-06-03 19:12:15 +03:00
}
2022-04-20 00:57:06 +02:00
2021-04-14 01:25:10 +02:00
// FIXME: 4. Set copy’ s node document and document to copy, if copy is a document, and set copy’ s node document to document otherwise.
2021-07-05 05:30:24 +01:00
2022-04-20 00:57:06 +02:00
// 5. Run any cloning steps defined for node in other applicable specifications and pass copy, node, document and the clone children flag if set, as parameters.
2024-06-25 11:06:41 +02:00
TRY ( cloned ( * copy , clone_children ) ) ;
2021-07-05 05:30:24 +01:00
2022-04-20 00:57:06 +02:00
// 6. If the clone children flag is set, clone all the children of node and append them to copy, with document as specified and the clone children flag being set.
2021-04-14 01:25:10 +02:00
if ( clone_children ) {
2024-06-25 11:06:41 +02:00
for ( auto child = first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
TRY ( copy - > append_child ( TRY ( child - > clone_node ( document , true ) ) ) ) ;
}
}
// 7. If node is a shadow host whose shadow root’ s clonable is true:
if ( is_element ( ) & & static_cast < Element const & > ( * this ) . is_shadow_host ( ) & & static_cast < Element const & > ( * this ) . shadow_root ( ) - > clonable ( ) ) {
// 1. Assert: copy is not a shadow host.
VERIFY ( ! copy - > is_element ( ) | | ! static_cast < Element const & > ( * copy ) . is_shadow_host ( ) ) ;
// 2. Run attach a shadow root with copy, node’ s shadow root’ s mode, true, node’ s shadow root’ s serializable,
// node’ s shadow root’ s delegates focus, and node’ s shadow root’ s slot assignment.
2024-06-25 11:28:58 +02:00
auto & node_shadow_root = * static_cast < Element & > ( * this ) . shadow_root ( ) ;
2024-06-25 11:06:41 +02:00
TRY ( static_cast < Element & > ( * copy ) . attach_a_shadow_root ( node_shadow_root . mode ( ) , true , node_shadow_root . serializable ( ) , node_shadow_root . delegates_focus ( ) , node_shadow_root . slot_assignment ( ) ) ) ;
// 3. Set copy’ s shadow root’ s declarative to node’ s shadow root’ s declarative.
static_cast < Element & > ( * copy ) . shadow_root ( ) - > set_declarative ( node_shadow_root . declarative ( ) ) ;
// 4. For each child child of node’ s shadow root, in tree order:
// append the result of cloning child with document and the clone children flag set, to copy’ s shadow root.
for ( auto child = node_shadow_root . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
TRY ( static_cast < Element & > ( * copy ) . shadow_root ( ) - > append_child ( TRY ( child - > clone_node ( document , true ) ) ) ) ;
}
2021-04-14 01:25:10 +02:00
}
2022-04-20 00:57:06 +02:00
// 7. Return copy.
2024-07-23 21:10:32 +01:00
VERIFY ( copy ) ;
2024-11-15 04:01:23 +13:00
return GC : : Ref { * copy } ;
2021-04-14 01:25:10 +02:00
}
// https://dom.spec.whatwg.org/#dom-node-clonenode
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Node > > Node : : clone_node_binding ( bool deep )
2021-04-14 01:25:10 +02:00
{
2022-04-20 00:57:06 +02:00
// 1. If this is a shadow root, then throw a "NotSupportedError" DOMException.
2021-04-14 01:25:10 +02:00
if ( is < ShadowRoot > ( * this ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : NotSupportedError : : create ( realm ( ) , " Cannot clone shadow root " _string ) ;
2022-04-20 00:57:06 +02:00
// 2. Return a clone of this, with the clone children flag set if deep is true.
2021-04-14 01:25:10 +02:00
return clone_node ( nullptr , deep ) ;
}
2020-06-25 23:42:08 +02:00
void Node : : set_document ( Badge < Document > , Document & document )
{
2022-08-28 13:42:07 +02:00
if ( m_document . ptr ( ) = = & document )
2020-10-22 23:37:17 +02:00
return ;
2021-08-30 15:52:08 +01:00
2020-06-25 23:42:08 +02:00
m_document = & document ;
2021-10-12 17:53:38 +02:00
if ( needs_style_update ( ) | | child_needs_style_update ( ) ) {
// NOTE: We unset and reset the "needs style update" flag here.
// This ensures that there's a pending style update in the new document
// that will eventually assign some style to this node if needed.
set_needs_style_update ( false ) ;
set_needs_style_update ( true ) ;
}
2020-06-25 23:42:08 +02:00
}
2020-08-02 16:05:59 +02:00
bool Node : : is_editable ( ) const
{
return parent ( ) & & parent ( ) - > is_editable ( ) ;
}
2024-11-15 04:01:23 +13:00
void Node : : set_layout_node ( Badge < Layout : : Node > , GC : : Ref < Layout : : Node > layout_node )
2020-10-22 20:26:32 +02:00
{
2022-07-04 00:42:44 +02:00
m_layout_node = layout_node ;
2020-10-22 20:26:32 +02:00
}
2023-08-01 08:23:13 +02:00
void Node : : detach_layout_node ( Badge < Layout : : TreeBuilder > )
2022-10-17 14:41:50 +02:00
{
m_layout_node = nullptr ;
}
2022-04-01 20:58:27 +03:00
EventTarget * Node : : get_parent ( Event const & )
2020-11-21 18:32:39 +00:00
{
2023-09-05 15:15:28 -04:00
// A node’ s get the parent algorithm, given an event, returns the node’ s assigned slot, if node is assigned;
// otherwise node’ s parent.
if ( auto assigned_slot = assigned_slot_for_node ( * this ) )
return assigned_slot . ptr ( ) ;
2020-11-21 18:32:39 +00:00
return parent ( ) ;
}
2020-12-13 15:19:42 +01:00
void Node : : set_needs_style_update ( bool value )
{
if ( m_needs_style_update = = value )
return ;
m_needs_style_update = value ;
2020-12-14 12:04:30 +01:00
if ( m_needs_style_update ) {
2022-03-14 12:52:27 +01:00
for ( auto * ancestor = parent_or_shadow_host ( ) ; ancestor ; ancestor = ancestor - > parent_or_shadow_host ( ) ) {
2024-10-19 17:16:59 +02:00
if ( ancestor - > m_child_needs_style_update )
break ;
2020-12-14 12:04:30 +01:00
ancestor - > m_child_needs_style_update = true ;
2021-04-06 19:34:49 +01:00
}
2020-12-13 15:19:42 +01:00
document ( ) . schedule_style_update ( ) ;
2020-12-14 12:04:30 +01:00
}
}
2021-04-06 19:34:49 +01:00
void Node : : inserted ( )
2020-12-14 12:04:30 +01:00
{
set_needs_style_update ( true ) ;
2020-12-13 15:19:42 +01:00
}
2024-02-19 02:22:15 +01:00
void Node : : removed_from ( Node * )
{
m_layout_node = nullptr ;
m_paintable = nullptr ;
}
2021-02-10 18:22:20 +01:00
ParentNode * Node : : parent_or_shadow_host ( )
{
if ( is < ShadowRoot > ( * this ) )
2022-11-06 00:45:27 +01:00
return static_cast < ShadowRoot & > ( * this ) . host ( ) ;
2021-06-24 19:53:42 +02:00
return verify_cast < ParentNode > ( parent ( ) ) ;
2021-02-10 18:22:20 +01:00
}
2022-11-05 17:06:19 +01:00
Element * Node : : parent_or_shadow_host_element ( )
{
if ( is < ShadowRoot > ( * this ) )
return static_cast < ShadowRoot & > ( * this ) . host ( ) ;
if ( ! parent ( ) )
return nullptr ;
if ( is < Element > ( * parent ( ) ) )
return static_cast < Element * > ( parent ( ) ) ;
if ( is < ShadowRoot > ( * parent ( ) ) )
return static_cast < ShadowRoot & > ( * parent ( ) ) . host ( ) ;
return nullptr ;
}
2023-09-05 13:07:35 -04:00
Slottable Node : : as_slottable ( )
{
VERIFY ( is_slottable ( ) ) ;
if ( is_element ( ) )
2024-11-15 04:01:23 +13:00
return GC : : Ref { static_cast < Element & > ( * this ) } ;
return GC : : Ref { static_cast < Text & > ( * this ) } ;
2023-09-05 13:07:35 -04:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < NodeList > Node : : child_nodes ( )
2021-10-02 20:37:45 +01:00
{
2022-09-21 13:49:31 +02:00
if ( ! m_child_nodes ) {
2023-05-23 12:28:16 +02:00
m_child_nodes = LiveNodeList : : create ( realm ( ) , * this , LiveNodeList : : Scope : : Children , [ ] ( auto & ) {
return true ;
2023-08-13 13:05:26 +02:00
} ) ;
2022-09-21 13:49:31 +02:00
}
return * m_child_nodes ;
2021-10-02 20:37:45 +01:00
}
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Node > > Node : : children_as_vector ( ) const
2021-03-06 17:06:25 +00:00
{
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Node > > nodes ;
2021-03-06 17:06:25 +00:00
for_each_child ( [ & ] ( auto & child ) {
2024-11-15 04:01:23 +13:00
nodes . append ( GC : : make_root ( child ) ) ;
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2021-03-06 17:06:25 +00:00
} ) ;
return nodes ;
}
2021-04-06 19:34:49 +01:00
void Node : : remove_all_children ( bool suppress_observers )
{
2024-11-15 04:01:23 +13:00
while ( GC : : Ptr < Node > child = first_child ( ) )
2021-04-06 19:34:49 +01:00
child - > remove ( suppress_observers ) ;
}
2021-04-10 17:21:22 -07:00
// https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
2024-11-15 04:01:23 +13:00
u16 Node : : compare_document_position ( GC : : Ptr < Node > other )
2021-04-10 17:21:22 -07:00
{
2022-04-20 00:57:06 +02:00
// 1. If this is other, then return zero.
2022-08-28 13:42:07 +02:00
if ( this = = other . ptr ( ) )
2021-04-10 17:21:22 -07:00
return DOCUMENT_POSITION_EQUAL ;
2022-04-20 00:57:06 +02:00
// 2. Let node1 be other and node2 be this.
2021-04-10 17:21:22 -07:00
Node * node1 = other . ptr ( ) ;
Node * node2 = this ;
2022-04-20 00:57:06 +02:00
// 3. Let attr1 and attr2 be null.
2023-05-24 00:18:35 +03:00
Attr * attr1 = nullptr ;
Attr * attr2 = nullptr ;
2022-04-20 00:57:06 +02:00
// 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’ s element.
2022-09-18 01:03:58 +02:00
if ( is < Attr > ( node1 ) ) {
attr1 = verify_cast < Attr > ( node1 ) ;
2022-04-20 00:57:06 +02:00
node1 = const_cast < Element * > ( attr1 - > owner_element ( ) ) ;
}
2021-04-10 17:21:22 -07:00
2022-04-20 00:57:06 +02:00
// 5. If node2 is an attribute, then:
2022-09-18 01:03:58 +02:00
if ( is < Attr > ( node2 ) ) {
2022-04-20 19:48:48 +02:00
// 1. Set attr2 to node2 and node2 to attr2’ s element.
2022-09-18 01:03:58 +02:00
attr2 = verify_cast < Attr > ( node2 ) ;
2022-04-20 00:57:06 +02:00
node2 = const_cast < Element * > ( attr2 - > owner_element ( ) ) ;
2022-04-20 19:48:48 +02:00
// 2. If attr1 and node1 are non-null, and node2 is node1, then:
2022-04-20 00:57:06 +02:00
if ( attr1 & & node1 & & node2 = = node1 ) {
2022-04-20 19:48:48 +02:00
// FIXME: 1. For each attr in node2’ s attribute list:
// 1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
// 2. If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING.
2022-04-20 00:57:06 +02:00
}
}
// 6. If node1 or node2 is null, or node1’ s root is not node2’ s root, then return the result of adding
// DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING, with the constraint that this is to be consistent, together.
2021-09-02 19:27:42 +01:00
if ( ( node1 = = nullptr | | node2 = = nullptr ) | | ( & node1 - > root ( ) ! = & node2 - > root ( ) ) )
2021-04-10 17:21:22 -07:00
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | ( node1 > node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING ) ;
2023-05-24 00:18:35 +03:00
Vector < Node * > node1_ancestors ;
for ( auto * node = node1 ; node ; node = node - > parent ( ) )
node1_ancestors . append ( node ) ;
Vector < Node * > node2_ancestors ;
for ( auto * node = node2 ; node ; node = node - > parent ( ) )
node2_ancestors . append ( node ) ;
auto it_node1_ancestors = node1_ancestors . rbegin ( ) ;
auto it_node2_ancestors = node2_ancestors . rbegin ( ) ;
// Walk ancestor chains of both nodes starting from root
while ( it_node1_ancestors ! = node1_ancestors . rend ( ) & & it_node2_ancestors ! = node2_ancestors . rend ( ) ) {
auto * ancestor1 = * it_node1_ancestors ;
auto * ancestor2 = * it_node2_ancestors ;
// If ancestors of nodes at the same level in the tree are different then preceding node is the one with lower sibling position
if ( ancestor1 ! = ancestor2 ) {
auto * node = ancestor1 ;
while ( node ) {
if ( node = = ancestor2 )
return DOCUMENT_POSITION_PRECEDING ;
node = node - > next_sibling ( ) ;
}
return DOCUMENT_POSITION_FOLLOWING ;
}
it_node1_ancestors + + ;
it_node2_ancestors + + ;
}
// NOTE: If nodes in ancestors chains are the same but one chain is longer, then one node is ancestor of another.
// The node with shorter ancestors chain is the ancestor.
// The node with longer ancestors chain is the descendant.
2022-04-20 00:57:06 +02:00
// 7. If node1 is an ancestor of node2 and attr1 is null, or node1 is node2 and attr2 is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING.
2023-05-24 00:18:35 +03:00
if ( ( node1_ancestors . size ( ) < node2_ancestors . size ( ) & & ! attr1 ) | | ( node1 = = node2 & & attr2 ) )
2021-04-10 17:21:22 -07:00
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING ;
2022-04-20 00:57:06 +02:00
// 8. If node1 is a descendant of node2 and attr2 is null, or node1 is node2 and attr1 is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING.
2023-05-24 00:18:35 +03:00
if ( ( node1_ancestors . size ( ) > node2_ancestors . size ( ) & & ! attr2 ) | | ( node1 = = node2 & & attr1 ) )
2021-04-10 17:21:22 -07:00
return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING ;
2022-04-20 00:57:06 +02:00
// 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING.
2023-05-24 00:18:35 +03:00
if ( node1_ancestors . size ( ) < node2_ancestors . size ( ) )
2021-04-10 17:21:22 -07:00
return DOCUMENT_POSITION_PRECEDING ;
2022-04-20 00:57:06 +02:00
// 10. Return DOCUMENT_POSITION_FOLLOWING.
return DOCUMENT_POSITION_FOLLOWING ;
2021-04-10 17:21:22 -07:00
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
2022-04-01 20:58:27 +03:00
bool Node : : is_host_including_inclusive_ancestor_of ( Node const & other ) const
2021-04-06 19:34:49 +01:00
{
2022-04-20 00:57:06 +02:00
// An object A is a host-including inclusive ancestor of an object B,
// if either A is an inclusive ancestor of B,
2022-03-14 12:46:14 +01:00
if ( is_inclusive_ancestor_of ( other ) )
return true ;
2022-04-20 00:57:06 +02:00
// or if B’ s root has a non-null host and A is a host-including inclusive ancestor of B’ s root’ s host
2022-03-14 12:46:14 +01:00
if ( is < DocumentFragment > ( other . root ( ) )
& & static_cast < DocumentFragment const & > ( other . root ( ) ) . host ( )
& & is_inclusive_ancestor_of ( * static_cast < DocumentFragment const & > ( other . root ( ) ) . host ( ) ) ) {
return true ;
}
return false ;
2021-04-06 19:34:49 +01:00
}
2021-05-02 21:03:50 +01:00
// https://dom.spec.whatwg.org/#dom-node-ownerdocument
2024-11-15 04:01:23 +13:00
GC : : Ptr < Document > Node : : owner_document ( ) const
2021-05-02 21:03:50 +01:00
{
2022-04-20 00:57:06 +02:00
// The ownerDocument getter steps are to return null, if this is a document; otherwise this’ s node document.
2021-05-02 21:03:50 +01:00
if ( is_document ( ) )
return nullptr ;
return m_document ;
}
2021-11-02 19:20:57 +01:00
// This function tells us whether a node is interesting enough to show up
// in the DOM inspector. This hides two things:
// - Non-rendered whitespace
// - Rendered whitespace between block-level elements
bool Node : : is_uninteresting_whitespace_node ( ) const
{
if ( ! is < Text > ( * this ) )
return false ;
2023-09-07 21:36:05 +12:00
if ( ! static_cast < Text const & > ( * this ) . data ( ) . bytes_as_string_view ( ) . is_whitespace ( ) )
2021-11-02 19:20:57 +01:00
return false ;
if ( ! layout_node ( ) )
return true ;
2022-12-18 01:18:19 +00:00
if ( auto parent = layout_node ( ) - > parent ( ) ; parent & & parent - > is_anonymous ( ) )
2021-11-02 19:20:57 +01:00
return true ;
return false ;
}
2021-06-07 16:32:24 +01:00
void Node : : serialize_tree_as_json ( JsonObjectSerializer < StringBuilder > & object ) const
{
2023-09-17 10:51:43 +12:00
MUST ( object . add ( " name " sv , node_name ( ) ) ) ;
2024-10-20 10:37:44 +02:00
MUST ( object . add ( " id " sv , unique_id ( ) . value ( ) ) ) ;
2021-07-01 12:32:31 -04:00
if ( is_document ( ) ) {
2022-07-11 17:32:29 +00:00
MUST ( object . add ( " type " sv , " document " ) ) ;
2021-07-01 12:32:31 -04:00
} else if ( is_element ( ) ) {
2022-07-11 17:32:29 +00:00
MUST ( object . add ( " type " sv , " element " ) ) ;
2021-06-07 16:32:24 +01:00
2021-11-24 19:15:04 +03:00
auto const * element = static_cast < DOM : : Element const * > ( this ) ;
2021-06-07 16:32:24 +01:00
if ( element - > has_attributes ( ) ) {
2022-07-11 17:32:29 +00:00
auto attributes = MUST ( object . add_object ( " attributes " sv ) ) ;
2021-06-07 16:32:24 +01:00
element - > for_each_attribute ( [ & attributes ] ( auto & name , auto & value ) {
2022-02-24 20:08:48 +02:00
MUST ( attributes . add ( name , value ) ) ;
2021-06-07 16:32:24 +01:00
} ) ;
2022-02-24 20:08:48 +02:00
MUST ( attributes . finish ( ) ) ;
2021-06-07 16:32:24 +01:00
}
2021-11-24 19:15:04 +03:00
2022-12-12 12:20:02 +01:00
if ( element - > is_navigable_container ( ) ) {
auto const * container = static_cast < HTML : : NavigableContainer const * > ( element ) ;
2021-11-24 19:15:04 +03:00
if ( auto const * content_document = container - > content_document ( ) ) {
2022-07-11 17:32:29 +00:00
auto children = MUST ( object . add_array ( " children " sv ) ) ;
2022-02-24 20:08:48 +02:00
JsonObjectSerializer < StringBuilder > content_document_object = MUST ( children . add_object ( ) ) ;
2021-11-24 19:15:04 +03:00
content_document - > serialize_tree_as_json ( content_document_object ) ;
2022-02-24 20:08:48 +02:00
MUST ( content_document_object . finish ( ) ) ;
MUST ( children . finish ( ) ) ;
2021-11-24 19:15:04 +03:00
}
}
2024-11-19 18:19:04 +01:00
if ( paintable_box ( ) ) {
if ( paintable_box ( ) - > is_scrollable ( ) ) {
MUST ( object . add ( " scrollable " sv , true ) ) ;
}
if ( ! paintable_box ( ) - > is_visible ( ) ) {
MUST ( object . add ( " invisible " sv , true ) ) ;
}
if ( paintable_box ( ) - > has_stacking_context ( ) ) {
MUST ( object . add ( " stackingContext " sv , true ) ) ;
}
}
2021-06-07 16:32:24 +01:00
} else if ( is_text ( ) ) {
2022-07-11 17:32:29 +00:00
MUST ( object . add ( " type " sv , " text " ) ) ;
2021-06-07 16:32:24 +01:00
2021-06-29 23:11:09 +02:00
auto text_node = static_cast < DOM : : Text const * > ( this ) ;
2022-07-11 17:32:29 +00:00
MUST ( object . add ( " text " sv , text_node - > data ( ) ) ) ;
2021-11-02 19:25:15 +01:00
} else if ( is_comment ( ) ) {
2022-02-24 20:08:48 +02:00
MUST ( object . add ( " type " sv , " comment " sv ) ) ;
MUST ( object . add ( " data " sv , static_cast < DOM : : Comment const & > ( * this ) . data ( ) ) ) ;
2023-03-18 00:19:25 +01:00
} else if ( is_shadow_root ( ) ) {
MUST ( object . add ( " type " sv , " shadow-root " ) ) ;
MUST ( object . add ( " mode " sv , static_cast < DOM : : ShadowRoot const & > ( * this ) . mode ( ) = = Bindings : : ShadowRootMode : : Open ? " open " sv : " closed " sv ) ) ;
2021-06-07 16:32:24 +01:00
}
2022-03-20 19:11:03 +01:00
MUST ( ( object . add ( " visible " sv , ! ! layout_node ( ) ) ) ) ;
2024-08-06 14:28:36 +01:00
auto const * element = is_element ( ) ? static_cast < DOM : : Element const * > ( this ) : nullptr ;
if ( has_child_nodes ( )
| | ( element & & ( element - > is_shadow_host ( ) | | element - > has_pseudo_elements ( ) ) ) ) {
2022-07-11 17:32:29 +00:00
auto children = MUST ( object . add_array ( " children " sv ) ) ;
2023-03-18 00:19:25 +01:00
auto add_child = [ & children ] ( DOM : : Node const & child ) {
2021-11-02 19:20:57 +01:00
if ( child . is_uninteresting_whitespace_node ( ) )
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2022-02-24 20:08:48 +02:00
JsonObjectSerializer < StringBuilder > child_object = MUST ( children . add_object ( ) ) ;
2021-06-07 16:32:24 +01:00
child . serialize_tree_as_json ( child_object ) ;
2022-02-24 20:08:48 +02:00
MUST ( child_object . finish ( ) ) ;
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2023-03-18 00:19:25 +01:00
} ;
for_each_child ( add_child ) ;
2022-03-03 17:50:12 +00:00
2024-08-06 14:28:36 +01:00
if ( element ) {
2023-03-18 00:19:25 +01:00
// Pseudo-elements don't have DOM nodes,so we have to add them separately.
2022-03-03 17:50:12 +00:00
element - > serialize_pseudo_elements_as_json ( children ) ;
2023-03-18 00:19:25 +01:00
if ( element - > is_shadow_host ( ) )
2024-06-25 11:28:58 +02:00
add_child ( * element - > shadow_root ( ) ) ;
2022-03-03 17:50:12 +00:00
}
2022-02-24 20:08:48 +02:00
MUST ( children . finish ( ) ) ;
2021-06-07 16:32:24 +01:00
}
}
2022-03-30 22:28:28 +01:00
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-n-script
2024-10-21 14:24:06 +13:00
// https://whatpr.org/html/9893/webappapis.html#concept-n-script
2022-03-30 22:28:28 +01:00
bool Node : : is_scripting_enabled ( ) const
{
2024-10-21 14:24:06 +13:00
// Scripting is enabled for a node node if node's node document's browsing context is non-null, and scripting is enabled for node's relevant realm.
return document ( ) . browsing_context ( ) & & HTML : : is_scripting_enabled ( HTML : : relevant_realm ( * this ) ) ;
2022-03-30 22:28:28 +01:00
}
2021-07-05 03:59:47 +01:00
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-n-noscript
2024-10-21 14:24:06 +13:00
// https://whatpr.org/html/9893/webappapis.html#concept-n-script
2021-07-05 03:59:47 +01:00
bool Node : : is_scripting_disabled ( ) const
{
2024-10-21 14:24:06 +13:00
// Scripting is disabled for a node when scripting is not enabled, i.e., when its node document's browsing context is null or when scripting is disabled for its relevant realm.
2022-03-30 22:28:28 +01:00
return ! is_scripting_enabled ( ) ;
2021-07-05 03:59:47 +01:00
}
2021-07-05 05:55:02 +01:00
// https://dom.spec.whatwg.org/#dom-node-contains
2024-11-15 04:01:23 +13:00
bool Node : : contains ( GC : : Ptr < Node > other ) const
2021-07-05 05:55:02 +01:00
{
2022-04-20 00:57:06 +02:00
// The contains(other) method steps are to return true if other is an inclusive descendant of this; otherwise false (including when other is null).
2021-07-05 05:55:02 +01:00
return other & & other - > is_inclusive_descendant_of ( * this ) ;
}
2021-09-02 02:17:13 +01:00
// https://dom.spec.whatwg.org/#concept-shadow-including-descendant
bool Node : : is_shadow_including_descendant_of ( Node const & other ) const
{
2022-04-20 00:57:06 +02:00
// An object A is a shadow-including descendant of an object B,
// if A is a descendant of B,
2021-09-02 02:17:13 +01:00
if ( is_descendant_of ( other ) )
return true ;
2022-04-20 00:57:06 +02:00
// or A’ s root is a shadow root
2021-09-02 02:17:13 +01:00
if ( ! is < ShadowRoot > ( root ( ) ) )
return false ;
2022-04-20 00:57:06 +02:00
// and A’ s root’ s host is a shadow-including inclusive descendant of B.
2021-09-02 19:27:42 +01:00
auto & shadow_root = verify_cast < ShadowRoot > ( root ( ) ) ;
2023-12-03 14:34:53 -05:00
return shadow_root . host ( ) & & shadow_root . host ( ) - > is_shadow_including_inclusive_descendant_of ( other ) ;
2021-09-02 02:17:13 +01:00
}
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
bool Node : : is_shadow_including_inclusive_descendant_of ( Node const & other ) const
{
2022-04-20 00:57:06 +02:00
// A shadow-including inclusive descendant is an object or one of its shadow-including descendants.
2021-09-02 02:17:13 +01:00
return & other = = this | | is_shadow_including_descendant_of ( other ) ;
}
// https://dom.spec.whatwg.org/#concept-shadow-including-ancestor
bool Node : : is_shadow_including_ancestor_of ( Node const & other ) const
{
2022-04-20 00:57:06 +02:00
// An object A is a shadow-including ancestor of an object B, if and only if B is a shadow-including descendant of A.
2021-09-02 02:17:13 +01:00
return other . is_shadow_including_descendant_of ( * this ) ;
}
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor
bool Node : : is_shadow_including_inclusive_ancestor_of ( Node const & other ) const
{
2022-04-20 00:57:06 +02:00
// A shadow-including inclusive ancestor is an object or one of its shadow-including ancestors.
2021-09-02 02:17:13 +01:00
return other . is_shadow_including_inclusive_descendant_of ( * this ) ;
}
2021-09-06 01:25:58 +01:00
// https://dom.spec.whatwg.org/#concept-node-replace-all
2024-11-15 04:01:23 +13:00
void Node : : replace_all ( GC : : Ptr < Node > node )
2021-09-06 01:25:58 +01:00
{
2022-07-11 16:39:14 +01:00
// 1. Let removedNodes be parent’ s children.
auto removed_nodes = children_as_vector ( ) ;
// 2. Let addedNodes be the empty set.
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Node > > added_nodes ;
2022-07-11 16:39:14 +01:00
// 3. If node is a DocumentFragment node, then set addedNodes to node’ s children.
if ( node & & is < DocumentFragment > ( * node ) ) {
added_nodes = node - > children_as_vector ( ) ;
}
// 4. Otherwise, if node is non-null, set addedNodes to « node ».
else if ( node ) {
2024-11-15 04:01:23 +13:00
added_nodes . append ( GC : : make_root ( * node ) ) ;
2022-07-11 16:39:14 +01:00
}
2021-09-06 01:25:58 +01:00
2022-04-20 00:57:06 +02:00
// 5. Remove all parent’ s children, in tree order, with the suppress observers flag set.
2021-09-06 01:25:58 +01:00
remove_all_children ( true ) ;
2022-04-20 00:57:06 +02:00
// 6. If node is non-null, then insert node into parent before null with the suppress observers flag set.
2021-09-06 01:25:58 +01:00
if ( node )
insert_before ( * node , nullptr , true ) ;
2022-07-11 16:39:14 +01:00
// 7. If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
2023-02-15 07:44:06 +01:00
if ( ! added_nodes . is_empty ( ) | | ! removed_nodes . is_empty ( ) ) {
2023-05-17 19:47:16 +02:00
queue_tree_mutation_record ( move ( added_nodes ) , move ( removed_nodes ) , nullptr , nullptr ) ;
2023-02-15 07:44:06 +01:00
}
2021-09-06 01:25:58 +01:00
}
// https://dom.spec.whatwg.org/#string-replace-all
2023-12-03 08:24:04 +13:00
void Node : : string_replace_all ( String const & string )
2021-09-06 01:25:58 +01:00
{
2022-04-20 00:57:06 +02:00
// 1. Let node be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > node ;
2021-09-06 01:25:58 +01:00
2022-04-20 00:57:06 +02:00
// 2. If string is not the empty string, then set node to a new Text node whose data is string and node document is parent’ s node document.
2021-09-06 01:25:58 +01:00
if ( ! string . is_empty ( ) )
2024-11-14 05:50:17 +13:00
node = realm ( ) . create < Text > ( document ( ) , string ) ;
2021-09-06 01:25:58 +01:00
2022-04-20 00:57:06 +02:00
// 3. Replace all with node within parent.
2021-09-06 01:25:58 +01:00
replace_all ( node ) ;
}
2024-06-25 10:49:54 +02:00
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-serializing-algorithm-steps
2024-04-09 14:44:58 +02:00
WebIDL : : ExceptionOr < String > Node : : serialize_fragment ( DOMParsing : : RequireWellFormed require_well_formed , FragmentSerializationMode fragment_serialization_mode ) const
2021-09-13 22:42:15 +01:00
{
2022-11-02 19:14:27 -04:00
// 1. Let context document be the value of node's node document.
auto const & context_document = document ( ) ;
2021-09-13 22:42:15 +01:00
2024-06-25 10:49:54 +02:00
// 2. If context document is an HTML document, return the result of HTML fragment serialization algorithm with node, false, and « ».
2022-11-02 19:14:27 -04:00
if ( context_document . is_html_document ( ) )
2024-06-25 10:49:54 +02:00
return HTML : : HTMLParser : : serialize_html_fragment ( * this , HTML : : HTMLParser : : SerializableShadowRoots : : No , { } , fragment_serialization_mode ) ;
2021-09-13 22:42:15 +01:00
2024-06-25 10:49:54 +02:00
// 3. Return the XML serialization of node given require well-formed.
2024-07-13 20:59:16 +02:00
// AD-HOC: XML serialization algorithm returns the "outer" XML serialization of the node.
// For inner, concatenate the serialization of all children.
if ( fragment_serialization_mode = = FragmentSerializationMode : : Inner ) {
StringBuilder markup ;
2024-11-02 02:57:46 +13:00
for ( auto * child = first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
auto child_markup = TRY ( DOMParsing : : serialize_node_to_xml_string ( * child , require_well_formed ) ) ;
2024-07-13 20:59:16 +02:00
markup . append ( child_markup . bytes_as_string_view ( ) ) ;
2024-11-02 02:57:46 +13:00
}
2024-07-13 20:59:16 +02:00
return MUST ( markup . to_string ( ) ) ;
}
2022-11-02 19:14:27 -04:00
return DOMParsing : : serialize_node_to_xml_string ( * this , require_well_formed ) ;
2021-09-13 22:42:15 +01:00
}
2024-06-25 20:55:58 +01:00
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#unsafely-set-html
WebIDL : : ExceptionOr < void > Node : : unsafely_set_html ( Element & context_element , StringView html )
{
// 1. Let newChildren be the result of the HTML fragment parsing algorithm given contextElement, html, and true.
auto new_children = HTML : : HTMLParser : : parse_html_fragment ( context_element , html , HTML : : HTMLParser : : AllowDeclarativeShadowRoots : : Yes ) ;
// 2. Let fragment be a new DocumentFragment whose node document is contextElement’ s node document.
2024-11-14 05:50:17 +13:00
auto fragment = realm ( ) . create < DocumentFragment > ( context_element . document ( ) ) ;
2024-06-25 20:55:58 +01:00
// 3. For each node in newChildren, append node to fragment.
for ( auto & child : new_children )
// I don't know if this can throw here, but let's be safe.
( void ) TRY ( fragment - > append_child ( * child ) ) ;
// 4. Replace all with fragment within contextElement.
replace_all ( fragment ) ;
return { } ;
}
2021-09-13 12:49:23 +02:00
// https://dom.spec.whatwg.org/#dom-node-issamenode
bool Node : : is_same_node ( Node const * other_node ) const
{
2022-04-20 00:57:06 +02:00
// The isSameNode(otherNode) method steps are to return true if otherNode is this; otherwise false.
2021-09-13 12:49:23 +02:00
return this = = other_node ;
}
2021-09-13 12:54:24 +02:00
// https://dom.spec.whatwg.org/#dom-node-isequalnode
bool Node : : is_equal_node ( Node const * other_node ) const
{
// The isEqualNode(otherNode) method steps are to return true if otherNode is non-null and this equals otherNode; otherwise false.
if ( ! other_node )
return false ;
// Fast path for testing a node against itself.
if ( this = = other_node )
return true ;
// A node A equals a node B if all of the following conditions are true:
// A and B implement the same interfaces.
2024-07-18 14:56:29 +01:00
if ( ! node_name ( ) . equals_ignoring_ascii_case ( other_node - > node_name ( ) ) )
2021-09-13 12:54:24 +02:00
return false ;
// The following are equal, switching on the interface A implements:
switch ( node_type ( ) ) {
case ( u16 ) NodeType : : DOCUMENT_TYPE_NODE : {
// Its name, public ID, and system ID.
auto & this_doctype = verify_cast < DocumentType > ( * this ) ;
auto & other_doctype = verify_cast < DocumentType > ( * other_node ) ;
if ( this_doctype . name ( ) ! = other_doctype . name ( )
| | this_doctype . public_id ( ) ! = other_doctype . public_id ( )
| | this_doctype . system_id ( ) ! = other_doctype . system_id ( ) )
return false ;
break ;
}
case ( u16 ) NodeType : : ELEMENT_NODE : {
// Its namespace, namespace prefix, local name, and its attribute list’ s size.
auto & this_element = verify_cast < Element > ( * this ) ;
auto & other_element = verify_cast < Element > ( * other_node ) ;
2023-11-05 13:12:53 +13:00
if ( this_element . namespace_uri ( ) ! = other_element . namespace_uri ( )
2021-09-13 12:54:24 +02:00
| | this_element . prefix ( ) ! = other_element . prefix ( )
| | this_element . local_name ( ) ! = other_element . local_name ( )
| | this_element . attribute_list_size ( ) ! = other_element . attribute_list_size ( ) )
return false ;
// If A is an element, each attribute in its attribute list has an attribute that equals an attribute in B’ s attribute list.
bool has_same_attributes = true ;
2024-07-18 14:17:37 +01:00
this_element . for_each_attribute ( [ & ] ( auto const & attribute ) {
if ( other_element . get_attribute_ns ( attribute . namespace_uri ( ) , attribute . local_name ( ) ) ! = attribute . value ( ) )
2021-09-13 12:54:24 +02:00
has_same_attributes = false ;
} ) ;
if ( ! has_same_attributes )
return false ;
break ;
}
case ( u16 ) NodeType : : COMMENT_NODE :
case ( u16 ) NodeType : : TEXT_NODE : {
// Its data.
auto & this_cdata = verify_cast < CharacterData > ( * this ) ;
auto & other_cdata = verify_cast < CharacterData > ( * other_node ) ;
if ( this_cdata . data ( ) ! = other_cdata . data ( ) )
return false ;
break ;
}
2022-12-13 13:09:09 +01:00
case ( u16 ) NodeType : : ATTRIBUTE_NODE : {
// Its namespace, local name, and value.
auto & this_attr = verify_cast < Attr > ( * this ) ;
auto & other_attr = verify_cast < Attr > ( * other_node ) ;
if ( this_attr . namespace_uri ( ) ! = other_attr . namespace_uri ( ) )
return false ;
if ( this_attr . local_name ( ) ! = other_attr . local_name ( ) )
return false ;
if ( this_attr . value ( ) ! = other_attr . value ( ) )
return false ;
break ;
}
2022-12-13 13:25:26 +01:00
case ( u16 ) NodeType : : PROCESSING_INSTRUCTION_NODE : {
// Its target and data.
auto & this_processing_instruction = verify_cast < ProcessingInstruction > ( * this ) ;
auto & other_processing_instruction = verify_cast < ProcessingInstruction > ( * other_node ) ;
if ( this_processing_instruction . target ( ) ! = other_processing_instruction . target ( ) )
return false ;
if ( this_processing_instruction . data ( ) ! = other_processing_instruction . data ( ) )
return false ;
break ;
}
2021-09-13 12:54:24 +02:00
default :
break ;
}
// A and B have the same number of children.
size_t this_child_count = child_count ( ) ;
size_t other_child_count = other_node - > child_count ( ) ;
if ( this_child_count ! = other_child_count )
return false ;
// Each child of A equals the child of B at the identical index.
// FIXME: This can be made nicer. child_at_index() is O(n).
for ( size_t i = 0 ; i < this_child_count ; + + i ) {
auto * this_child = child_at_index ( i ) ;
auto * other_child = other_node - > child_at_index ( i ) ;
VERIFY ( this_child ) ;
VERIFY ( other_child ) ;
if ( ! this_child - > is_equal_node ( other_child ) )
return false ;
}
return true ;
}
2024-07-14 22:50:38 +01:00
// https://dom.spec.whatwg.org/#locate-a-namespace
Optional < String > Node : : locate_a_namespace ( Optional < String > const & prefix ) const
{
// To locate a namespace for a node using prefix, switch on the interface node implements:
// Element
if ( is < Element > ( * this ) ) {
// 1. If prefix is "xml", then return the XML namespace.
if ( prefix = = " xml " )
return Web : : Namespace : : XML . to_string ( ) ;
// 2. If prefix is "xmlns", then return the XMLNS namespace.
if ( prefix = = " xmlns " )
return Web : : Namespace : : XMLNS . to_string ( ) ;
// 3. If its namespace is non-null and its namespace prefix is prefix, then return namespace.
auto & element = verify_cast < Element > ( * this ) ;
if ( element . namespace_uri ( ) . has_value ( ) & & element . prefix ( ) = = prefix )
return element . namespace_uri ( ) - > to_string ( ) ;
// 4. If it has an attribute whose namespace is the XMLNS namespace, namespace prefix is "xmlns", and local name is prefix,
// or if prefix is null and it has an attribute whose namespace is the XMLNS namespace, namespace prefix is null,
// and local name is "xmlns", then return its value if it is not the empty string, and null otherwise.
if ( auto * attributes = element . attributes ( ) ) {
for ( size_t i = 0 ; i < attributes - > length ( ) ; + + i ) {
auto & attr = * attributes - > item ( i ) ;
if ( attr . namespace_uri ( ) = = Web : : Namespace : : XMLNS ) {
if ( ( attr . prefix ( ) = = " xmlns " & & attr . local_name ( ) = = prefix ) | | ( ! prefix . has_value ( ) & & ! attr . prefix ( ) . has_value ( ) & & attr . local_name ( ) = = " xmlns " ) ) {
auto value = attr . value ( ) ;
if ( ! value . is_empty ( ) )
return value ;
return { } ;
}
}
}
}
// 5. If its parent element is null, then return null.
auto * parent_element = element . parent_element ( ) ;
if ( ! element . parent_element ( ) )
return { } ;
// 6. Return the result of running locate a namespace on its parent element using prefix.
return parent_element - > locate_a_namespace ( prefix ) ;
}
// Document
if ( is < Document > ( * this ) ) {
// 1. If its document element is null, then return null.
auto * document_element = verify_cast < Document > ( * this ) . document_element ( ) ;
if ( ! document_element )
return { } ;
// 2. Return the result of running locate a namespace on its document element using prefix.
return document_element - > locate_a_namespace ( prefix ) ;
}
// DocumentType
// DocumentFragment
if ( is < DocumentType > ( * this ) | | is < DocumentFragment > ( * this ) ) {
// Return null.
return { } ;
}
// Attr
if ( is < Attr > ( * this ) ) {
// 1. If its element is null, then return null.
auto * element = verify_cast < Attr > ( * this ) . owner_element ( ) ;
if ( ! element )
return { } ;
// 2. Return the result of running locate a namespace on its element using prefix.
return element - > locate_a_namespace ( prefix ) ;
}
// Otherwise
// 1. If its parent element is null, then return null.
auto * parent_element = this - > parent_element ( ) ;
if ( ! parent_element )
return { } ;
// 2. Return the result of running locate a namespace on its parent element using prefix.
return parent_element - > locate_a_namespace ( prefix ) ;
}
// https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
Optional < String > Node : : lookup_namespace_uri ( Optional < String > prefix ) const
{
// 1. If prefix is the empty string, then set it to null.
if ( prefix . has_value ( ) & & prefix - > is_empty ( ) )
prefix = { } ;
// 2. Return the result of running locate a namespace for this using prefix.
return locate_a_namespace ( prefix ) ;
}
2024-07-27 12:51:36 -06:00
// https://dom.spec.whatwg.org/#dom-node-lookupprefix
Optional < String > Node : : lookup_prefix ( Optional < String > namespace_ ) const
{
// 1. If namespace is null or the empty string, then return null.
if ( ! namespace_ . has_value ( ) | | namespace_ - > is_empty ( ) )
return { } ;
// 2. Switch on the interface this implements:
// Element
if ( is < Element > ( * this ) ) {
// Return the result of locating a namespace prefix for it using namespace.
auto & element = verify_cast < Element > ( * this ) ;
return element . locate_a_namespace_prefix ( namespace_ ) ;
}
// Document
if ( is < Document > ( * this ) ) {
// Return the result of locating a namespace prefix for its document element, if its document element is non-null; otherwise null.
auto * document_element = verify_cast < Document > ( * this ) . document_element ( ) ;
if ( ! document_element )
return { } ;
return document_element - > locate_a_namespace_prefix ( namespace_ ) ;
}
// DocumentType
// DocumentFragment
if ( is < DocumentType > ( * this ) | | is < DocumentFragment > ( * this ) )
// Return null
return { } ;
// Attr
if ( is < Attr > ( * this ) ) {
// Return the result of locating a namespace prefix for its element, if its element is non-null; otherwise null.
auto * element = verify_cast < Attr > ( * this ) . owner_element ( ) ;
if ( ! element )
return { } ;
return element - > locate_a_namespace_prefix ( namespace_ ) ;
}
// Otherwise
// Return the result of locating a namespace prefix for its parent element, if its parent element is non-null; otherwise null.
auto * parent_element = this - > parent_element ( ) ;
if ( ! parent_element )
return { } ;
return parent_element - > locate_a_namespace_prefix ( namespace_ ) ;
}
2024-07-17 14:42:45 +01:00
// https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
bool Node : : is_default_namespace ( Optional < String > namespace_ ) const
{
// 1. If namespace is the empty string, then set it to null.
if ( namespace_ . has_value ( ) & & namespace_ - > is_empty ( ) )
namespace_ = { } ;
// 2. Let defaultNamespace be the result of running locate a namespace for this using null.
auto default_namespace = locate_a_namespace ( { } ) ;
// 3. Return true if defaultNamespace is the same as namespace; otherwise false.
return default_namespace = = namespace_ ;
}
2021-09-29 22:23:28 +02:00
// https://dom.spec.whatwg.org/#in-a-document-tree
bool Node : : in_a_document_tree ( ) const
{
// An element is in a document tree if its root is a document.
return root ( ) . is_document ( ) ;
}
2021-10-16 03:04:55 +01:00
// https://dom.spec.whatwg.org/#dom-node-getrootnode
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > Node : : get_root_node ( GetRootNodeOptions const & options )
2021-10-16 03:04:55 +01:00
{
2022-04-20 00:57:06 +02:00
// The getRootNode(options) method steps are to return this’ s shadow-including root if options["composed"] is true;
2021-10-16 03:04:55 +01:00
if ( options . composed )
return shadow_including_root ( ) ;
2022-04-20 00:57:06 +02:00
// otherwise this’ s root.
2021-10-16 03:04:55 +01:00
return root ( ) ;
}
2023-12-03 08:24:04 +13:00
String Node : : debug_description ( ) const
2022-02-26 08:18:14 +01:00
{
StringBuilder builder ;
2023-09-17 10:51:43 +12:00
builder . append ( node_name ( ) . to_deprecated_fly_string ( ) . to_lowercase ( ) ) ;
2022-02-26 08:18:14 +01:00
if ( is_element ( ) ) {
2023-12-03 08:24:04 +13:00
auto const & element = static_cast < DOM : : Element const & > ( * this ) ;
2024-01-13 20:12:25 +13:00
if ( element . id ( ) . has_value ( ) )
builder . appendff ( " #{} " , element . id ( ) . value ( ) ) ;
2022-02-26 08:18:14 +01:00
for ( auto const & class_name : element . class_names ( ) )
builder . appendff ( " .{} " , class_name ) ;
}
2023-12-03 08:24:04 +13:00
return MUST ( builder . to_string ( ) ) ;
2022-02-26 08:18:14 +01:00
}
2022-01-31 18:05:54 +00:00
// https://dom.spec.whatwg.org/#concept-node-length
size_t Node : : length ( ) const
{
// 1. If node is a DocumentType or Attr node, then return 0.
if ( is_document_type ( ) | | is_attribute ( ) )
return 0 ;
// 2. If node is a CharacterData node, then return node’ s data’ s length.
2023-12-22 20:41:34 +13:00
if ( is_character_data ( ) )
return verify_cast < CharacterData > ( * this ) . length_in_utf16_code_units ( ) ;
2022-01-31 18:05:54 +00:00
// 3. Return the number of node’ s children.
return child_count ( ) ;
}
2024-11-15 04:01:23 +13:00
void Node : : set_paintable ( GC : : Ptr < Painting : : Paintable > paintable )
2023-08-19 12:00:42 +02:00
{
m_paintable = paintable ;
}
2024-10-14 16:07:56 +02:00
void Node : : clear_paintable ( )
{
m_paintable = nullptr ;
}
2022-03-10 22:46:35 +01:00
Painting : : Paintable const * Node : : paintable ( ) const
{
2023-08-19 12:00:42 +02:00
return m_paintable ;
2023-08-19 09:29:04 +02:00
}
Painting : : Paintable * Node : : paintable ( )
{
2023-08-19 12:00:42 +02:00
return m_paintable ;
2022-03-10 22:46:35 +01:00
}
2023-04-20 16:01:16 +01:00
Painting : : PaintableBox const * Node : : paintable_box ( ) const
2022-03-09 23:53:41 +01:00
{
2024-01-13 12:33:06 +01:00
if ( paintable ( ) & & paintable ( ) - > is_paintable_box ( ) )
return static_cast < Painting : : PaintableBox const * > ( paintable ( ) ) ;
return nullptr ;
2022-03-09 23:53:41 +01:00
}
2023-08-07 00:59:23 +02:00
Painting : : PaintableBox * Node : : paintable_box ( )
{
2024-01-13 12:33:06 +01:00
if ( paintable ( ) & & paintable ( ) - > is_paintable_box ( ) )
return static_cast < Painting : : PaintableBox * > ( paintable ( ) ) ;
return nullptr ;
2023-08-07 00:59:23 +02:00
}
2022-07-11 16:39:14 +01:00
// https://dom.spec.whatwg.org/#queue-a-mutation-record
2024-11-15 04:01:23 +13:00
void Node : : queue_mutation_record ( FlyString const & type , Optional < FlyString > const & attribute_name , Optional < FlyString > const & attribute_namespace , Optional < String > const & old_value , Vector < GC : : Root < Node > > added_nodes , Vector < GC : : Root < Node > > removed_nodes , Node * previous_sibling , Node * next_sibling ) const
2022-07-11 16:39:14 +01:00
{
2022-09-01 17:59:48 +02:00
// NOTE: We defer garbage collection until the end of the scope, since we can't safely use MutationObserver* as a hashmap key otherwise.
// FIXME: This is a total hack.
2024-11-15 04:01:23 +13:00
GC : : DeferGC defer_gc ( heap ( ) ) ;
2022-09-01 17:59:48 +02:00
2022-07-11 16:39:14 +01:00
// 1. Let interestedObservers be an empty map.
// mutationObserver -> mappedOldValue
2023-11-19 18:10:36 +13:00
OrderedHashMap < MutationObserver * , Optional < String > > interested_observers ;
2022-07-11 16:39:14 +01:00
// 2. Let nodes be the inclusive ancestors of target.
// 3. For each node in nodes, and then for each registered of node’ s registered observer list:
2023-11-02 07:48:51 +01:00
for ( auto * node = this ; node ; node = node - > parent ( ) ) {
2023-11-18 11:22:51 +01:00
if ( ! node - > m_registered_observer_list )
continue ;
for ( auto & registered_observer : * node - > m_registered_observer_list ) {
2022-07-11 16:39:14 +01:00
// 1. Let options be registered’ s options.
2023-02-26 16:09:02 -07:00
auto & options = registered_observer - > options ( ) ;
2022-07-11 16:39:14 +01:00
// 2. If none of the following are true
// - node is not target and options["subtree"] is false
// - type is "attributes" and options["attributes"] either does not exist or is false
// - type is "attributes", options["attributeFilter"] exists, and options["attributeFilter"] does not contain name or namespace is non-null
// - type is "characterData" and options["characterData"] either does not exist or is false
// - type is "childList" and options["childList"] is false
// then:
2023-11-02 07:48:51 +01:00
if ( ! ( node ! = this & & ! options . subtree )
2022-07-11 16:39:14 +01:00
& & ! ( type = = MutationType : : attributes & & ( ! options . attributes . has_value ( ) | | ! options . attributes . value ( ) ) )
2023-11-19 18:10:36 +13:00
& & ! ( type = = MutationType : : attributes & & options . attribute_filter . has_value ( ) & & ( attribute_namespace . has_value ( ) | | ! options . attribute_filter - > contains_slow ( attribute_name . value_or ( String { } ) ) ) )
2022-07-11 16:39:14 +01:00
& & ! ( type = = MutationType : : characterData & & ( ! options . character_data . has_value ( ) | | ! options . character_data . value ( ) ) )
& & ! ( type = = MutationType : : childList & & ! options . child_list ) ) {
// 1. Let mo be registered’ s observer.
2023-02-26 16:09:02 -07:00
auto mutation_observer = registered_observer - > observer ( ) ;
2022-07-11 16:39:14 +01:00
// 2. If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
if ( ! interested_observers . contains ( mutation_observer ) )
interested_observers . set ( mutation_observer , { } ) ;
// 3. If either type is "attributes" and options["attributeOldValue"] is true, or type is "characterData" and options["characterDataOldValue"] is true, then set interestedObservers[mo] to oldValue.
if ( ( type = = MutationType : : attributes & & options . attribute_old_value . has_value ( ) & & options . attribute_old_value . value ( ) ) | | ( type = = MutationType : : characterData & & options . character_data_old_value . has_value ( ) & & options . character_data_old_value . value ( ) ) )
interested_observers . set ( mutation_observer , old_value ) ;
}
}
}
2023-05-17 19:47:16 +02:00
// OPTIMIZATION: If there are no interested observers, bail without doing any more work.
if ( interested_observers . is_empty ( ) )
return ;
2023-08-13 13:05:26 +02:00
auto added_nodes_list = StaticNodeList : : create ( realm ( ) , move ( added_nodes ) ) ;
auto removed_nodes_list = StaticNodeList : : create ( realm ( ) , move ( removed_nodes ) ) ;
2023-05-17 19:47:16 +02:00
2022-07-11 16:39:14 +01:00
// 4. For each observer → mappedOldValue of interestedObservers:
for ( auto & interested_observer : interested_observers ) {
2023-11-19 18:10:36 +13:00
// FIXME: The MutationRecord constructor shuld take an Optional<FlyString> attribute name and namespace
Optional < String > string_attribute_name ;
if ( attribute_name . has_value ( ) )
string_attribute_name = attribute_name - > to_string ( ) ;
Optional < String > string_attribute_namespace ;
if ( attribute_namespace . has_value ( ) )
string_attribute_name = attribute_namespace - > to_string ( ) ;
2022-07-11 16:39:14 +01:00
// 1. Let record be a new MutationRecord object with its type set to type, target set to target, attributeName set to name, attributeNamespace set to namespace, oldValue set to mappedOldValue,
// addedNodes set to addedNodes, removedNodes set to removedNodes, previousSibling set to previousSibling, and nextSibling set to nextSibling.
2023-11-19 18:10:36 +13:00
auto record = MutationRecord : : create ( realm ( ) , type , * this , added_nodes_list , removed_nodes_list , previous_sibling , next_sibling , string_attribute_name , string_attribute_namespace , /* mappedOldValue */ interested_observer . value ) ;
2022-07-11 16:39:14 +01:00
// 2. Enqueue record to observer’ s record queue.
interested_observer . key - > enqueue_record ( { } , move ( record ) ) ;
}
// 5. Queue a mutation observer microtask.
Bindings : : queue_mutation_observer_microtask ( document ( ) ) ;
}
// https://dom.spec.whatwg.org/#queue-a-tree-mutation-record
2024-11-15 04:01:23 +13:00
void Node : : queue_tree_mutation_record ( Vector < GC : : Root < Node > > added_nodes , Vector < GC : : Root < Node > > removed_nodes , Node * previous_sibling , Node * next_sibling )
2022-07-11 16:39:14 +01:00
{
// 1. Assert: either addedNodes or removedNodes is not empty.
2023-05-17 19:47:16 +02:00
VERIFY ( added_nodes . size ( ) > 0 | | removed_nodes . size ( ) > 0 ) ;
2022-07-11 16:39:14 +01:00
// 2. Queue a mutation record of "childList" for target with null, null, null, addedNodes, removedNodes, previousSibling, and nextSibling.
queue_mutation_record ( MutationType : : childList , { } , { } , { } , move ( added_nodes ) , move ( removed_nodes ) , previous_sibling , next_sibling ) ;
}
2024-11-15 04:01:23 +13:00
void Node : : append_child_impl ( GC : : Ref < Node > node )
2022-08-28 13:42:07 +02:00
{
VERIFY ( ! node - > m_parent ) ;
if ( ! is_child_allowed ( * node ) )
return ;
if ( m_last_child )
m_last_child - > m_next_sibling = node . ptr ( ) ;
node - > m_previous_sibling = m_last_child ;
node - > m_parent = this ;
m_last_child = node . ptr ( ) ;
if ( ! m_first_child )
m_first_child = m_last_child ;
}
2024-11-15 04:01:23 +13:00
void Node : : insert_before_impl ( GC : : Ref < Node > node , GC : : Ptr < Node > child )
2022-08-28 13:42:07 +02:00
{
if ( ! child )
return append_child_impl ( move ( node ) ) ;
VERIFY ( ! node - > m_parent ) ;
VERIFY ( child - > parent ( ) = = this ) ;
node - > m_previous_sibling = child - > m_previous_sibling ;
node - > m_next_sibling = child ;
if ( child - > m_previous_sibling )
child - > m_previous_sibling - > m_next_sibling = node ;
if ( m_first_child = = child )
m_first_child = node ;
child - > m_previous_sibling = node ;
node - > m_parent = this ;
}
2024-11-15 04:01:23 +13:00
void Node : : remove_child_impl ( GC : : Ref < Node > node )
2022-08-28 13:42:07 +02:00
{
VERIFY ( node - > m_parent . ptr ( ) = = this ) ;
if ( m_first_child = = node )
m_first_child = node - > m_next_sibling ;
if ( m_last_child = = node )
m_last_child = node - > m_previous_sibling ;
if ( node - > m_next_sibling )
node - > m_next_sibling - > m_previous_sibling = node - > m_previous_sibling ;
if ( node - > m_previous_sibling )
node - > m_previous_sibling - > m_next_sibling = node - > m_next_sibling ;
node - > m_next_sibling = nullptr ;
node - > m_previous_sibling = nullptr ;
node - > m_parent = nullptr ;
}
bool Node : : is_ancestor_of ( Node const & other ) const
{
for ( auto * ancestor = other . parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
if ( ancestor = = this )
return true ;
}
return false ;
}
bool Node : : is_inclusive_ancestor_of ( Node const & other ) const
{
return & other = = this | | is_ancestor_of ( other ) ;
}
bool Node : : is_descendant_of ( Node const & other ) const
{
return other . is_ancestor_of ( * this ) ;
}
bool Node : : is_inclusive_descendant_of ( Node const & other ) const
{
return other . is_inclusive_ancestor_of ( * this ) ;
}
// https://dom.spec.whatwg.org/#concept-tree-following
bool Node : : is_following ( Node const & other ) const
{
// An object A is following an object B if A and B are in the same tree and A comes after B in tree order.
for ( auto * node = previous_in_pre_order ( ) ; node ; node = node - > previous_in_pre_order ( ) ) {
if ( node = = & other )
return true ;
}
return false ;
}
2023-02-25 10:44:51 -07:00
void Node : : build_accessibility_tree ( AccessibilityTreeNode & parent )
2022-12-11 10:56:37 -06:00
{
if ( is_uninteresting_whitespace_node ( ) )
return ;
if ( is_document ( ) ) {
2023-02-25 10:44:51 -07:00
auto * document = static_cast < DOM : : Document * > ( this ) ;
auto * document_element = document - > document_element ( ) ;
2023-06-26 22:41:25 +01:00
if ( document_element & & document_element - > include_in_accessibility_tree ( ) ) {
2022-12-11 10:56:37 -06:00
parent . set_value ( document_element ) ;
if ( document_element - > has_child_nodes ( ) )
2023-02-25 10:44:51 -07:00
document_element - > for_each_child ( [ & parent ] ( DOM : : Node & child ) {
2022-12-11 10:56:37 -06:00
child . build_accessibility_tree ( parent ) ;
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2022-12-11 10:56:37 -06:00
} ) ;
}
} else if ( is_element ( ) ) {
auto const * element = static_cast < DOM : : Element const * > ( this ) ;
if ( is < HTML : : HTMLScriptElement > ( element ) | | is < HTML : : HTMLStyleElement > ( element ) )
return ;
if ( element - > include_in_accessibility_tree ( ) ) {
2023-08-13 13:05:26 +02:00
auto current_node = AccessibilityTreeNode : : create ( & document ( ) , this ) ;
2022-12-11 10:56:37 -06:00
parent . append_child ( current_node ) ;
if ( has_child_nodes ( ) ) {
for_each_child ( [ & current_node ] ( DOM : : Node & child ) {
child . build_accessibility_tree ( * current_node ) ;
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2022-12-11 10:56:37 -06:00
} ) ;
}
} else if ( has_child_nodes ( ) ) {
for_each_child ( [ & parent ] ( DOM : : Node & child ) {
child . build_accessibility_tree ( parent ) ;
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2022-12-11 10:56:37 -06:00
} ) ;
}
} else if ( is_text ( ) ) {
2023-08-13 13:05:26 +02:00
parent . append_child ( AccessibilityTreeNode : : create ( & document ( ) , this ) ) ;
2022-12-11 10:56:37 -06:00
if ( has_child_nodes ( ) ) {
for_each_child ( [ & parent ] ( DOM : : Node & child ) {
child . build_accessibility_tree ( parent ) ;
2024-05-04 14:59:52 +01:00
return IterationDecision : : Continue ;
2022-12-11 10:56:37 -06:00
} ) ;
}
}
}
2023-02-05 11:21:59 -06:00
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
2024-11-01 21:11:32 +09:00
ErrorOr < String > Node : : name_or_description ( NameOrDescription target , Document const & document , HashTable < UniqueNodeID > & visited_nodes , IsDescendant is_descendant ) const
2023-02-05 11:21:59 -06:00
{
// The text alternative for a given element is computed as follows:
2024-12-05 08:49:29 -05:00
// 1. Set the root node to the given element, the current node to the root node, and the total accumulated text to the
// empty string (""). If the root node's role prohibits naming, return the empty string ("").
2023-02-05 11:21:59 -06:00
auto const * root_node = this ;
auto const * current_node = root_node ;
StringBuilder total_accumulated_text ;
2023-11-02 14:30:00 +01:00
visited_nodes . set ( unique_id ( ) ) ;
2023-02-05 11:21:59 -06:00
if ( is_element ( ) ) {
auto const * element = static_cast < DOM : : Element const * > ( this ) ;
2024-11-07 20:33:43 +09:00
auto role = element - > role_or_default ( ) ;
2023-02-05 11:21:59 -06:00
// 2. Compute the text alternative for the current node:
2024-11-11 16:21:05 +09:00
// A. Hidden Not Referenced: If the current node is hidden and is:
// i. Not part of an aria-labelledby or aria-describedby traversal, where the node directly referenced by that
2024-12-05 08:49:29 -05:00
// relation was hidden.
2024-11-11 16:21:05 +09:00
// ii. Nor part of a native host language text alternative element (e.g. label in HTML) or attribute traversal,
2024-12-05 08:49:29 -05:00
// where the root of that traversal was hidden.
2024-11-11 16:21:05 +09:00
// Return the empty string.
2024-12-05 08:49:29 -05:00
//
// NOTE: Nodes with CSS properties display:none, visibility:hidden, visibility:collapse or content-visibility:hidden:
// They are considered hidden, as they match the guidelines "not perceivable" and "explicitly hidden".
2024-11-11 16:21:05 +09:00
//
// AD-HOC: We don’ t implement this step here — because strictly implementing this would cause us to return early
// whenever encountering a node (element, actually) that “is hidden and is not directly referenced by
// aria-labelledby or aria-describedby”, without traversing down through that element’ s subtree to see if it has
// (1) any descendant elements that are directly referenced and/or (2) any un-hidden nodes. So we instead (in
// substep G below) traverse upward through ancestor nodes of every text node, and check in that way to do the
// equivalent of what this step seems to have been intended to do.
// https://github.com/w3c/aria/issues/2387
2023-02-05 11:21:59 -06:00
// B. Otherwise:
2024-12-05 08:49:29 -05:00
// - if computing a name, and the current node has an aria-labelledby attribute that contains at least one valid
// IDREF, and the current node is not already part of an aria-labelledby traversal, process its IDREFs in the
// order they occur:
// - or, if computing a description, and the current node has an aria-describedby attribute that contains at least
// one valid IDREF, and the current node is not already part of an aria-describedby traversal, process its IDREFs
// in the order they occur:
2023-10-06 07:43:52 +13:00
auto aria_labelled_by = element - > aria_labelled_by ( ) ;
auto aria_described_by = element - > aria_described_by ( ) ;
2024-12-05 08:49:29 -05:00
2024-04-05 09:26:03 +02:00
if ( ( target = = NameOrDescription : : Name & & aria_labelled_by . has_value ( ) & & Node : : first_valid_id ( * aria_labelled_by , document ) . has_value ( ) )
| | ( target = = NameOrDescription : : Description & & aria_described_by . has_value ( ) & & Node : : first_valid_id ( * aria_described_by , document ) . has_value ( ) ) ) {
2023-02-05 11:21:59 -06:00
// i. Set the accumulated text to the empty string.
total_accumulated_text . clear ( ) ;
Vector < StringView > id_list ;
if ( target = = NameOrDescription : : Name ) {
2023-10-06 07:43:52 +13:00
id_list = aria_labelled_by - > bytes_as_string_view ( ) . split_view_if ( Infra : : is_ascii_whitespace ) ;
2023-02-05 11:21:59 -06:00
} else {
2023-10-06 07:43:52 +13:00
id_list = aria_described_by - > bytes_as_string_view ( ) . split_view_if ( Infra : : is_ascii_whitespace ) ;
2023-02-05 11:21:59 -06:00
}
2024-12-05 08:49:29 -05:00
2023-02-05 11:21:59 -06:00
// ii. For each IDREF:
for ( auto const & id_ref : id_list ) {
2023-10-08 13:34:11 +13:00
auto node = document . get_element_by_id ( MUST ( FlyString : : from_utf8 ( id_ref ) ) ) ;
2023-02-05 11:21:59 -06:00
if ( ! node )
continue ;
2024-11-16 18:02:10 +09:00
// AD-HOC: The “For each IDREF” substep in the spec doesn’ t seem to explicitly require the following
// check for an aria-label value; but the “div group explicitly labelledby self and heading” subtest at
// https://wpt.fyi/results/accname/name/comp_labelledby.html won’ t pass unless we do this check.
2024-11-11 16:21:05 +09:00
// https://github.com/w3c/aria/issues/2388
2024-11-16 18:02:10 +09:00
if ( target = = NameOrDescription : : Name & & node - > aria_label ( ) . has_value ( ) & & ! node - > aria_label ( ) - > is_empty ( ) & & ! node - > aria_label ( ) - > bytes_as_string_view ( ) . is_whitespace ( ) ) {
total_accumulated_text . append ( ' ' ) ;
total_accumulated_text . append ( node - > aria_label ( ) . value ( ) ) ;
}
2023-11-02 14:30:00 +01:00
if ( visited_nodes . contains ( node - > unique_id ( ) ) )
2023-02-05 11:21:59 -06:00
continue ;
2024-12-05 08:49:29 -05:00
2023-02-05 11:21:59 -06:00
// a. Set the current node to the node referenced by the IDREF.
current_node = node ;
// b. Compute the text alternative of the current node beginning with step 2. Set the result to that text alternative.
auto result = TRY ( node - > name_or_description ( target , document , visited_nodes ) ) ;
// c. Append the result, with a space, to the accumulated text.
2024-11-06 17:40:55 +09:00
total_accumulated_text . append ( ' ' ) ;
total_accumulated_text . append ( result ) ;
2023-02-05 11:21:59 -06:00
}
2024-12-05 08:49:29 -05:00
2023-02-05 11:21:59 -06:00
// iii. Return the accumulated text.
2024-11-11 16:21:05 +09:00
// AD-HOC: This substep in the spec doesn’ t seem to explicitly require the following check for an aria-label
// value; but the “button's hidden referenced name (visibility:hidden) with hidden aria-labelledby traversal
// falls back to aria-label” subtest at https://wpt.fyi/results/accname/name/comp_labelledby.html won’ t pass
// unless we do this check.
// https://github.com/w3c/aria/issues/2388
if ( total_accumulated_text . string_view ( ) . is_whitespace ( ) & & target = = NameOrDescription : : Name & & element - > aria_label ( ) . has_value ( ) & & ! element - > aria_label ( ) - > is_empty ( ) & & ! element - > aria_label ( ) - > bytes_as_string_view ( ) . is_whitespace ( ) )
return element - > aria_label ( ) . release_value ( ) ;
2023-02-05 11:21:59 -06:00
return total_accumulated_text . to_string ( ) ;
}
2024-11-18 04:20:24 +09:00
// D. AriaLabel: Otherwise, if the current node has an aria-label attribute whose value is not undefined, not
2024-12-05 08:49:29 -05:00
// the empty string, nor, when trimmed of whitespace, is not the empty string:
//
2024-11-18 04:20:24 +09:00
// AD-HOC: We’ ve reordered substeps C and D from https://w3c.github.io/accname/#step2 — because
// the more-specific per-HTML-element requirements at https://w3c.github.io/html-aam/#accname-computation
// necessitate doing so, and the “input with label for association is superceded by aria-label” subtest at
// https://wpt.fyi/results/accname/name/comp_label.html won’ t pass unless we do this reordering.
// Spec PR: https://github.com/w3c/aria/pull/2377
if ( target = = NameOrDescription : : Name & & element - > aria_label ( ) . has_value ( ) & & ! element - > aria_label ( ) - > is_empty ( ) & & ! element - > aria_label ( ) - > bytes_as_string_view ( ) . is_whitespace ( ) ) {
// TODO: - If traversal of the current node is due to recursion and the current node is an embedded control as defined in step 2E, ignore aria-label and skip to rule 2E.
// https://github.com/w3c/aria/pull/2385 and https://github.com/w3c/accname/issues/173
if ( ! element - > is_html_slot_element ( ) )
return element - > aria_label ( ) . value ( ) ;
}
// C. Embedded Control: Otherwise, if the current node is a control embedded within the label (e.g. any element
2024-12-05 08:49:29 -05:00
// directly referenced by aria-labelledby) for another widget, where the user can adjust the embedded control's
// value, then return the embedded control as part of the text alternative in the following manner:
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : NodeList > labels ;
2024-10-25 10:47:06 +09:00
if ( is < HTML : : HTMLElement > ( this ) )
labels = ( const_cast < HTML : : HTMLElement & > ( static_cast < HTML : : HTMLElement const & > ( * current_node ) ) ) . labels ( ) ;
if ( labels ! = nullptr & & labels - > length ( ) > 0 ) {
StringBuilder builder ;
for ( u32 i = 0 ; i < labels - > length ( ) ; i + + ) {
auto nodes = labels - > item ( i ) - > children_as_vector ( ) ;
for ( auto const & node : nodes ) {
2024-11-19 21:14:36 +09:00
// AD-HOC: https://wpt.fyi/results/accname/name/comp_host_language_label.html has “encapsulation”
// tests, from which can be induced a requirement that when computing the accessible name for a
// <label>-ed form control (“embedded control”), then any content (text content or attribute values)
// from the control itself that would otherwise be included in the accessible-name computation for
// it ancestor <label> must instead be skipped and not included. The HTML-AAM spec seems to maybe
// be trying to achieve that result by expressing specific steps for each particular type of form
// control. But what all that reduces/optimizes/simplifies down to is just, “skip over self”.
2024-11-11 16:21:05 +09:00
// https://github.com/w3c/aria/issues/2389
2024-11-19 21:14:36 +09:00
if ( node = = this )
continue ;
2024-12-05 08:49:29 -05:00
2024-10-25 10:47:06 +09:00
if ( node - > is_element ( ) ) {
auto const & element = static_cast < DOM : : Element const & > ( * node ) ;
auto role = element . role_or_default ( ) ;
2024-12-05 08:49:29 -05:00
2024-10-25 10:47:06 +09:00
if ( role = = ARIA : : Role : : textbox ) {
// i. Textbox: If the embedded control has role textbox, return its value.
if ( is < HTML : : HTMLInputElement > ( * node ) ) {
auto const & element = static_cast < HTML : : HTMLInputElement const & > ( * node ) ;
2024-12-04 19:06:47 +09:00
if ( element . has_attribute ( HTML : : AttributeNames : : value ) )
2024-10-25 10:47:06 +09:00
builder . append ( element . value ( ) ) ;
} else
builder . append ( node - > text_content ( ) . value ( ) ) ;
} else if ( role = = ARIA : : Role : : combobox ) {
2024-12-05 08:49:29 -05:00
// ii. Combobox/Listbox: If the embedded control has role combobox or listbox, return the text
// alternative of the chosen option.
2024-10-25 10:47:06 +09:00
if ( is < HTML : : HTMLInputElement > ( * node ) ) {
auto const & element = static_cast < HTML : : HTMLInputElement const & > ( * node ) ;
2024-12-04 19:06:47 +09:00
if ( element . has_attribute ( HTML : : AttributeNames : : value ) )
2024-10-25 10:47:06 +09:00
builder . append ( element . value ( ) ) ;
} else if ( is < HTML : : HTMLSelectElement > ( * node ) ) {
auto const & element = static_cast < HTML : : HTMLSelectElement const & > ( * node ) ;
builder . append ( element . value ( ) ) ;
} else
builder . append ( node - > text_content ( ) . value ( ) ) ;
} else if ( role = = ARIA : : Role : : listbox ) {
2024-12-05 08:49:29 -05:00
// ii. Combobox/Listbox: If the embedded control has role combobox or listbox, return the text
// alternative of the chosen option.
2024-10-25 10:47:06 +09:00
if ( is < HTML : : HTMLSelectElement > ( * node ) ) {
auto const & element = static_cast < HTML : : HTMLSelectElement const & > ( * node ) ;
builder . append ( element . value ( ) ) ;
}
auto children = node - > children_as_vector ( ) ;
for ( auto & child : children ) {
if ( child - > is_element ( ) ) {
auto const & element = static_cast < DOM : : Element const & > ( * child ) ;
auto role = element . role_or_default ( ) ;
if ( role = = ARIA : : Role : : option & & element . aria_selected ( ) = = " true " )
builder . append ( element . text_content ( ) . value ( ) ) ;
}
}
} else if ( role = = ARIA : : Role : : spinbutton | | role = = ARIA : : Role : : slider ) {
2024-12-04 19:10:31 +09:00
auto aria_valuenow = element . aria_value_now ( ) ;
auto aria_valuetext = element . aria_value_text ( ) ;
2024-12-05 08:49:29 -05:00
2024-10-25 10:47:06 +09:00
// iii. Range: If the embedded control has role range (e.g., a spinbutton or slider):
// a. If the aria-valuetext property is present, return its value,
2024-12-04 19:10:31 +09:00
if ( aria_valuetext . has_value ( ) )
builder . append ( aria_valuetext . value ( ) ) ;
2024-10-25 10:47:06 +09:00
// b. Otherwise, if the aria-valuenow property is present, return its value
2024-12-04 19:10:31 +09:00
else if ( aria_valuenow . has_value ( ) )
builder . append ( aria_valuenow . value ( ) ) ;
2024-10-25 10:47:06 +09:00
// c. Otherwise, use the value as specified by a host language attribute.
else if ( is < HTML : : HTMLInputElement > ( * node ) ) {
auto const & element = static_cast < HTML : : HTMLInputElement const & > ( * node ) ;
2024-12-04 19:06:47 +09:00
if ( element . has_attribute ( HTML : : AttributeNames : : value ) )
2024-10-25 10:47:06 +09:00
builder . append ( element . value ( ) ) ;
}
}
} else if ( node - > is_text ( ) ) {
auto const & text_node = static_cast < DOM : : Text const & > ( * node ) ;
builder . append ( text_node . data ( ) ) ;
}
}
}
return builder . to_string ( ) ;
}
2024-11-07 20:33:43 +09:00
// E. Host Language Label: Otherwise, if the current node's native markup provides an attribute (e.g. alt) or
2024-12-05 08:49:29 -05:00
// element (e.g. HTML label or SVG title) that defines a text alternative, return that alternative in the form
// of a flat string as defined by the host language, unless the element is marked as presentational
// (role="presentation" or role="none").
//
2024-11-21 05:03:28 +09:00
// TODO: Confirm (through existing WPT test cases) whether HTMLLabelElement is already handled (by the code for
// step C. “Embedded Control” above) in conformance with the spec requirements — and if not, then add handling.
if ( role ! = ARIA : : Role : : presentation & & role ! = ARIA : : Role : : none & & is < HTML : : HTMLImageElement > ( * element ) )
2024-11-09 23:45:09 +09:00
return element - > alternative_text ( ) . release_value ( ) ;
2024-12-05 08:49:29 -05:00
2024-11-21 05:03:28 +09:00
// https://w3c.github.io/svg-aam/#mapping_additional_nd
Optional < String > title_element_text ;
if ( element - > is_svg_element ( ) ) {
// If the current node has at least one direct child title element, select the appropriate title based on
// the language rules for the SVG specification, and return the title text alternative as a flat string.
element - > for_each_child_of_type < SVG : : SVGTitleElement > ( [ & ] ( SVG : : SVGTitleElement const & title ) mutable {
title_element_text = title . text_content ( ) ;
return IterationDecision : : Break ;
} ) ;
if ( title_element_text . has_value ( ) )
return title_element_text . release_value ( ) ;
2024-12-05 08:49:29 -05:00
2024-11-21 05:03:28 +09:00
// If the current node is a link, and there was no child title element, but it has an xlink:title attribute,
// return the value of that attribute.
if ( auto title_attribute = element - > get_attribute_ns ( Namespace : : XLink , XLink : : AttributeNames : : title ) ; title_attribute . has_value ( ) )
return title_attribute . release_value ( ) ;
2024-11-07 20:33:43 +09:00
}
2024-12-05 08:49:29 -05:00
2024-11-19 21:03:48 +09:00
// https://w3c.github.io/html-aam/#table-element-accessible-name-computation
2024-12-05 08:49:29 -05:00
// 2. If the accessible name is still empty, then: if the table element has a child that is a caption element,
// then use the subtree of the first such element.
2024-11-19 21:03:48 +09:00
if ( is < HTML : : HTMLTableElement > ( * element ) )
if ( auto & table = ( const_cast < HTML : : HTMLTableElement & > ( static_cast < HTML : : HTMLTableElement const & > ( * element ) ) ) ; table . caption ( ) )
return table . caption ( ) - > text_content ( ) . release_value ( ) ;
2024-12-05 08:49:29 -05:00
// https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation
// 2. If the accessible name is still empty, then: if the fieldset element has a child that is a legend element,
// then use the subtree of the first such element.
2024-11-19 21:03:48 +09:00
if ( is < HTML : : HTMLFieldSetElement > ( * element ) ) {
Optional < String > legend ;
auto & fieldset = ( const_cast < HTML : : HTMLFieldSetElement & > ( static_cast < HTML : : HTMLFieldSetElement const & > ( * element ) ) ) ;
fieldset . for_each_child_of_type < HTML : : HTMLLegendElement > ( [ & ] ( HTML : : HTMLLegendElement const & element ) mutable {
legend = element . text_content ( ) . release_value ( ) ;
return IterationDecision : : Break ;
} ) ;
if ( legend . has_value ( ) )
return legend . release_value ( ) ;
}
2024-12-05 08:49:29 -05:00
2024-11-19 21:03:48 +09:00
if ( is < HTML : : HTMLInputElement > ( * element ) ) {
auto & input = ( const_cast < HTML : : HTMLInputElement & > ( static_cast < HTML : : HTMLInputElement const & > ( * element ) ) ) ;
2024-11-20 05:25:26 +09:00
// https://w3c.github.io/html-aam/#input-type-button-input-type-submit-and-input-type-reset-accessible-name-computation
2024-12-05 08:49:29 -05:00
// 3. Otherwise use the value attribute.
2024-11-20 05:25:26 +09:00
if ( input . type_state ( ) = = HTML : : HTMLInputElement : : TypeAttributeState : : Button
| | input . type_state ( ) = = HTML : : HTMLInputElement : : TypeAttributeState : : SubmitButton
| | input . type_state ( ) = = HTML : : HTMLInputElement : : TypeAttributeState : : ResetButton )
if ( auto value = input . get_attribute ( HTML : : AttributeNames : : value ) ; value . has_value ( ) )
return value . release_value ( ) ;
2024-12-05 08:49:29 -05:00
2024-11-19 21:03:48 +09:00
// https://w3c.github.io/html-aam/#input-type-image-accessible-name-computation
2024-12-05 08:49:29 -05:00
// 3. Otherwise use alt attribute if present and its value is not the empty string.
2024-11-19 21:03:48 +09:00
if ( input . type_state ( ) = = HTML : : HTMLInputElement : : TypeAttributeState : : ImageButton )
if ( auto alt = element - > get_attribute ( HTML : : AttributeNames : : alt ) ; alt . has_value ( ) )
return alt . release_value ( ) ;
}
2023-02-05 11:21:59 -06:00
2024-11-11 16:21:05 +09:00
// F. Name From Content: Otherwise, if the current node's role allows name from content, or if the current node
2024-12-05 08:49:29 -05:00
// is referenced by aria-labelledby, aria-describedby, or is a native host language text alternative element
// (e.g. label in HTML), or is a descendant of a native host language text alternative element:
2024-11-11 16:21:05 +09:00
if ( ( role . has_value ( ) & & ARIA : : allows_name_from_content ( role . value ( ) ) ) | | element - > is_referenced ( ) | | is_descendant = = IsDescendant : : Yes ) {
2023-02-05 11:21:59 -06:00
// i. Set the accumulated text to the empty string.
total_accumulated_text . clear ( ) ;
2024-12-05 08:49:29 -05:00
// ii. Name From Generated Content: Check for CSS generated textual content associated with the current node
// and include it in the accumulated text. The CSS ::before and ::after pseudo elements [CSS2] can provide
// textual content for elements that have a content model.
2024-11-04 14:47:55 +09:00
// a. For ::before pseudo elements, User agents MUST prepend CSS textual content, without a space, to the textual
2024-12-05 08:49:29 -05:00
// content of the current node.
// b. For ::after pseudo elements, User agents MUST append CSS textual content, without a space, to the textual
// content of the current node. NOTE: The code for handling the ::after pseudo elements case is further below,
// following the “iii. For each child node of the current node” code.
2024-11-04 14:47:55 +09:00
if ( auto before = element - > get_pseudo_element_node ( CSS : : Selector : : PseudoElement : : Type : : Before ) ) {
if ( before - > computed_values ( ) . content ( ) . alt_text . has_value ( ) )
total_accumulated_text . append ( before - > computed_values ( ) . content ( ) . alt_text . release_value ( ) ) ;
else
total_accumulated_text . append ( before - > computed_values ( ) . content ( ) . data ) ;
}
2024-12-05 08:49:29 -05:00
2024-11-22 18:04:38 +09:00
// iii. Determine Child Nodes: Determine the rendered child nodes of the current node:
// iii. Determine Child Nodes: Determine the rendered child nodes of the current node:
// c. [Otherwise,] set the rendered child nodes to be the child nodes of the current node.
auto child_nodes = current_node - > children_as_vector ( ) ;
2024-12-05 08:49:29 -05:00
2024-11-22 18:04:38 +09:00
// a. If the current node has an attached shadow root, set the rendered child nodes to be the child nodes of
2024-12-05 08:49:29 -05:00
// the shadow root.
2024-11-22 18:04:38 +09:00
if ( element - > is_shadow_host ( ) & & element - > shadow_root ( ) & & element - > shadow_root ( ) - > is_connected ( ) )
child_nodes = element - > shadow_root ( ) - > children_as_vector ( ) ;
2024-12-05 08:49:29 -05:00
2024-11-22 18:04:38 +09:00
// b. Otherwise, if the current node is a slot with assigned nodes, set the rendered child nodes to be the
2024-12-05 08:49:29 -05:00
// assigned nodes of the current node.
2024-11-22 18:04:38 +09:00
if ( element - > is_html_slot_element ( ) ) {
total_accumulated_text . append ( element - > text_content ( ) . value ( ) ) ;
child_nodes = static_cast < HTML : : HTMLSlotElement const * > ( element ) - > assigned_nodes ( ) ;
}
2024-12-05 08:49:29 -05:00
2024-11-22 18:04:38 +09:00
// iv. Name From Each Child: For each rendered child node of the current node
for ( auto & child_node : child_nodes ) {
if ( ! child_node - > is_element ( ) & & ! child_node - > is_text ( ) )
continue ;
2024-11-04 15:50:42 +09:00
bool should_add_space = true ;
const_cast < DOM : : Document & > ( document ) . update_layout ( ) ;
2024-11-22 18:04:38 +09:00
auto const * layout_node = child_node - > layout_node ( ) ;
2024-11-04 15:50:42 +09:00
if ( layout_node ) {
auto display = layout_node - > display ( ) ;
if ( display . is_inline_outside ( ) & & display . is_flow_inside ( ) ) {
should_add_space = false ;
}
}
2024-11-22 18:04:38 +09:00
if ( visited_nodes . contains ( child_node - > unique_id ( ) ) )
continue ;
2024-12-05 08:49:29 -05:00
2023-02-05 11:21:59 -06:00
// a. Set the current node to the child node.
2024-11-22 18:04:38 +09:00
current_node = child_node ;
2024-12-05 08:49:29 -05:00
2023-02-05 11:21:59 -06:00
// b. Compute the text alternative of the current node beginning with step 2. Set the result to that text alternative.
2024-11-01 21:11:32 +09:00
auto result = MUST ( current_node - > name_or_description ( target , document , visited_nodes , IsDescendant : : Yes ) ) ;
2024-12-05 08:49:29 -05:00
// J. Append a space character and the result of each step above to the total accumulated text.
2024-11-04 15:50:42 +09:00
// AD-HOC: Doing the space-adding here is in a different order from what the spec states.
if ( should_add_space )
total_accumulated_text . append ( ' ' ) ;
2024-12-05 08:49:29 -05:00
2023-02-05 11:21:59 -06:00
// c. Append the result to the accumulated text.
total_accumulated_text . append ( result ) ;
2024-11-22 18:04:38 +09:00
}
2024-12-05 08:49:29 -05:00
2024-11-04 14:47:55 +09:00
// NOTE: See step ii.b above.
if ( auto after = element - > get_pseudo_element_node ( CSS : : Selector : : PseudoElement : : Type : : After ) ) {
if ( after - > computed_values ( ) . content ( ) . alt_text . has_value ( ) )
total_accumulated_text . append ( after - > computed_values ( ) . content ( ) . alt_text . release_value ( ) ) ;
else
total_accumulated_text . append ( after - > computed_values ( ) . content ( ) . data ) ;
}
2024-12-05 08:49:29 -05:00
2024-11-07 20:33:43 +09:00
// v. Return the accumulated text if it is not the empty string ("").
2024-11-06 17:40:55 +09:00
if ( ! total_accumulated_text . is_empty ( ) )
return total_accumulated_text . to_string ( ) ;
2024-12-05 08:49:29 -05:00
// Important: Each node in the subtree is consulted only once. If text has been collected from a descendant,
// but is referenced by another IDREF in some descendant node, then that second, or subsequent, reference is
// not followed. This is done to avoid infinite loops.
2023-02-05 11:21:59 -06:00
}
}
2024-11-04 15:50:42 +09:00
// G. Text Node: Otherwise, if the current node is a Text Node, return its textual contents.
2024-12-05 08:49:29 -05:00
//
2024-11-11 16:21:05 +09:00
// AD-HOC: The spec doesn’ t require ascending through the parent node and ancestor nodes of every text node we
// reach — the way we’ re doing there. But we implement it this way because the spec algorithm as written doesn’ t
// appear to achieve what it seems to be intended to achieve. Specifically, the spec algorithm as written doesn’ t
// cause traversal through element subtrees in way that’ s necessary to check for descendants that are referenced by
// aria-labelledby or aria-describedby and/or un-hidden. See the comment for substep A above.
if ( is_text ( ) & & ( ! parent_element ( ) | | ( parent_element ( ) - > is_referenced ( ) | | ! parent_element ( ) - > is_hidden ( ) | | ! parent_element ( ) - > has_hidden_ancestor ( ) | | parent_element ( ) - > has_referenced_and_hidden_ancestor ( ) ) ) ) {
2024-11-04 15:50:42 +09:00
if ( layout_node ( ) & & layout_node ( ) - > is_text_node ( ) )
return verify_cast < Layout : : TextNode > ( layout_node ( ) ) - > text_for_rendering ( ) ;
2024-11-11 16:21:05 +09:00
return text_content ( ) . release_value ( ) ;
2023-02-05 11:21:59 -06:00
}
2024-11-04 15:50:42 +09:00
2024-11-11 16:21:05 +09:00
// H. Otherwise, if the current node is a descendant of an element whose Accessible Name or Accessible Description
2024-12-05 08:49:29 -05:00
// is being computed, and contains descendants, proceed to 2F.i.
//
2024-11-11 16:21:05 +09:00
// AD-HOC: We don’ t implement this step here — because is essentially unreachable code in the spec algorithm.
// We could never get here without descending through every subtree of an element whose Accessible Name or
// Accessible Description is being computed. And in our implementation of substep F about, we’ re anyway already
// recursively descending through all the child nodes of every element whose Accessible Name or Accessible
// Description is being computed, in a way that never leads to this substep H every being hit.
2023-02-05 11:21:59 -06:00
// I. Otherwise, if the current node has a Tooltip attribute, return its value.
2024-12-05 08:49:29 -05:00
//
2023-02-05 11:21:59 -06:00
// https://www.w3.org/TR/accname-1.2/#dfn-tooltip-attribute
2024-12-05 08:49:29 -05:00
// Any host language attribute that would result in a user agent generating a tooltip such as in response to a mouse
// hover in desktop user agents.
2023-02-05 11:21:59 -06:00
// FIXME: Support SVG tooltips and CSS tooltips
if ( is < HTML : : HTMLElement > ( this ) ) {
auto const * element = static_cast < HTML : : HTMLElement const * > ( this ) ;
auto tooltip = element - > title ( ) ;
2023-10-14 22:03:21 +01:00
if ( tooltip . has_value ( ) & & ! tooltip - > is_empty ( ) )
2023-10-10 15:00:58 +03:30
return tooltip . release_value ( ) ;
2023-02-05 11:21:59 -06:00
}
2024-12-05 08:49:29 -05:00
// 3. After all steps are completed, the total accumulated text is used as the accessible name or accessible description
// of the element that initiated the computation.
2023-02-05 11:21:59 -06:00
return total_accumulated_text . to_string ( ) ;
}
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_name
ErrorOr < String > Node : : accessible_name ( Document const & document ) const
{
2024-10-20 10:37:44 +02:00
HashTable < UniqueNodeID > visited_nodes ;
2023-02-05 11:21:59 -06:00
// User agents MUST compute an accessible name using the rules outlined below in the section titled Accessible Name and Description Computation.
return name_or_description ( NameOrDescription : : Name , document , visited_nodes ) ;
}
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_description
ErrorOr < String > Node : : accessible_description ( Document const & document ) const
{
// If aria-describedby is present, user agents MUST compute the accessible description by concatenating the text alternatives for elements referenced by an aria-describedby attribute on the current element.
// The text alternatives for the referenced elements are computed using a number of methods, outlined below in the section titled Accessible Name and Description Computation.
2023-10-06 07:43:52 +13:00
if ( ! is_element ( ) )
return String { } ;
auto const * element = static_cast < Element const * > ( this ) ;
auto described_by = element - > aria_described_by ( ) ;
if ( ! described_by . has_value ( ) )
return String { } ;
2024-10-20 10:37:44 +02:00
HashTable < UniqueNodeID > visited_nodes ;
2023-10-06 07:43:52 +13:00
StringBuilder builder ;
auto id_list = described_by - > bytes_as_string_view ( ) . split_view_if ( Infra : : is_ascii_whitespace ) ;
for ( auto const & id : id_list ) {
2023-10-08 13:34:11 +13:00
if ( auto description_element = document . get_element_by_id ( MUST ( FlyString : : from_utf8 ( id ) ) ) ) {
2023-10-06 07:43:52 +13:00
auto description = TRY (
description_element - > name_or_description ( NameOrDescription : : Description , document ,
visited_nodes ) ) ;
if ( ! description . is_empty ( ) ) {
if ( builder . is_empty ( ) ) {
builder . append ( description ) ;
} else {
builder . append ( " " sv ) ;
builder . append ( description ) ;
2023-02-05 11:21:59 -06:00
}
}
}
}
2023-10-06 07:43:52 +13:00
return builder . to_string ( ) ;
2023-02-05 11:21:59 -06:00
}
2023-12-03 08:24:04 +13:00
Optional < StringView > Node : : first_valid_id ( StringView value , Document const & document )
2023-02-05 11:21:59 -06:00
{
2023-12-03 08:24:04 +13:00
auto id_list = value . split_view_if ( Infra : : is_ascii_whitespace ) ;
2023-02-05 11:21:59 -06:00
for ( auto const & id : id_list ) {
2023-10-08 13:34:11 +13:00
if ( document . get_element_by_id ( MUST ( FlyString : : from_utf8 ( id ) ) ) )
2023-02-05 11:21:59 -06:00
return id ;
}
return { } ;
}
2023-11-18 11:22:51 +01:00
void Node : : add_registered_observer ( RegisteredObserver & registered_observer )
{
if ( ! m_registered_observer_list )
2024-11-15 04:01:23 +13:00
m_registered_observer_list = make < Vector < GC : : Ref < RegisteredObserver > > > ( ) ;
2023-11-18 11:22:51 +01:00
m_registered_observer_list - > append ( registered_observer ) ;
}
2020-03-07 10:27:02 +01:00
}
2024-10-20 10:37:44 +02:00
namespace IPC {
template < >
ErrorOr < void > encode ( Encoder & encoder , Web : : UniqueNodeID const & value )
{
return encode ( encoder , value . value ( ) ) ;
}
template < >
ErrorOr < Web : : UniqueNodeID > decode ( Decoder & decoder )
{
auto value = TRY ( decoder . decode < i64 > ( ) ) ;
return Web : : UniqueNodeID ( value ) ;
}
}