2022-03-09 14:37:48 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2022 , Andreas Kling < andreas @ ladybird . org >
2022-03-09 14:37:48 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2025-04-09 12:29:53 +02:00
# include <LibJS/Runtime/ValueInlines.h>
2022-09-30 17:16:16 -06:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/TreeWalkerPrototype.h>
2022-03-09 14:37:48 +01:00
# include <LibWeb/DOM/Node.h>
# include <LibWeb/DOM/NodeFilter.h>
# include <LibWeb/DOM/TreeWalker.h>
2022-09-24 16:14:37 +01:00
# include <LibWeb/WebIDL/AbstractOperations.h>
2022-09-25 17:28:46 +01:00
# include <LibWeb/WebIDL/DOMException.h>
2022-03-09 14:37:48 +01:00
namespace Web : : DOM {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( TreeWalker ) ;
2023-11-19 19:47:52 +01:00
2025-01-10 16:09:53 +13:00
TreeWalker : : TreeWalker ( JS : : Realm & realm , Node & root )
: PlatformObject ( realm )
2022-08-08 23:03:27 +02:00
, m_root ( root )
2022-03-09 14:37:48 +01:00
, m_current ( root )
{
}
2022-08-08 23:03:27 +02:00
TreeWalker : : ~ TreeWalker ( ) = default ;
2023-08-07 08:41:28 +02:00
void TreeWalker : : initialize ( JS : : Realm & realm )
2023-01-10 06:56:59 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( TreeWalker ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:56:59 -05:00
}
2022-08-08 23:03:27 +02:00
void TreeWalker : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2023-11-19 16:18:00 +13:00
visitor . visit ( m_filter ) ;
visitor . visit ( m_root ) ;
visitor . visit ( m_current ) ;
2022-08-08 23:03:27 +02:00
}
2022-03-09 14:37:48 +01:00
// https://dom.spec.whatwg.org/#dom-document-createtreewalker
2025-01-10 16:09:53 +13:00
GC : : Ref < TreeWalker > TreeWalker : : create ( JS : : Realm & realm , Node & root , unsigned what_to_show , GC : : Ptr < NodeFilter > filter )
2022-03-09 14:37:48 +01:00
{
// 1. Let walker be a new TreeWalker object.
// 2. Set walker’ s root and walker’ s current to root.
2025-01-10 16:09:53 +13:00
auto walker = realm . create < TreeWalker > ( realm , root ) ;
2022-03-09 14:37:48 +01:00
// 3. Set walker’ s whatToShow to whatToShow.
walker - > m_what_to_show = what_to_show ;
// 4. Set walker’ s filter to filter.
2022-08-08 23:03:27 +02:00
walker - > m_filter = filter ;
2022-03-09 14:37:48 +01:00
// 5. Return walker.
2022-12-14 17:40:33 +00:00
return walker ;
2022-03-09 14:37:48 +01:00
}
// https://dom.spec.whatwg.org/#dom-treewalker-currentnode
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > TreeWalker : : current_node ( ) const
2022-03-09 14:37:48 +01:00
{
return * m_current ;
}
// https://dom.spec.whatwg.org/#dom-treewalker-currentnode
void TreeWalker : : set_current_node ( Node & node )
{
2022-12-14 13:43:57 +01:00
m_current = node ;
2022-03-09 14:37:48 +01:00
}
// https://dom.spec.whatwg.org/#dom-treewalker-parentnode
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : parent_node ( )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be this’ s current.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > node = m_current ;
2022-03-09 14:37:48 +01:00
// 2. While node is non-null and is not this’ s root:
while ( node & & node ! = m_root ) {
// 1. Set node to node’ s parent.
node = node - > parent ( ) ;
// 2. If node is non-null and filtering node within this returns FILTER_ACCEPT,
// then set this’ s current to node and return node.
if ( node ) {
2022-03-22 21:10:59 +01:00
auto result = TRY ( filter ( * node ) ) ;
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-12-14 13:43:57 +01:00
m_current = * node ;
2022-03-09 14:37:48 +01:00
return node ;
}
}
}
2022-03-22 21:10:59 +01:00
return nullptr ;
2022-03-09 14:37:48 +01:00
}
// https://dom.spec.whatwg.org/#dom-treewalker-firstchild
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : first_child ( )
2022-03-09 14:37:48 +01:00
{
return traverse_children ( ChildTraversalType : : First ) ;
}
// https://dom.spec.whatwg.org/#dom-treewalker-lastchild
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : last_child ( )
2022-03-09 14:37:48 +01:00
{
return traverse_children ( ChildTraversalType : : Last ) ;
}
// https://dom.spec.whatwg.org/#dom-treewalker-previoussibling
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : previous_sibling ( )
2022-03-09 14:37:48 +01:00
{
return traverse_siblings ( SiblingTraversalType : : Previous ) ;
}
// https://dom.spec.whatwg.org/#dom-treewalker-nextsibling
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : next_sibling ( )
2022-03-09 14:37:48 +01:00
{
return traverse_siblings ( SiblingTraversalType : : Next ) ;
}
// https://dom.spec.whatwg.org/#dom-treewalker-previousnode
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : previous_node ( )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be this’ s current.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > node = m_current ;
2022-03-09 14:37:48 +01:00
// 2. While node is not this’ s root:
while ( node ! = m_root ) {
// 1. Let sibling be node’ s previous sibling.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > sibling = node - > previous_sibling ( ) ;
2022-03-09 14:37:48 +01:00
// 2. While sibling is non-null:
while ( sibling ) {
// 1. Set node to sibling.
2022-12-14 13:43:57 +01:00
node = * sibling ;
2022-03-09 14:37:48 +01:00
// 2. Let result be the result of filtering node within this.
auto result = TRY ( filter ( * node ) ) ;
// 3. While result is not FILTER_REJECT and node has a child:
2023-01-27 13:22:36 +00:00
while ( result ! = NodeFilter : : Result : : FILTER_REJECT & & node - > has_children ( ) ) {
2022-03-09 14:37:48 +01:00
// 1. Set node to node’ s last child.
2022-12-14 13:43:57 +01:00
node = * node - > last_child ( ) ;
2022-03-09 14:37:48 +01:00
// 2. Set result to the result of filtering node within this.
result = TRY ( filter ( * node ) ) ;
}
// 4. If result is FILTER_ACCEPT, then set this’ s current to node and return node.
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-08-28 13:42:07 +02:00
m_current = node ;
2022-03-09 14:37:48 +01:00
return node ;
}
// 5. Set sibling to node’ s previous sibling.
sibling = node - > previous_sibling ( ) ;
}
// 3. If node is this’ s root or node’ s parent is null, then return null.
if ( node = = m_root | | ! node - > parent ( ) )
return nullptr ;
// 4. Set node to node’ s parent.
2022-12-14 13:43:57 +01:00
node = * node - > parent ( ) ;
2022-03-09 14:37:48 +01:00
// 5. If the return value of filtering node within this is FILTER_ACCEPT, then set this’ s current to node and return node.
2023-01-27 13:22:36 +00:00
if ( TRY ( filter ( * node ) ) = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-08-28 13:42:07 +02:00
m_current = node ;
2022-03-09 14:37:48 +01:00
return node ;
}
}
// 3. Return null.
return nullptr ;
}
// https://dom.spec.whatwg.org/#dom-treewalker-nextnode
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : next_node ( )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be this’ s current.
2024-11-15 04:01:23 +13:00
GC : : Ref < Node > node = m_current ;
2022-03-09 14:37:48 +01:00
// 2. Let result be FILTER_ACCEPT.
2023-01-27 13:22:36 +00:00
auto result = NodeFilter : : Result : : FILTER_ACCEPT ;
2022-03-09 14:37:48 +01:00
// 3. While true:
while ( true ) {
// 1. While result is not FILTER_REJECT and node has a child:
2023-01-27 13:22:36 +00:00
while ( result ! = NodeFilter : : Result : : FILTER_REJECT & & node - > has_children ( ) ) {
2022-03-09 14:37:48 +01:00
// 1. Set node to its first child.
2022-12-14 13:43:57 +01:00
node = * node - > first_child ( ) ;
2022-03-09 14:37:48 +01:00
// 2. Set result to the result of filtering node within this.
2023-01-27 13:13:25 +00:00
result = TRY ( filter ( * node ) ) ;
2022-03-09 14:37:48 +01:00
// 3. If result is FILTER_ACCEPT, then set this’ s current to node and return node.
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-12-14 13:43:57 +01:00
m_current = * node ;
2022-03-09 14:37:48 +01:00
return node ;
}
}
// 2. Let sibling be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > sibling = nullptr ;
2022-03-09 14:37:48 +01:00
// 3. Let temporary be node.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > temporary = node ;
2022-03-09 14:37:48 +01:00
// 4. While temporary is non-null:
while ( temporary ) {
// 1. If temporary is this’ s root, then return null.
if ( temporary = = m_root )
return nullptr ;
// 2. Set sibling to temporary’ s next sibling.
sibling = temporary - > next_sibling ( ) ;
// 3. If sibling is non-null, then set node to sibling and break.
if ( sibling ) {
2022-12-14 13:43:57 +01:00
node = * sibling ;
2022-03-09 14:37:48 +01:00
break ;
}
// 4. Set temporary to temporary’ s parent.
temporary = temporary - > parent ( ) ;
2023-12-03 21:27:51 +01:00
// NON-STANDARD: If temporary is null, then return null.
// This prevents us from infinite looping if the current node is not connected.
// Spec bug: https://github.com/whatwg/dom/issues/1102
if ( temporary = = nullptr ) {
return nullptr ;
}
2022-03-09 14:37:48 +01:00
}
// 5. Set result to the result of filtering node within this.
result = TRY ( filter ( * node ) ) ;
// 6. If result is FILTER_ACCEPT, then set this’ s current to node and return node.
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-12-14 13:43:57 +01:00
m_current = * node ;
2022-03-09 14:37:48 +01:00
return node ;
}
}
}
2025-02-24 16:35:14 +00:00
// https://dom.spec.whatwg.org/#concept-traversal-filter
JS : : Object * TreeWalker : : filter ( ) const
{
if ( ! m_filter )
return nullptr ;
return m_filter - > callback ( ) . callback ;
}
2022-03-09 14:37:48 +01:00
// https://dom.spec.whatwg.org/#concept-node-filter
JS : : ThrowCompletionOr < NodeFilter : : Result > TreeWalker : : filter ( Node & node )
{
// 1. If traverser’ s active flag is set, then throw an "InvalidStateError" DOMException.
if ( m_active )
2025-08-07 19:31:52 -04:00
return throw_completion ( WebIDL : : InvalidStateError : : create ( realm ( ) , " NodeIterator is already active " _utf16 ) ) ;
2022-03-09 14:37:48 +01:00
// 2. Let n be node’ s nodeType attribute value − 1.
auto n = node . node_type ( ) - 1 ;
// 3. If the nth bit (where 0 is the least significant bit) of traverser’ s whatToShow is not set, then return FILTER_SKIP.
if ( ! ( m_what_to_show & ( 1u < < n ) ) )
2023-01-27 13:22:36 +00:00
return NodeFilter : : Result : : FILTER_SKIP ;
2022-03-09 14:37:48 +01:00
// 4. If traverser’ s filter is null, then return FILTER_ACCEPT.
2022-08-08 23:03:27 +02:00
if ( ! m_filter )
2023-01-27 13:22:36 +00:00
return NodeFilter : : Result : : FILTER_ACCEPT ;
2022-03-09 14:37:48 +01:00
// 5. Set traverser’ s active flag.
m_active = true ;
// 6. Let result be the return value of call a user object’ s operation with traverser’ s filter, "acceptNode", and « node ».
// If this throws an exception, then unset traverser’ s active flag and rethrow the exception.
2025-08-02 19:27:29 -04:00
auto result = WebIDL : : call_user_object_operation ( m_filter - > callback ( ) , " acceptNode " _utf16_fly_string , { } , { { & node } } ) ;
2022-03-09 14:37:48 +01:00
if ( result . is_abrupt ( ) ) {
m_active = false ;
return result ;
}
// 7. Unset traverser’ s active flag.
m_active = false ;
// 8. Return result.
2025-04-04 18:11:45 +02:00
auto result_value = TRY ( result . value ( ) . to_i32 ( vm ( ) ) ) ;
2022-03-09 14:37:48 +01:00
return static_cast < NodeFilter : : Result > ( result_value ) ;
}
// https://dom.spec.whatwg.org/#concept-traverse-children
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : traverse_children ( ChildTraversalType type )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be walker’ s current.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > node = m_current ;
2022-03-09 14:37:48 +01:00
// 2. Set node to node’ s first child if type is first, and node’ s last child if type is last.
node = type = = ChildTraversalType : : First ? node - > first_child ( ) : node - > last_child ( ) ;
// 3. While node is non-null:
while ( node ) {
// 1. Let result be the result of filtering node within walker.
auto result = TRY ( filter ( * node ) ) ;
// 2. If result is FILTER_ACCEPT, then set walker’ s current to node and return node.
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-12-14 13:43:57 +01:00
m_current = * node ;
2022-03-09 14:37:48 +01:00
return node ;
}
// 3. If result is FILTER_SKIP, then:
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_SKIP ) {
2022-03-09 14:37:48 +01:00
// 1. Let child be node’ s first child if type is first, and node’ s last child if type is last.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > child = type = = ChildTraversalType : : First ? node - > first_child ( ) : node - > last_child ( ) ;
2022-03-09 14:37:48 +01:00
// 2. If child is non-null, then set node to child and continue.
if ( child ) {
2022-08-28 13:42:07 +02:00
node = child ;
2022-03-09 14:37:48 +01:00
continue ;
}
}
// 4. While node is non-null:
while ( node ) {
// 1. Let sibling be node’ s next sibling if type is first, and node’ s previous sibling if type is last.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > sibling = type = = ChildTraversalType : : First ? node - > next_sibling ( ) : node - > previous_sibling ( ) ;
2022-03-09 14:37:48 +01:00
// 2. If sibling is non-null, then set node to sibling and break.
if ( sibling ) {
2022-08-28 13:42:07 +02:00
node = sibling ;
2022-03-09 14:37:48 +01:00
break ;
}
// 3. Let parent be node’ s parent.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > parent = node - > parent ( ) ;
2022-03-09 14:37:48 +01:00
// 4. If parent is null, walker’ s root, or walker’ s current, then return null.
if ( ! parent | | parent = = m_root | | parent = = m_current )
return nullptr ;
// 5. Set node to parent.
2022-08-28 13:42:07 +02:00
node = parent ;
2022-03-09 14:37:48 +01:00
}
}
// 4. Return null.
return nullptr ;
}
// https://dom.spec.whatwg.org/#concept-traverse-siblings
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < Node > > TreeWalker : : traverse_siblings ( SiblingTraversalType type )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be walker’ s current.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > node = m_current ;
2022-03-09 14:37:48 +01:00
// 2. If node is root, then return null.
if ( node = = m_root )
return nullptr ;
// 3. While true:
while ( true ) {
// 1. Let sibling be node’ s next sibling if type is next, and node’ s previous sibling if type is previous.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Node > sibling = type = = SiblingTraversalType : : Next ? node - > next_sibling ( ) : node - > previous_sibling ( ) ;
2022-03-09 14:37:48 +01:00
// 2. While sibling is non-null:
while ( sibling ) {
// 1. Set node to sibling.
node = sibling ;
// 2. Let result be the result of filtering node within walker.
auto result = TRY ( filter ( * node ) ) ;
// 3. If result is FILTER_ACCEPT, then set walker’ s current to node and return node.
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_ACCEPT ) {
2022-12-14 13:43:57 +01:00
m_current = * node ;
2022-03-09 14:37:48 +01:00
return node ;
}
// 4. Set sibling to node’ s first child if type is next, and node’ s last child if type is previous.
sibling = type = = SiblingTraversalType : : Next ? node - > first_child ( ) : node - > last_child ( ) ;
// 5. If result is FILTER_REJECT or sibling is null, then set sibling to node’ s next sibling if type is next, and node’ s previous sibling if type is previous.
2023-01-27 13:22:36 +00:00
if ( result = = NodeFilter : : Result : : FILTER_REJECT | | ! sibling )
2022-03-09 14:37:48 +01:00
sibling = type = = SiblingTraversalType : : Next ? node - > next_sibling ( ) : node - > previous_sibling ( ) ;
}
// 3. Set node to node’ s parent.
node = node - > parent ( ) ;
// 4. If node is null or walker’ s root, then return null.
if ( ! node | | node = = m_root )
return nullptr ;
// 5. If the return value of filtering node within walker is FILTER_ACCEPT, then return null.
2023-01-27 13:22:36 +00:00
if ( TRY ( filter ( * node ) ) = = NodeFilter : : Result : : FILTER_ACCEPT )
2022-03-09 14:37:48 +01:00
return nullptr ;
}
}
}