Skip to main content

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