summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/DOM/TreeWalker.cpp
blob: 2711aab3dc32172ac35a6ae0bc0d0c5d6aa18a14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/*
 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/NodeFilter.h>
#include <LibWeb/DOM/TreeWalker.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/DOMException.h>

namespace Web::DOM {

TreeWalker::TreeWalker(Node& root)
    : PlatformObject(Bindings::cached_web_prototype(root.realm(), "TreeWalker"))
    , m_root(root)
    , m_current(root)
{
}

TreeWalker::~TreeWalker() = default;

void TreeWalker::visit_edges(Cell::Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_filter.ptr());
    visitor.visit(m_root.ptr());
    visitor.visit(m_current.ptr());
}

// https://dom.spec.whatwg.org/#dom-document-createtreewalker
JS::NonnullGCPtr<TreeWalker> TreeWalker::create(Node& root, unsigned what_to_show, JS::GCPtr<NodeFilter> filter)
{
    // 1. Let walker be a new TreeWalker object.
    // 2. Set walker’s root and walker’s current to root.
    auto& realm = root.realm();
    auto* walker = realm.heap().allocate<TreeWalker>(realm, root);

    // 3. Set walker’s whatToShow to whatToShow.
    walker->m_what_to_show = what_to_show;

    // 4. Set walker’s filter to filter.
    walker->m_filter = filter;

    // 5. Return walker.
    return *walker;
}

// https://dom.spec.whatwg.org/#dom-treewalker-currentnode
JS::NonnullGCPtr<Node> TreeWalker::current_node() const
{
    return *m_current;
}

// https://dom.spec.whatwg.org/#dom-treewalker-currentnode
void TreeWalker::set_current_node(Node& node)
{
    m_current = node;
}

// https://dom.spec.whatwg.org/#dom-treewalker-parentnode
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::parent_node()
{
    // 1. Let node be this’s current.
    JS::GCPtr<Node> node = m_current;

    // 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) {
            auto result = TRY(filter(*node));
            if (result == NodeFilter::FILTER_ACCEPT) {
                m_current = *node;
                return node;
            }
        }
    }

    return nullptr;
}

// https://dom.spec.whatwg.org/#dom-treewalker-firstchild
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::first_child()
{
    return traverse_children(ChildTraversalType::First);
}

// https://dom.spec.whatwg.org/#dom-treewalker-lastchild
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::last_child()
{
    return traverse_children(ChildTraversalType::Last);
}

// https://dom.spec.whatwg.org/#dom-treewalker-previoussibling
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::previous_sibling()
{
    return traverse_siblings(SiblingTraversalType::Previous);
}

// https://dom.spec.whatwg.org/#dom-treewalker-nextsibling
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::next_sibling()
{
    return traverse_siblings(SiblingTraversalType::Next);
}

// https://dom.spec.whatwg.org/#dom-treewalker-previousnode
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::previous_node()
{
    // 1. Let node be this’s current.
    JS::NonnullGCPtr<Node> node = m_current;

    // 2. While node is not this’s root:
    while (node != m_root) {
        // 1. Let sibling be node’s previous sibling.
        JS::GCPtr<Node> sibling = node->previous_sibling();

        // 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 this.
            auto result = TRY(filter(*node));

            // 3. While result is not FILTER_REJECT and node has a child:
            while (result != NodeFilter::FILTER_REJECT && node->has_children()) {
                // 1. Set node to node’s last child.
                node = *node->last_child();

                // 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.
            if (result == NodeFilter::FILTER_ACCEPT) {
                m_current = node;
                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.
        node = *node->parent();

        // 5. If the return value of filtering node within this is FILTER_ACCEPT, then set this’s current to node and return node.
        if (TRY(filter(*node)) == NodeFilter::FILTER_ACCEPT) {
            m_current = node;
            return node;
        }
    }
    // 3. Return null.
    return nullptr;
}

// https://dom.spec.whatwg.org/#dom-treewalker-nextnode
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::next_node()
{
    // 1. Let node be this’s current.
    JS::NonnullGCPtr<Node> node = m_current;

    // 2. Let result be FILTER_ACCEPT.
    auto result = NodeFilter::FILTER_ACCEPT;

    // 3. While true:
    while (true) {
        // 1. While result is not FILTER_REJECT and node has a child:
        while (result != NodeFilter::FILTER_REJECT && node->has_children()) {
            // 1. Set node to its first child.
            node = *node->first_child();

            // 2. Set result to the result of filtering node within this.
            auto result = TRY(filter(*node));

            // 3. If result is FILTER_ACCEPT, then set this’s current to node and return node.
            if (result == NodeFilter::FILTER_ACCEPT) {
                m_current = *node;
                return node;
            }
        }

        // 2. Let sibling be null.
        JS::GCPtr<Node> sibling = nullptr;

        // 3. Let temporary be node.
        JS::GCPtr<Node> temporary = node;

        // 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) {
                node = *sibling;
                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.
        if (result == NodeFilter::FILTER_ACCEPT) {
            m_current = *node;
            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)
        return throw_completion(WebIDL::InvalidStateError::create(realm(), "NodeIterator is already active"));

    // 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)))
        return NodeFilter::FILTER_SKIP;

    // 4. If traverser’s filter is null, then return FILTER_ACCEPT.
    if (!m_filter)
        return NodeFilter::FILTER_ACCEPT;

    // 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.
    auto result = WebIDL::call_user_object_operation(m_filter->callback(), "acceptNode", {}, &node);
    if (result.is_abrupt()) {
        m_active = false;
        return result;
    }

    // 7. Unset traverser’s active flag.
    m_active = false;

    // 8. Return result.
    auto result_value = TRY(result.value()->to_i32(vm()));
    return static_cast<NodeFilter::Result>(result_value);
}

// https://dom.spec.whatwg.org/#concept-traverse-children
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::traverse_children(ChildTraversalType type)
{
    // 1. Let node be walker’s current.
    JS::GCPtr<Node> node = m_current;

    // 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.
        if (result == NodeFilter::FILTER_ACCEPT) {
            m_current = *node;
            return node;
        }

        // 3. If result is FILTER_SKIP, then:
        if (result == NodeFilter::FILTER_SKIP) {
            // 1. Let child be node’s first child if type is first, and node’s last child if type is last.
            JS::GCPtr<Node> child = type == ChildTraversalType::First ? node->first_child() : node->last_child();

            // 2. If child is non-null, then set node to child and continue.
            if (child) {
                node = child;
                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.
            JS::GCPtr<Node> sibling = type == ChildTraversalType::First ? node->next_sibling() : node->previous_sibling();

            // 2. If sibling is non-null, then set node to sibling and break.
            if (sibling) {
                node = sibling;
                break;
            }

            // 3. Let parent be node’s parent.
            JS::GCPtr<Node> parent = node->parent();

            // 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.
            node = parent;
        }
    }

    // 4. Return null.
    return nullptr;
}

// https://dom.spec.whatwg.org/#concept-traverse-siblings
JS::ThrowCompletionOr<JS::GCPtr<Node>> TreeWalker::traverse_siblings(SiblingTraversalType type)
{
    // 1. Let node be walker’s current.
    JS::GCPtr<Node> node = m_current;

    // 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.
        JS::GCPtr<Node> sibling = type == SiblingTraversalType::Next ? node->next_sibling() : node->previous_sibling();

        // 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.
            if (result == NodeFilter::FILTER_ACCEPT) {
                m_current = *node;
                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.
            if (result == NodeFilter::FILTER_REJECT || !sibling)
                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.
        if (TRY(filter(*node)) == NodeFilter::FILTER_ACCEPT)
            return nullptr;
    }
}

}