/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 | | } |