Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/DOM/TreeWalker.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibWeb/Bindings/Intrinsics.h>
8
#include <LibWeb/Bindings/TreeWalkerPrototype.h>
9
#include <LibWeb/DOM/Node.h>
10
#include <LibWeb/DOM/NodeFilter.h>
11
#include <LibWeb/DOM/TreeWalker.h>
12
#include <LibWeb/WebIDL/AbstractOperations.h>
13
#include <LibWeb/WebIDL/DOMException.h>
14
15
namespace Web::DOM {
16
17
JS_DEFINE_ALLOCATOR(TreeWalker);
18
19
TreeWalker::TreeWalker(Node& root)
20
0
    : PlatformObject(root.realm())
21
0
    , m_root(root)
22
0
    , m_current(root)
23
0
{
24
0
}
25
26
0
TreeWalker::~TreeWalker() = default;
27
28
void TreeWalker::initialize(JS::Realm& realm)
29
0
{
30
0
    Base::initialize(realm);
31
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(TreeWalker);
32
0
}
33
34
void TreeWalker::visit_edges(Cell::Visitor& visitor)
35
0
{
36
0
    Base::visit_edges(visitor);
37
0
    visitor.visit(m_filter);
38
0
    visitor.visit(m_root);
39
0
    visitor.visit(m_current);
40
0
}
41
42
// https://dom.spec.whatwg.org/#dom-document-createtreewalker
43
JS::NonnullGCPtr<TreeWalker> TreeWalker::create(Node& root, unsigned what_to_show, JS::GCPtr<NodeFilter> filter)
44
0
{
45
    // 1. Let walker be a new TreeWalker object.
46
    // 2. Set walker’s root and walker’s current to root.
47
0
    auto& realm = root.realm();
48
0
    auto walker = realm.heap().allocate<TreeWalker>(realm, root);
49
50
    // 3. Set walker’s whatToShow to whatToShow.
51
0
    walker->m_what_to_show = what_to_show;
52
53
    // 4. Set walker’s filter to filter.
54
0
    walker->m_filter = filter;
55
56
    // 5. Return walker.
57
0
    return walker;
58
0
}
59
60
// https://dom.spec.whatwg.org/#dom-treewalker-currentnode
61
JS::NonnullGCPtr<Node> TreeWalker::current_node() const
62
0
{
63
0
    return *m_current;
64
0
}
65
66
// https://dom.spec.whatwg.org/#dom-treewalker-currentnode
67
void TreeWalker::set_current_node(Node& node)
68
0
{
69
0
    m_current = node;
70
0
}
71
72
// https://dom.spec.whatwg.org/#dom-treewalker-parentnode
73
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::parent_node()
74
0
{
75
    // 1. Let node be this’s current.
76
0
    JS::GCPtr<Node> node = m_current;
77
78
    // 2. While node is non-null and is not this’s root:
79
0
    while (node && node != m_root) {
80
        // 1. Set node to node’s parent.
81
0
        node = node->parent();
82
83
        // 2. If node is non-null and filtering node within this returns FILTER_ACCEPT,
84
        //    then set this’s current to node and return node.
85
0
        if (node) {
86
0
            auto result = TRY(filter(*node));
87
0
            if (result == NodeFilter::Result::FILTER_ACCEPT) {
88
0
                m_current = *node;
89
0
                return node;
90
0
            }
91
0
        }
92
0
    }
93
94
0
    return nullptr;
95
0
}
96
97
// https://dom.spec.whatwg.org/#dom-treewalker-firstchild
98
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::first_child()
99
0
{
100
0
    return traverse_children(ChildTraversalType::First);
101
0
}
102
103
// https://dom.spec.whatwg.org/#dom-treewalker-lastchild
104
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::last_child()
105
0
{
106
0
    return traverse_children(ChildTraversalType::Last);
107
0
}
108
109
// https://dom.spec.whatwg.org/#dom-treewalker-previoussibling
110
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::previous_sibling()
111
0
{
112
0
    return traverse_siblings(SiblingTraversalType::Previous);
113
0
}
114
115
// https://dom.spec.whatwg.org/#dom-treewalker-nextsibling
116
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::next_sibling()
117
0
{
118
0
    return traverse_siblings(SiblingTraversalType::Next);
119
0
}
120
121
// https://dom.spec.whatwg.org/#dom-treewalker-previousnode
122
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::previous_node()
123
0
{
124
    // 1. Let node be this’s current.
125
0
    JS::NonnullGCPtr<Node> node = m_current;
126
127
    // 2. While node is not this’s root:
128
0
    while (node != m_root) {
129
        // 1. Let sibling be node’s previous sibling.
130
0
        JS::GCPtr<Node> sibling = node->previous_sibling();
131
132
        // 2. While sibling is non-null:
133
0
        while (sibling) {
134
            // 1. Set node to sibling.
135
0
            node = *sibling;
136
137
            // 2. Let result be the result of filtering node within this.
138
0
            auto result = TRY(filter(*node));
139
140
            // 3. While result is not FILTER_REJECT and node has a child:
141
0
            while (result != NodeFilter::Result::FILTER_REJECT && node->has_children()) {
142
                // 1. Set node to node’s last child.
143
0
                node = *node->last_child();
144
145
                // 2. Set result to the result of filtering node within this.
146
0
                result = TRY(filter(*node));
147
0
            }
148
149
            // 4. If result is FILTER_ACCEPT, then set this’s current to node and return node.
150
0
            if (result == NodeFilter::Result::FILTER_ACCEPT) {
151
0
                m_current = node;
152
0
                return node;
153
0
            }
154
155
            // 5. Set sibling to node’s previous sibling.
156
0
            sibling = node->previous_sibling();
157
0
        }
158
159
        // 3. If node is this’s root or node’s parent is null, then return null.
160
0
        if (node == m_root || !node->parent())
161
0
            return nullptr;
162
163
        // 4. Set node to node’s parent.
164
0
        node = *node->parent();
165
166
        // 5. If the return value of filtering node within this is FILTER_ACCEPT, then set this’s current to node and return node.
167
0
        if (TRY(filter(*node)) == NodeFilter::Result::FILTER_ACCEPT) {
168
0
            m_current = node;
169
0
            return node;
170
0
        }
171
0
    }
172
    // 3. Return null.
173
0
    return nullptr;
174
0
}
175
176
// https://dom.spec.whatwg.org/#dom-treewalker-nextnode
177
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::next_node()
178
0
{
179
    // 1. Let node be this’s current.
180
0
    JS::NonnullGCPtr<Node> node = m_current;
181
182
    // 2. Let result be FILTER_ACCEPT.
183
0
    auto result = NodeFilter::Result::FILTER_ACCEPT;
184
185
    // 3. While true:
186
0
    while (true) {
187
        // 1. While result is not FILTER_REJECT and node has a child:
188
0
        while (result != NodeFilter::Result::FILTER_REJECT && node->has_children()) {
189
            // 1. Set node to its first child.
190
0
            node = *node->first_child();
191
192
            // 2. Set result to the result of filtering node within this.
193
0
            result = TRY(filter(*node));
194
195
            // 3. If result is FILTER_ACCEPT, then set this’s current to node and return node.
196
0
            if (result == NodeFilter::Result::FILTER_ACCEPT) {
197
0
                m_current = *node;
198
0
                return node;
199
0
            }
200
0
        }
201
202
        // 2. Let sibling be null.
203
0
        JS::GCPtr<Node> sibling = nullptr;
204
205
        // 3. Let temporary be node.
206
0
        JS::GCPtr<Node> temporary = node;
207
208
        // 4. While temporary is non-null:
209
0
        while (temporary) {
210
            // 1. If temporary is this’s root, then return null.
211
0
            if (temporary == m_root)
212
0
                return nullptr;
213
214
            // 2. Set sibling to temporary’s next sibling.
215
0
            sibling = temporary->next_sibling();
216
217
            // 3. If sibling is non-null, then set node to sibling and break.
218
0
            if (sibling) {
219
0
                node = *sibling;
220
0
                break;
221
0
            }
222
223
            // 4. Set temporary to temporary’s parent.
224
0
            temporary = temporary->parent();
225
226
            // NON-STANDARD: If temporary is null, then return null.
227
            //               This prevents us from infinite looping if the current node is not connected.
228
            //               Spec bug: https://github.com/whatwg/dom/issues/1102
229
0
            if (temporary == nullptr) {
230
0
                return nullptr;
231
0
            }
232
0
        }
233
234
        // 5. Set result to the result of filtering node within this.
235
0
        result = TRY(filter(*node));
236
237
        // 6. If result is FILTER_ACCEPT, then set this’s current to node and return node.
238
0
        if (result == NodeFilter::Result::FILTER_ACCEPT) {
239
0
            m_current = *node;
240
0
            return node;
241
0
        }
242
0
    }
243
0
}
244
245
// https://dom.spec.whatwg.org/#concept-node-filter
246
JS::ThrowCompletionOr<NodeFilter::Result> TreeWalker::filter(Node& node)
247
0
{
248
    // 1. If traverser’s active flag is set, then throw an "InvalidStateError" DOMException.
249
0
    if (m_active)
250
0
        return throw_completion(WebIDL::InvalidStateError::create(realm(), "NodeIterator is already active"_string));
251
252
    // 2. Let n be node’s nodeType attribute value − 1.
253
0
    auto n = node.node_type() - 1;
254
255
    // 3. If the nth bit (where 0 is the least significant bit) of traverser’s whatToShow is not set, then return FILTER_SKIP.
256
0
    if (!(m_what_to_show & (1u << n)))
257
0
        return NodeFilter::Result::FILTER_SKIP;
258
259
    // 4. If traverser’s filter is null, then return FILTER_ACCEPT.
260
0
    if (!m_filter)
261
0
        return NodeFilter::Result::FILTER_ACCEPT;
262
263
    // 5. Set traverser’s active flag.
264
0
    m_active = true;
265
266
    // 6. Let result be the return value of call a user object’s operation with traverser’s filter, "acceptNode", and « node ».
267
    //    If this throws an exception, then unset traverser’s active flag and rethrow the exception.
268
0
    auto result = WebIDL::call_user_object_operation(m_filter->callback(), "acceptNode"_string, {}, &node);
269
0
    if (result.is_abrupt()) {
270
0
        m_active = false;
271
0
        return result;
272
0
    }
273
274
    // 7. Unset traverser’s active flag.
275
0
    m_active = false;
276
277
    // 8. Return result.
278
0
    auto result_value = TRY(result.value()->to_i32(vm()));
279
0
    return static_cast<NodeFilter::Result>(result_value);
280
0
}
281
282
// https://dom.spec.whatwg.org/#concept-traverse-children
283
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::traverse_children(ChildTraversalType type)
284
0
{
285
    // 1. Let node be walker’s current.
286
0
    JS::GCPtr<Node> node = m_current;
287
288
    // 2. Set node to node’s first child if type is first, and node’s last child if type is last.
289
0
    node = type == ChildTraversalType::First ? node->first_child() : node->last_child();
290
291
    // 3. While node is non-null:
292
0
    while (node) {
293
        // 1. Let result be the result of filtering node within walker.
294
0
        auto result = TRY(filter(*node));
295
296
        // 2. If result is FILTER_ACCEPT, then set walker’s current to node and return node.
297
0
        if (result == NodeFilter::Result::FILTER_ACCEPT) {
298
0
            m_current = *node;
299
0
            return node;
300
0
        }
301
302
        // 3. If result is FILTER_SKIP, then:
303
0
        if (result == NodeFilter::Result::FILTER_SKIP) {
304
            // 1. Let child be node’s first child if type is first, and node’s last child if type is last.
305
0
            JS::GCPtr<Node> child = type == ChildTraversalType::First ? node->first_child() : node->last_child();
306
307
            // 2. If child is non-null, then set node to child and continue.
308
0
            if (child) {
309
0
                node = child;
310
0
                continue;
311
0
            }
312
0
        }
313
314
        // 4. While node is non-null:
315
0
        while (node) {
316
            // 1. Let sibling be node’s next sibling if type is first, and node’s previous sibling if type is last.
317
0
            JS::GCPtr<Node> sibling = type == ChildTraversalType::First ? node->next_sibling() : node->previous_sibling();
318
319
            // 2. If sibling is non-null, then set node to sibling and break.
320
0
            if (sibling) {
321
0
                node = sibling;
322
0
                break;
323
0
            }
324
325
            // 3. Let parent be node’s parent.
326
0
            JS::GCPtr<Node> parent = node->parent();
327
328
            // 4. If parent is null, walker’s root, or walker’s current, then return null.
329
0
            if (!parent || parent == m_root || parent == m_current)
330
0
                return nullptr;
331
332
            // 5. Set node to parent.
333
0
            node = parent;
334
0
        }
335
0
    }
336
337
    // 4. Return null.
338
0
    return nullptr;
339
0
}
340
341
// https://dom.spec.whatwg.org/#concept-traverse-siblings
342
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::traverse_siblings(SiblingTraversalType type)
343
0
{
344
    // 1. Let node be walker’s current.
345
0
    JS::GCPtr<Node> node = m_current;
346
347
    // 2. If node is root, then return null.
348
0
    if (node == m_root)
349
0
        return nullptr;
350
351
    // 3. While true:
352
0
    while (true) {
353
        // 1. Let sibling be node’s next sibling if type is next, and node’s previous sibling if type is previous.
354
0
        JS::GCPtr<Node> sibling = type == SiblingTraversalType::Next ? node->next_sibling() : node->previous_sibling();
355
356
        // 2. While sibling is non-null:
357
0
        while (sibling) {
358
            // 1. Set node to sibling.
359
0
            node = sibling;
360
361
            // 2. Let result be the result of filtering node within walker.
362
0
            auto result = TRY(filter(*node));
363
364
            // 3. If result is FILTER_ACCEPT, then set walker’s current to node and return node.
365
0
            if (result == NodeFilter::Result::FILTER_ACCEPT) {
366
0
                m_current = *node;
367
0
                return node;
368
0
            }
369
370
            // 4. Set sibling to node’s first child if type is next, and node’s last child if type is previous.
371
0
            sibling = type == SiblingTraversalType::Next ? node->first_child() : node->last_child();
372
373
            // 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.
374
0
            if (result == NodeFilter::Result::FILTER_REJECT || !sibling)
375
0
                sibling = type == SiblingTraversalType::Next ? node->next_sibling() : node->previous_sibling();
376
0
        }
377
378
        // 3. Set node to node’s parent.
379
0
        node = node->parent();
380
381
        // 4. If node is null or walker’s root, then return null.
382
0
        if (!node || node == m_root)
383
0
            return nullptr;
384
385
        // 5. If the return value of filtering node within walker is FILTER_ACCEPT, then return null.
386
0
        if (TRY(filter(*node)) == NodeFilter::Result::FILTER_ACCEPT)
387
0
            return nullptr;
388
0
    }
389
0
}
390
391
}