2022-03-09 14:37:48 +01:00
/*
* Copyright ( c ) 2022 , Andreas Kling < kling @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-09-30 17:16:16 -06:00
# include <LibWeb/Bindings/Intrinsics.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 {
TreeWalker : : TreeWalker ( Node & root )
2023-01-10 06:56:59 -05:00
: PlatformObject ( root . 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-01-28 12:33:35 -05:00
JS : : ThrowCompletionOr < void > TreeWalker : : initialize ( JS : : Realm & realm )
2023-01-10 06:56:59 -05:00
{
2023-01-28 12:33:35 -05:00
MUST_OR_THROW_OOM ( Base : : initialize ( realm ) ) ;
2023-01-10 06:56:59 -05:00
set_prototype ( & Bindings : : ensure_web_prototype < Bindings : : TreeWalkerPrototype > ( realm , " TreeWalker " ) ) ;
2023-01-28 12:33:35 -05:00
return { } ;
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 ) ;
visitor . visit ( m_filter . ptr ( ) ) ;
2022-08-28 13:42:07 +02:00
visitor . visit ( m_root . ptr ( ) ) ;
visitor . visit ( m_current . ptr ( ) ) ;
2022-08-08 23:03:27 +02:00
}
2022-03-09 14:37:48 +01:00
// https://dom.spec.whatwg.org/#dom-document-createtreewalker
2023-02-14 21:41:47 +01:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < TreeWalker > > TreeWalker : : create ( Node & root , unsigned what_to_show , JS : : GCPtr < 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.
2022-09-30 17:16:16 -06:00
auto & realm = root . realm ( ) ;
2023-02-14 21:41:47 +01:00
auto walker = MUST_OR_THROW_OOM ( realm . heap ( ) . allocate < 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
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < Node > > TreeWalker : : parent_node ( )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be this’ s current.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < Node > > TreeWalker : : previous_node ( )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be this’ s current.
2022-12-14 13:43:57 +01:00
JS : : NonnullGCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < Node > > TreeWalker : : next_node ( )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be this’ s current.
2022-12-14 13:43:57 +01:00
JS : : NonnullGCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < Node > sibling = nullptr ;
2022-03-09 14:37:48 +01:00
// 3. Let temporary be node.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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 ( ) ;
}
// 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 ;
}
}
}
// 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 )
2022-09-25 16:15:49 -06:00
return throw_completion ( WebIDL : : InvalidStateError : : create ( realm ( ) , " NodeIterator is already active " ) ) ;
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.
2022-09-24 16:14:37 +01:00
auto result = WebIDL : : call_user_object_operation ( m_filter - > callback ( ) , " acceptNode " , { } , & 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.
2022-08-08 23:03:27 +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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < Node > > TreeWalker : : traverse_children ( ChildTraversalType type )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be walker’ s current.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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
2022-08-28 13:42:07 +02:00
JS : : ThrowCompletionOr < JS : : GCPtr < Node > > TreeWalker : : traverse_siblings ( SiblingTraversalType type )
2022-03-09 14:37:48 +01:00
{
// 1. Let node be walker’ s current.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < 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 ;
}
}
}