2020-08-17 19:14:30 +01:00
/*
2021-04-22 22:12:55 +01:00
* Copyright ( c ) 2020 , Luke Wilde < lukew @ serenityos . org >
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2022 , Andreas Kling < andreas @ ladybird . org >
2023-11-05 12:15:50 +13:00
* Copyright ( c ) 2023 , Shannon Booth < shannon @ serenityos . org >
2020-08-17 19:14:30 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-08-17 19:14:30 +01:00
*/
2021-07-30 19:31:46 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2020-08-17 19:14:30 +01:00
# include <LibWeb/CSS/SelectorEngine.h>
2022-09-18 00:39:42 +02:00
# include <LibWeb/DOM/Document.h>
2021-09-13 23:01:17 +01:00
# include <LibWeb/DOM/HTMLCollection.h>
2022-01-29 20:23:48 +00:00
# include <LibWeb/DOM/NodeOperations.h>
2020-08-17 19:14:30 +01:00
# include <LibWeb/DOM/ParentNode.h>
2024-07-23 15:22:28 +02:00
# include <LibWeb/DOM/ShadowRoot.h>
2021-10-02 20:36:39 +01:00
# include <LibWeb/DOM/StaticNodeList.h>
2020-08-17 19:14:30 +01:00
# include <LibWeb/Dump.h>
2024-07-22 20:38:36 +01:00
# include <LibWeb/Infra/CharacterTypes.h>
2023-11-05 12:15:50 +13:00
# include <LibWeb/Infra/Strings.h>
2021-09-22 18:06:19 +01:00
# include <LibWeb/Namespace.h>
2020-08-17 19:14:30 +01:00
namespace Web : : DOM {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( ParentNode ) ;
2024-04-06 10:16:04 -07:00
2025-08-29 13:02:52 +01:00
static bool contains_named_namespace ( CSS : : SelectorList const & selectors )
2024-11-21 21:31:39 +11:00
{
for ( auto const & selector : selectors ) {
for ( auto const & compound_selector : selector - > compound_selectors ( ) ) {
for ( auto simple_selector : compound_selector . simple_selectors ) {
if ( simple_selector . value . has < CSS : : Selector : : SimpleSelector : : QualifiedName > ( ) ) {
if ( simple_selector . qualified_name ( ) . namespace_type = = CSS : : Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named )
return true ;
}
if ( simple_selector . value . has < CSS : : Selector : : SimpleSelector : : PseudoClassSelector > ( ) ) {
if ( contains_named_namespace ( simple_selector . pseudo_class ( ) . argument_selector_list ) )
return true ;
}
}
}
}
return false ;
}
2024-11-11 16:45:04 +00:00
enum class ReturnMatches {
First ,
All ,
} ;
// https://dom.spec.whatwg.org/#scope-match-a-selectors-string
2024-11-15 04:01:23 +13:00
static WebIDL : : ExceptionOr < Variant < GC : : Ptr < Element > , GC : : Ref < NodeList > > > scope_match_a_selectors_string ( ParentNode & node , StringView selector_text , ReturnMatches return_matches )
2020-08-17 19:14:30 +01:00
{
2023-03-20 23:48:59 +01:00
// To scope-match a selectors string selectors against a node, run these steps:
// 1. Let s be the result of parse a selector selectors.
2025-02-05 12:08:27 +00:00
auto maybe_selectors = parse_selector ( CSS : : Parser : : ParsingParams { node . document ( ) } , selector_text ) ;
2023-03-20 23:48:59 +01:00
// 2. If s is failure, then throw a "SyntaxError" DOMException.
2021-07-30 19:31:46 +01:00
if ( ! maybe_selectors . has_value ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( node . realm ( ) , " Failed to parse selector " _utf16 ) ;
2020-08-17 19:14:30 +01:00
2021-07-30 19:31:46 +01:00
auto selectors = maybe_selectors . value ( ) ;
2024-11-21 21:31:39 +11:00
// "Note: Support for namespaces within selectors is not planned and will not be added."
if ( contains_named_namespace ( selectors ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( node . realm ( ) , " Failed to parse selector " _utf16 ) ;
2024-11-21 21:31:39 +11:00
2023-03-20 23:48:59 +01:00
// 3. Return the result of match a selector against a tree with s and node’ s root using scoping root node.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Element > single_result ;
Vector < GC : : Root < Node > > results ;
2022-01-20 21:33:16 +00:00
// FIXME: This should be shadow-including. https://drafts.csswg.org/selectors-4/#match-a-selector-against-a-tree
2024-11-11 16:45:04 +00:00
node . for_each_in_subtree_of_type < Element > ( [ & ] ( auto & element ) {
2021-07-30 19:31:46 +01:00
for ( auto & selector : selectors ) {
2025-01-03 20:39:25 +03:00
SelectorEngine : : MatchContext context ;
if ( SelectorEngine : : matches ( selector , element , nullptr , context , { } , node ) ) {
2024-11-11 16:45:04 +00:00
if ( return_matches = = ReturnMatches : : First ) {
single_result = & element ;
return TraversalDecision : : Break ;
}
results . append ( element ) ;
2024-11-23 16:22:42 +11:00
break ;
2021-07-30 19:31:46 +01:00
}
2020-08-17 19:14:30 +01:00
}
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2020-08-17 19:14:30 +01:00
} ) ;
2024-11-11 16:45:04 +00:00
if ( return_matches = = ReturnMatches : : First )
return { single_result } ;
return { StaticNodeList : : create ( node . realm ( ) , move ( results ) ) } ;
}
// https://dom.spec.whatwg.org/#dom-parentnode-queryselector
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ptr < Element > > ParentNode : : query_selector ( StringView selector_text )
2024-11-11 16:45:04 +00:00
{
// The querySelector(selectors) method steps are to return the first result of running scope-match a selectors string selectors against this,
// if the result is not an empty list; otherwise null.
2024-11-15 04:01:23 +13:00
return TRY ( scope_match_a_selectors_string ( * this , selector_text , ReturnMatches : : First ) ) . get < GC : : Ptr < Element > > ( ) ;
2020-08-17 19:14:30 +01:00
}
2023-03-20 23:49:27 +01:00
// https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < NodeList > > ParentNode : : query_selector_all ( StringView selector_text )
2020-08-17 19:14:30 +01:00
{
2023-03-20 23:49:27 +01:00
// The querySelectorAll(selectors) method steps are to return the static result of running scope-match a selectors string selectors against this.
2024-11-15 04:01:23 +13:00
return TRY ( scope_match_a_selectors_string ( * this , selector_text , ReturnMatches : : All ) ) . get < GC : : Ref < NodeList > > ( ) ;
2020-08-17 19:14:30 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < Element > ParentNode : : first_element_child ( )
2020-11-21 18:49:09 +00:00
{
return first_child_of_type < Element > ( ) ;
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < Element > ParentNode : : last_element_child ( )
2020-11-21 18:49:09 +00:00
{
return last_child_of_type < Element > ( ) ;
}
2021-04-11 17:13:10 +01:00
// https://dom.spec.whatwg.org/#dom-parentnode-childelementcount
u32 ParentNode : : child_element_count ( ) const
{
u32 count = 0 ;
for ( auto * child = first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
if ( is < Element > ( child ) )
+ + count ;
}
return count ;
}
2022-09-18 00:42:33 +02:00
void ParentNode : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_children ) ;
}
2021-09-13 23:01:17 +01:00
// https://dom.spec.whatwg.org/#dom-parentnode-children
2024-11-15 04:01:23 +13:00
GC : : Ref < HTMLCollection > ParentNode : : children ( )
2021-09-13 23:01:17 +01:00
{
// The children getter steps are to return an HTMLCollection collection rooted at this matching only element children.
2022-09-18 00:42:33 +02:00
if ( ! m_children ) {
2023-05-23 11:25:07 +02:00
m_children = HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Children , [ ] ( Element const & ) {
return true ;
2023-08-13 13:05:26 +02:00
} ) ;
2022-09-18 00:42:33 +02:00
}
return * m_children ;
2021-09-13 23:01:17 +01:00
}
2021-09-22 18:06:19 +01:00
// https://dom.spec.whatwg.org/#concept-getelementsbytagname
// NOTE: This method is only exposed on Document and Element, but is in ParentNode to prevent code duplication.
2024-11-15 04:01:23 +13:00
GC : : Ref < HTMLCollection > ParentNode : : get_elements_by_tag_name ( FlyString const & qualified_name )
2021-09-22 18:06:19 +01:00
{
// 1. If qualifiedName is "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches only descendant elements.
if ( qualified_name = = " * " ) {
2023-05-23 11:25:07 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ ] ( Element const & ) {
2022-09-18 00:37:24 +02:00
return true ;
2023-08-13 13:05:26 +02:00
} ) ;
2021-09-22 18:06:19 +01:00
}
2022-09-18 00:39:42 +02:00
// 2. Otherwise, if root’ s node document is an HTML document, return a HTMLCollection rooted at root, whose filter matches the following descendant elements:
if ( root ( ) . document ( ) . document_type ( ) = = Document : : Type : : HTML ) {
2024-10-14 10:51:15 +02:00
FlyString qualified_name_in_ascii_lowercase = qualified_name . to_ascii_lowercase ( ) ;
2023-08-22 14:34:25 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ qualified_name , qualified_name_in_ascii_lowercase ] ( Element const & element ) {
2022-09-18 00:39:42 +02:00
// - Whose namespace is the HTML namespace and whose qualified name is qualifiedName, in ASCII lowercase.
2023-11-04 18:42:04 +01:00
if ( element . namespace_uri ( ) = = Namespace : : HTML )
2023-08-22 14:34:25 +02:00
return element . qualified_name ( ) = = qualified_name_in_ascii_lowercase ;
2022-09-18 00:39:42 +02:00
// - Whose namespace is not the HTML namespace and whose qualified name is qualifiedName.
2023-11-05 12:15:50 +13:00
return element . qualified_name ( ) = = qualified_name ;
2023-08-13 13:05:26 +02:00
} ) ;
2022-09-18 00:39:42 +02:00
}
2021-09-22 18:06:19 +01:00
2022-09-18 00:39:42 +02:00
// 3. Otherwise, return a HTMLCollection rooted at root, whose filter matches descendant elements whose qualified name is qualifiedName.
2023-05-23 11:25:07 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ qualified_name ] ( Element const & element ) {
2023-11-05 12:15:50 +13:00
return element . qualified_name ( ) = = qualified_name ;
2023-08-13 13:05:26 +02:00
} ) ;
2021-09-22 18:06:19 +01:00
}
// https://dom.spec.whatwg.org/#concept-getelementsbytagnamens
// NOTE: This method is only exposed on Document and Element, but is in ParentNode to prevent code duplication.
2024-11-15 04:01:23 +13:00
GC : : Ref < HTMLCollection > ParentNode : : get_elements_by_tag_name_ns ( Optional < FlyString > namespace_ , FlyString const & local_name )
2021-09-22 18:06:19 +01:00
{
// 1. If namespace is the empty string, set it to null.
2024-05-12 14:50:45 +12:00
if ( namespace_ = = FlyString { } )
namespace_ = OptionalNone { } ;
2021-09-22 18:06:19 +01:00
// 2. If both namespace and localName are "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches descendant elements.
if ( namespace_ = = " * " & & local_name = = " * " ) {
2023-05-23 11:25:07 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ ] ( Element const & ) {
2022-09-18 00:37:24 +02:00
return true ;
2023-08-13 13:05:26 +02:00
} ) ;
2021-09-22 18:06:19 +01:00
}
// 3. Otherwise, if namespace is "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches descendant elements whose local name is localName.
if ( namespace_ = = " * " ) {
2023-05-23 11:25:07 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ local_name ] ( Element const & element ) {
2022-09-18 00:37:24 +02:00
return element . local_name ( ) = = local_name ;
2023-08-13 13:05:26 +02:00
} ) ;
2021-09-22 18:06:19 +01:00
}
// 4. Otherwise, if localName is "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches descendant elements whose namespace is namespace.
if ( local_name = = " * " ) {
2023-05-23 11:25:07 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ namespace_ ] ( Element const & element ) {
2023-11-05 12:15:50 +13:00
return element . namespace_uri ( ) = = namespace_ ;
2023-08-13 13:05:26 +02:00
} ) ;
2021-09-22 18:06:19 +01:00
}
// 5. Otherwise, return a HTMLCollection rooted at root, whose filter matches descendant elements whose namespace is namespace and local name is localName.
2023-05-23 11:25:07 +02:00
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ namespace_ , local_name ] ( Element const & element ) {
2023-11-05 12:15:50 +13:00
return element . namespace_uri ( ) = = namespace_ & & element . local_name ( ) = = local_name ;
2023-08-13 13:05:26 +02:00
} ) ;
2021-09-22 18:06:19 +01:00
}
2022-01-29 20:23:48 +00:00
// https://dom.spec.whatwg.org/#dom-parentnode-prepend
2025-07-24 12:05:52 -04:00
WebIDL : : ExceptionOr < void > ParentNode : : prepend ( Vector < Variant < GC : : Root < Node > , Utf16String > > const & nodes )
2022-01-29 20:23:48 +00:00
{
// 1. Let node be the result of converting nodes into a node given nodes and this’ s node document.
2022-03-22 12:39:49 +00:00
auto node = TRY ( convert_nodes_to_single_node ( nodes , document ( ) ) ) ;
2022-01-29 20:23:48 +00:00
// 2. Pre-insert node into this before this’ s first child.
2022-03-22 12:39:49 +00:00
( void ) TRY ( pre_insert ( node , first_child ( ) ) ) ;
2022-01-29 20:23:48 +00:00
return { } ;
}
2025-07-24 12:05:52 -04:00
WebIDL : : ExceptionOr < void > ParentNode : : append ( Vector < Variant < GC : : Root < Node > , Utf16String > > const & nodes )
2022-01-29 20:45:17 +00:00
{
// 1. Let node be the result of converting nodes into a node given nodes and this’ s node document.
2022-03-22 12:39:49 +00:00
auto node = TRY ( convert_nodes_to_single_node ( nodes , document ( ) ) ) ;
2022-01-29 20:45:17 +00:00
// 2. Append node to this.
2022-03-22 12:39:49 +00:00
( void ) TRY ( append_child ( node ) ) ;
2022-01-29 20:45:17 +00:00
return { } ;
}
2025-07-24 12:05:52 -04:00
WebIDL : : ExceptionOr < void > ParentNode : : replace_children ( Vector < Variant < GC : : Root < Node > , Utf16String > > const & nodes )
2022-01-29 21:01:09 +00:00
{
// 1. Let node be the result of converting nodes into a node given nodes and this’ s node document.
2022-03-22 12:39:49 +00:00
auto node = TRY ( convert_nodes_to_single_node ( nodes , document ( ) ) ) ;
2022-01-29 21:01:09 +00:00
// 2. Ensure pre-insertion validity of node into this before null.
2025-07-25 03:35:01 +01:00
TRY ( ensure_pre_insertion_validity ( realm ( ) , node , nullptr ) ) ;
2022-01-29 21:01:09 +00:00
// 3. Replace all with node within this.
2022-08-28 13:42:07 +02:00
replace_all ( * node ) ;
2022-01-29 21:01:09 +00:00
return { } ;
}
2025-03-08 12:45:26 +13:00
// https://dom.spec.whatwg.org/#dom-parentnode-movebefore
WebIDL : : ExceptionOr < void > ParentNode : : move_before ( GC : : Ref < Node > node , GC : : Ptr < Node > child )
{
// 1. Let referenceChild be child.
auto reference_child = child ;
// 2. If referenceChild is node, then set referenceChild to node’ s next sibling.
if ( reference_child = = node )
reference_child = node - > next_sibling ( ) ;
// 3. Move node into this before referenceChild.
TRY ( node - > move_node ( * this , reference_child ) ) ;
return { } ;
}
2024-08-03 19:21:51 +01:00
// https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname
2024-11-15 04:01:23 +13:00
GC : : Ref < HTMLCollection > ParentNode : : get_elements_by_class_name ( StringView class_names )
2024-07-22 20:38:36 +01:00
{
Vector < FlyString > list_of_class_names ;
for ( auto & name : class_names . split_view_if ( Infra : : is_ascii_whitespace ) ) {
list_of_class_names . append ( FlyString : : from_utf8 ( name ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
}
return HTMLCollection : : create ( * this , HTMLCollection : : Scope : : Descendants , [ list_of_class_names = move ( list_of_class_names ) , quirks_mode = document ( ) . in_quirks_mode ( ) ] ( Element const & element ) {
for ( auto & name : list_of_class_names ) {
2024-08-03 19:21:51 +01:00
if ( ! element . has_class ( name , quirks_mode ? CaseSensitivity : : CaseInsensitive : CaseSensitivity : : CaseSensitive ) )
return false ;
2024-07-22 20:38:36 +01:00
}
2024-08-03 19:21:51 +01:00
return ! list_of_class_names . is_empty ( ) ;
2024-07-22 20:38:36 +01:00
} ) ;
}
2025-03-25 17:30:52 +00:00
GC : : Ptr < Element > ParentNode : : get_element_by_id ( FlyString const & id ) const
{
2025-04-06 02:28:18 +02:00
if ( is_connected ( ) ) {
// For connected document and shadow root we have a cache that allows fast lookup.
if ( is_document ( ) ) {
auto const & document = static_cast < Document const & > ( * this ) ;
return document . element_by_id ( ) . get ( id ) ;
}
if ( is_shadow_root ( ) ) {
auto const & shadow_root = static_cast < ShadowRoot const & > ( * this ) ;
return shadow_root . element_by_id ( ) . get ( id ) ;
}
2025-03-25 17:30:52 +00:00
}
GC : : Ptr < Element > found_element ;
const_cast < ParentNode & > ( * this ) . for_each_in_inclusive_subtree_of_type < Element > ( [ & ] ( Element & element ) {
if ( element . id ( ) = = id ) {
found_element = & element ;
return TraversalDecision : : Break ;
}
return TraversalDecision : : Continue ;
} ) ;
return found_element ;
}
2020-08-17 19:14:30 +01:00
}