summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/DOM/NodeIterator.cpp
blob: efe2bacb347ba422157660adf250bf89cdf45142 (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
/*
 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibWeb/Bindings/DOMExceptionWrapper.h>
#include <LibWeb/Bindings/IDLAbstractOperations.h>
#include <LibWeb/Bindings/NodeWrapper.h>
#include <LibWeb/Bindings/NodeWrapperFactory.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/NodeIterator.h>

namespace Web::DOM {

NodeIterator::NodeIterator(Node& root)
    : m_root(root)
    , m_reference({ root })
{
    root.document().register_node_iterator({}, *this);
}

NodeIterator::~NodeIterator()
{
    m_root->document().unregister_node_iterator({}, *this);
}

// https://dom.spec.whatwg.org/#dom-document-createnodeiterator
NonnullRefPtr<NodeIterator> NodeIterator::create(Node& root, unsigned what_to_show, RefPtr<NodeFilter> filter)
{
    // 1. Let iterator be a new NodeIterator object.
    // 2. Set iterator’s root and iterator’s reference to root.
    // 3. Set iterator’s pointer before reference to true.
    auto iterator = adopt_ref(*new NodeIterator(root));

    // 4. Set iterator’s whatToShow to whatToShow.
    iterator->m_what_to_show = what_to_show;

    // 5. Set iterator’s filter to filter.
    iterator->m_filter = move(filter);

    // 6. Return iterator.
    return iterator;
}

// https://dom.spec.whatwg.org/#dom-nodeiterator-detach
void NodeIterator::detach()
{
    // The detach() method steps are to do nothing.
    // Its functionality (disabling a NodeIterator object) was removed, but the method itself is preserved for compatibility.
}

// https://dom.spec.whatwg.org/#concept-nodeiterator-traverse
JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::traverse(Direction direction)
{
    // 1. Let node be iterator’s reference.
    // 2. Let beforeNode be iterator’s pointer before reference.
    m_traversal_pointer = m_reference;

    RefPtr<Node> candidate;

    // 3. While true:
    while (true) {
        // 4. Branch on direction:
        if (direction == Direction::Next) {
            // next
            // If beforeNode is false, then set node to the first node following node in iterator’s iterator collection.
            // If there is no such node, then return null.
            if (!m_traversal_pointer->is_before_node) {
                auto* next_node = m_traversal_pointer->node->next_in_pre_order(m_root.ptr());
                if (!next_node)
                    return nullptr;
                m_traversal_pointer->node = *next_node;
            } else {
                // If beforeNode is true, then set it to false.
                m_traversal_pointer->is_before_node = false;
            }
        } else {
            // previous
            // If beforeNode is true, then set node to the first node preceding node in iterator’s iterator collection.
            // If there is no such node, then return null.
            if (m_traversal_pointer->is_before_node) {
                if (m_traversal_pointer->node == m_root.ptr())
                    return nullptr;
                auto* previous_node = m_traversal_pointer->node->previous_in_pre_order();
                if (!previous_node)
                    return nullptr;
                m_traversal_pointer->node = *previous_node;
            } else {
                // If beforeNode is false, then set it to true.
                m_traversal_pointer->is_before_node = true;
            }
        }

        // NOTE: If the NodeFilter deletes the iterator's current traversal pointer,
        //       we will automatically retarget it. However, in that case, we're expected
        //       to return the node passed to the filter, not the adjusted traversal pointer's
        //       node after the filter returns!
        candidate = m_traversal_pointer->node;

        // 2. Let result be the result of filtering node within iterator.
        auto result = TRY(filter(*m_traversal_pointer->node));

        // 3. If result is FILTER_ACCEPT, then break.
        if (result == NodeFilter::FILTER_ACCEPT)
            break;
    }

    // 4. Set iterator’s reference to node.
    // 5. Set iterator’s pointer before reference to beforeNode.
    m_reference = m_traversal_pointer.release_value();

    // 6. Return node.
    return candidate;
}

// https://dom.spec.whatwg.org/#concept-node-filter
JS::ThrowCompletionOr<NodeFilter::Result> NodeIterator::filter(Node& node)
{
    VERIFY(wrapper());
    auto& global_object = wrapper()->global_object();

    // 1. If traverser’s active flag is set, then throw an "InvalidStateError" DOMException.
    if (m_active)
        return JS::throw_completion(wrap(global_object, InvalidStateError::create("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 = Bindings::IDL::call_user_object_operation(m_filter->callback(), "acceptNode", {}, wrap(global_object, 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(global_object));
    return static_cast<NodeFilter::Result>(result_value);
}

// https://dom.spec.whatwg.org/#dom-nodeiterator-nextnode
JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::next_node()
{
    return traverse(Direction::Next);
}

// https://dom.spec.whatwg.org/#dom-nodeiterator-previousnode
JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::previous_node()
{
    return traverse(Direction::Previous);
}

void NodeIterator::run_pre_removing_steps_with_node_pointer(Node& to_be_removed_node, NodePointer& pointer)
{
    // NOTE: This function tries to match the behavior of other engines, but not the DOM specification
    //       as it's a known issue that the spec doesn't match how major browsers behave.
    //       Spec bug: https://github.com/whatwg/dom/issues/907

    if (!to_be_removed_node.is_descendant_of(root()))
        return;

    if (!to_be_removed_node.is_inclusive_ancestor_of(pointer.node))
        return;

    if (pointer.is_before_node) {
        if (auto* node = to_be_removed_node.next_in_pre_order(root())) {
            while (node && node->is_descendant_of(to_be_removed_node))
                node = node->next_in_pre_order(root());
            if (node)
                pointer.node = *node;
            return;
        }
        if (auto* node = to_be_removed_node.previous_in_pre_order()) {
            if (to_be_removed_node.is_ancestor_of(pointer.node)) {
                while (node && node->is_descendant_of(to_be_removed_node))
                    node = node->previous_in_pre_order();
            }
            if (node) {
                pointer = {
                    .node = *node,
                    .is_before_node = false,
                };
            }
        }
        return;
    }

    if (auto* node = to_be_removed_node.previous_in_pre_order()) {
        if (to_be_removed_node.is_ancestor_of(pointer.node)) {
            while (node && node->is_descendant_of(to_be_removed_node))
                node = node->previous_in_pre_order();
        }
        if (node)
            pointer.node = *node;
        return;
    }
    auto* node = to_be_removed_node.next_in_pre_order(root());
    if (to_be_removed_node.is_ancestor_of(pointer.node)) {
        while (node && node->is_descendant_of(to_be_removed_node))
            node = node->previous_in_pre_order();
    }
    if (node)
        pointer.node = *node;
}

// https://dom.spec.whatwg.org/#nodeiterator-pre-removing-steps
void NodeIterator::run_pre_removing_steps(Node& to_be_removed_node)
{
    // NOTE: If we're in the middle of traversal, we have to adjust the traversal pointer in response to node removal.
    if (m_traversal_pointer.has_value())
        run_pre_removing_steps_with_node_pointer(to_be_removed_node, *m_traversal_pointer);

    run_pre_removing_steps_with_node_pointer(to_be_removed_node, m_reference);
}

}