script/dom/node/treewalker.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use script_bindings::callback::{OwnerWindow, RootedCallback, TracedCallback};
10use script_bindings::reflector::{Reflector, reflect_dom_object};
11use script_bindings::script_runtime::temp_cx;
12
13use crate::dom::bindings::callback::ExceptionHandling::Rethrow;
14use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
15use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::{NodeFilter, NodeFilterConstants};
16use crate::dom::bindings::codegen::Bindings::TreeWalkerBinding::TreeWalkerMethods;
17use crate::dom::bindings::error::{Error, Fallible};
18use crate::dom::bindings::root::{Dom, DomRoot, MutDom};
19use crate::dom::document::Document;
20use crate::dom::node::Node;
21
22// https://dom.spec.whatwg.org/#interface-treewalker
23#[dom_struct]
24pub(crate) struct TreeWalker {
25 reflector_: Reflector,
26 root_node: Dom<Node>,
27 current_node: MutDom<Node>,
28 what_to_show: u32,
29 #[ignore_malloc_size_of = "function pointers and Rc<T> are hard"]
30 filter: Filter,
31 active: Cell<bool>,
32}
33
34impl TreeWalker {
35 fn new_inherited(
36 root_node: &Node,
37 what_to_show: u32,
38 node_filter: Option<RootedCallback<NodeFilter>>,
39 ) -> TreeWalker {
40 TreeWalker {
41 reflector_: Reflector::new(),
42 root_node: Dom::from_ref(root_node),
43 current_node: MutDom::new(root_node),
44 what_to_show,
45 filter: match node_filter {
46 None => Filter::None,
47 Some(jsfilter) => Filter::Dom(jsfilter.to_traced()),
48 },
49 active: Cell::new(false),
50 }
51 }
52
53 pub(crate) fn new_with_filter(
54 cx: &mut JSContext,
55 document: &Document,
56 root_node: &Node,
57 what_to_show: u32,
58 node_filter: Option<RootedCallback<NodeFilter>>,
59 ) -> DomRoot<TreeWalker> {
60 reflect_dom_object(
61 cx,
62 Box::new(TreeWalker::new_inherited(
63 root_node,
64 what_to_show,
65 node_filter,
66 )),
67 document.window(),
68 )
69 }
70
71 pub(crate) fn new(
72 cx: &mut JSContext,
73 document: &Document,
74 root_node: &Node,
75 what_to_show: u32,
76 node_filter: Option<RootedCallback<NodeFilter>>,
77 ) -> DomRoot<TreeWalker> {
78 TreeWalker::new_with_filter(cx, document, root_node, what_to_show, node_filter)
79 }
80}
81
82impl TreeWalkerMethods<crate::DomTypeHolder> for TreeWalker {
83 /// <https://dom.spec.whatwg.org/#dom-treewalker-root>
84 fn Root(&self) -> DomRoot<Node> {
85 DomRoot::from_ref(&*self.root_node)
86 }
87
88 /// <https://dom.spec.whatwg.org/#dom-treewalker-whattoshow>
89 fn WhatToShow(&self) -> u32 {
90 self.what_to_show
91 }
92
93 /// <https://dom.spec.whatwg.org/#dom-treewalker-filter>
94 fn GetFilter(&self, cx: &JSContext) -> Option<RootedCallback<NodeFilter>> {
95 match self.filter {
96 Filter::None => None,
97 Filter::Dom(ref nf) => Some(nf.root(cx)),
98 }
99 }
100
101 /// <https://dom.spec.whatwg.org/#dom-treewalker-currentnode>
102 fn CurrentNode(&self) -> DomRoot<Node> {
103 self.current_node.get()
104 }
105
106 /// <https://dom.spec.whatwg.org/#dom-treewalker-currentnode>
107 fn SetCurrentNode(&self, node: &Node) {
108 self.current_node.set(node);
109 }
110
111 /// <https://dom.spec.whatwg.org/#dom-treewalker-parentnode>
112 fn ParentNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
113 // "1. Let node be the value of the currentNode attribute."
114 let mut node = self.current_node.get();
115 // "2. While node is not null and is not root, run these substeps:"
116 while !self.is_root_node(&node) {
117 // "1. Let node be node's parent."
118 match node.GetParentNode() {
119 Some(n) => {
120 node = n;
121 // "2. If node is not null and filtering node returns FILTER_ACCEPT,
122 // then set the currentNode attribute to node, return node."
123 if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(cx, &node)? {
124 self.current_node.set(&node);
125 return Ok(Some(node));
126 }
127 },
128 None => break,
129 }
130 }
131 // "3. Return null."
132 Ok(None)
133 }
134
135 /// <https://dom.spec.whatwg.org/#dom-treewalker-firstchild>
136 fn FirstChild(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
137 // "The firstChild() method must traverse children of type first."
138 self.traverse_children(
139 cx,
140 |node| node.GetFirstChild(),
141 |node| node.GetNextSibling(),
142 )
143 }
144
145 /// <https://dom.spec.whatwg.org/#dom-treewalker-lastchild>
146 fn LastChild(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
147 // "The lastChild() method must traverse children of type last."
148 self.traverse_children(
149 cx,
150 |node| node.GetLastChild(),
151 |node| node.GetPreviousSibling(),
152 )
153 }
154
155 /// <https://dom.spec.whatwg.org/#dom-treewalker-previoussibling>
156 fn PreviousSibling(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
157 // "The nextSibling() method must traverse siblings of type next."
158 self.traverse_siblings(
159 cx,
160 |node| node.GetLastChild(),
161 |node| node.GetPreviousSibling(),
162 )
163 }
164
165 /// <https://dom.spec.whatwg.org/#dom-treewalker-nextsibling>
166 fn NextSibling(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
167 // "The previousSibling() method must traverse siblings of type previous."
168 self.traverse_siblings(
169 cx,
170 |node| node.GetFirstChild(),
171 |node| node.GetNextSibling(),
172 )
173 }
174
175 /// <https://dom.spec.whatwg.org/#dom-treewalker-previousnode>
176 fn PreviousNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
177 // "1. Let node be the value of the currentNode attribute."
178 let mut node = self.current_node.get();
179 // "2. While node is not root, run these substeps:"
180 while !self.is_root_node(&node) {
181 // "1. Let sibling be the previous sibling of node."
182 let mut sibling_op = node.GetPreviousSibling();
183 // "2. While sibling is not null, run these subsubsteps:"
184 while sibling_op.is_some() {
185 // "1. Set node to sibling."
186 node = sibling_op.unwrap();
187 // "2. Filter node and let result be the return value."
188 // "3. While result is not FILTER_REJECT and node has a child,
189 // set node to its last child and then filter node and
190 // set result to the return value."
191 // "4. If result is FILTER_ACCEPT, then
192 // set the currentNode attribute to node and return node."
193 loop {
194 let result = self.accept_node(cx, &node)?;
195 match result {
196 NodeFilterConstants::FILTER_REJECT => break,
197 _ if node.GetFirstChild().is_some() => node = node.GetLastChild().unwrap(),
198 NodeFilterConstants::FILTER_ACCEPT => {
199 self.current_node.set(&node);
200 return Ok(Some(node));
201 },
202 _ => break,
203 }
204 }
205 // "5. Set sibling to the previous sibling of node."
206 sibling_op = node.GetPreviousSibling()
207 }
208 // "3. If node is root or node's parent is null, return null."
209 if self.is_root_node(&node) || node.GetParentNode().is_none() {
210 return Ok(None);
211 }
212 // "4. Set node to its parent."
213 match node.GetParentNode() {
214 None =>
215 // This can happen if the user set the current node to somewhere
216 // outside of the tree rooted at the original root.
217 {
218 return Ok(None);
219 },
220 Some(n) => node = n,
221 }
222 // "5. Filter node and if the return value is FILTER_ACCEPT, then
223 // set the currentNode attribute to node and return node."
224 if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(cx, &node)? {
225 self.current_node.set(&node);
226 return Ok(Some(node));
227 }
228 }
229 // "6. Return null."
230 Ok(None)
231 }
232
233 /// <https://dom.spec.whatwg.org/#dom-treewalker-nextnode>
234 fn NextNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
235 // "1. Let node be the value of the currentNode attribute."
236 let mut node = self.current_node.get();
237 // "2. Let result be FILTER_ACCEPT."
238 let mut result = NodeFilterConstants::FILTER_ACCEPT;
239 // "3. Run these substeps:"
240 loop {
241 // "1. While result is not FILTER_REJECT and node has a child, run these subsubsteps:"
242 loop {
243 if NodeFilterConstants::FILTER_REJECT == result {
244 break;
245 }
246 match node.GetFirstChild() {
247 None => break,
248 Some(child) => {
249 // "1. Set node to its first child."
250 node = child;
251 // "2. Filter node and set result to the return value."
252 result = self.accept_node(cx, &node)?;
253 // "3. If result is FILTER_ACCEPT, then
254 // set the currentNode attribute to node and return node."
255 if NodeFilterConstants::FILTER_ACCEPT == result {
256 self.current_node.set(&node);
257 return Ok(Some(node));
258 }
259 },
260 }
261 }
262 // "2. If a node is following node and is not following root,
263 // set node to the first such node."
264 // "Otherwise, return null."
265 match self.first_following_node_not_following_root(&node) {
266 None => return Ok(None),
267 Some(n) => {
268 node = n;
269 // "3. Filter node and set result to the return value."
270 result = self.accept_node(cx, &node)?;
271 // "4. If result is FILTER_ACCEPT, then
272 // set the currentNode attribute to node and return node."
273 if NodeFilterConstants::FILTER_ACCEPT == result {
274 self.current_node.set(&node);
275 return Ok(Some(node));
276 }
277 },
278 }
279 // "5. Run these substeps again."
280 }
281 }
282}
283
284impl TreeWalker {
285 /// <https://dom.spec.whatwg.org/#concept-traverse-children>
286 fn traverse_children<F, G>(
287 &self,
288 cx: &mut JSContext,
289 next_child: F,
290 next_sibling: G,
291 ) -> Fallible<Option<DomRoot<Node>>>
292 where
293 F: Fn(&Node) -> Option<DomRoot<Node>>,
294 G: Fn(&Node) -> Option<DomRoot<Node>>,
295 {
296 // "To **traverse children** of type *type*, run these steps:"
297 // "1. Let node be the value of the currentNode attribute."
298 let cur = self.current_node.get();
299
300 // "2. Set node to node's first child if type is first, and node's last child if type is last."
301 // "3. If node is null, return null."
302 let mut node = match next_child(&cur) {
303 Some(node) => node,
304 None => return Ok(None),
305 };
306
307 // 4. Main: Repeat these substeps:
308 'main: loop {
309 // "1. Filter node and let result be the return value."
310 let result = self.accept_node(cx, &node)?;
311 match result {
312 // "2. If result is FILTER_ACCEPT, then set the currentNode
313 // attribute to node and return node."
314 NodeFilterConstants::FILTER_ACCEPT => {
315 self.current_node.set(&node);
316 return Ok(Some(DomRoot::from_ref(&node)));
317 },
318 // "3. If result is FILTER_SKIP, run these subsubsteps:"
319 NodeFilterConstants::FILTER_SKIP => {
320 // "1. Let child be node's first child if type is first,
321 // and node's last child if type is last."
322 if let Some(child) = next_child(&node) {
323 // "2. If child is not null, set node to child and goto Main."
324 node = child;
325 continue 'main;
326 }
327 },
328 _ => {},
329 }
330 // "4. Repeat these subsubsteps:"
331 loop {
332 // "1. Let sibling be node's next sibling if type is next,
333 // and node's previous sibling if type is previous."
334 match next_sibling(&node) {
335 // "2. If sibling is not null,
336 // set node to sibling and goto Main."
337 Some(sibling) => {
338 node = sibling;
339 continue 'main;
340 },
341 None => {
342 // "3. Let parent be node's parent."
343 match node.GetParentNode() {
344 // "4. If parent is null, parent is root,
345 // or parent is currentNode attribute's value,
346 // return null."
347 None => return Ok(None),
348 Some(ref parent)
349 if self.is_root_node(parent) || self.is_current_node(parent) =>
350 {
351 return Ok(None);
352 },
353 // "5. Otherwise, set node to parent."
354 Some(parent) => node = parent,
355 }
356 },
357 }
358 }
359 }
360 }
361
362 /// <https://dom.spec.whatwg.org/#concept-traverse-siblings>
363 fn traverse_siblings<F, G>(
364 &self,
365 cx: &mut JSContext,
366 next_child: F,
367 next_sibling: G,
368 ) -> Fallible<Option<DomRoot<Node>>>
369 where
370 F: Fn(&Node) -> Option<DomRoot<Node>>,
371 G: Fn(&Node) -> Option<DomRoot<Node>>,
372 {
373 // "To **traverse siblings** of type *type* run these steps:"
374 // "1. Let node be the value of the currentNode attribute."
375 let mut node = self.current_node.get();
376 // "2. If node is root, return null."
377 if self.is_root_node(&node) {
378 return Ok(None);
379 }
380 // "3. Run these substeps:"
381 loop {
382 // "1. Let sibling be node's next sibling if type is next,
383 // and node's previous sibling if type is previous."
384 let mut sibling_op = next_sibling(&node);
385 // "2. While sibling is not null, run these subsubsteps:"
386 while sibling_op.is_some() {
387 // "1. Set node to sibling."
388 node = sibling_op.unwrap();
389 // "2. Filter node and let result be the return value."
390 let result = self.accept_node(cx, &node)?;
391 // "3. If result is FILTER_ACCEPT, then set the currentNode
392 // attribute to node and return node."
393 if NodeFilterConstants::FILTER_ACCEPT == result {
394 self.current_node.set(&node);
395 return Ok(Some(node));
396 }
397
398 // "4. Set sibling to node's first child if type is next,
399 // and node's last child if type is previous."
400 sibling_op = next_child(&node);
401 // "5. If result is FILTER_REJECT or sibling is null,
402 // then set sibling to node's next sibling if type is next,
403 // and node's previous sibling if type is previous."
404 match (result, &sibling_op) {
405 (NodeFilterConstants::FILTER_REJECT, _) | (_, &None) => {
406 sibling_op = next_sibling(&node)
407 },
408 _ => {},
409 }
410 }
411 // "3. Set node to its parent."
412 match node.GetParentNode() {
413 // "4. If node is null or is root, return null."
414 None => return Ok(None),
415 Some(ref n) if self.is_root_node(n) => return Ok(None),
416 // "5. Filter node and if the return value is FILTER_ACCEPT, then return null."
417 Some(n) => {
418 node = n;
419 if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(cx, &node)? {
420 return Ok(None);
421 }
422 },
423 }
424 // "6. Run these substeps again."
425 }
426 }
427
428 /// <https://dom.spec.whatwg.org/#concept-tree-following>
429 fn first_following_node_not_following_root(&self, node: &Node) -> Option<DomRoot<Node>> {
430 // "An object A is following an object B if A and B are in the same tree
431 // and A comes after B in tree order."
432 match node.GetNextSibling() {
433 None => {
434 let mut candidate = DomRoot::from_ref(node);
435 while !self.is_root_node(&candidate) && candidate.GetNextSibling().is_none() {
436 // This can return None if the user set the current node to somewhere
437 // outside of the tree rooted at the original root.
438 candidate = candidate.GetParentNode()?;
439 }
440 if self.is_root_node(&candidate) {
441 None
442 } else {
443 candidate.GetNextSibling()
444 }
445 },
446 it => it,
447 }
448 }
449
450 /// <https://dom.spec.whatwg.org/#concept-node-filter>
451 fn accept_node(&self, cx: &mut JSContext, node: &Node) -> Fallible<u16> {
452 // Step 1.
453 if self.active.get() {
454 return Err(Error::InvalidState(Some(
455 "TreeWalker cannot be active".into(),
456 )));
457 }
458 // Step 2.
459 let n = node.NodeType() - 1;
460 // Step 3.
461 if (self.what_to_show & (1 << n)) == 0 {
462 return Ok(NodeFilterConstants::FILTER_SKIP);
463 }
464 match self.filter {
465 // Step 4.
466 Filter::None => Ok(NodeFilterConstants::FILTER_ACCEPT),
467 Filter::Dom(ref callback) => {
468 // Step 5.
469 self.active.set(true);
470 // Step 6.
471 let result = callback.AcceptNode_(cx, self, node, Rethrow);
472 // Step 7.
473 self.active.set(false);
474 // Step 8.
475 result
476 },
477 }
478 }
479
480 fn is_root_node(&self, node: &Node) -> bool {
481 Dom::from_ref(node) == self.root_node
482 }
483
484 fn is_current_node(&self, node: &Node) -> bool {
485 node == &*self.current_node.get()
486 }
487}
488
489impl Iterator for &TreeWalker {
490 type Item = DomRoot<Node>;
491
492 #[expect(unsafe_code)]
493 fn next(&mut self) -> Option<DomRoot<Node>> {
494 // TODO: https://github.com/servo/servo/issues/43311
495 let mut cx = unsafe { temp_cx() };
496 match self.NextNode(&mut cx) {
497 Ok(node) => node,
498 Err(_) =>
499 // The Err path happens only when a JavaScript
500 // NodeFilter throws an exception. This iterator
501 // is meant for internal use from Rust code, which
502 // will probably be using a native Rust filter,
503 // which cannot produce an Err result.
504 {
505 unreachable!()
506 },
507 }
508 }
509}
510
511#[derive(JSTraceable)]
512#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
513pub(crate) enum Filter {
514 None,
515 Dom(TracedCallback<NodeFilter>),
516}
517
518impl OwnerWindow<crate::DomTypeHolder> for TreeWalker {}