Skip to main content

blitz_dom/
traversal.rs

1use blitz_traits::node_id::NodeId;
2use std::cmp::Ordering;
3
4use style::{dom::TNode as _, values::specified::box_::DisplayInside};
5
6use crate::{BaseDocument, Node};
7
8macro_rules! iter_children {
9    ($node_expr:expr, $cb:expr) => {{
10        // For shadow hosts and slots, iterate the flattened-tree children
11        // (cloned to avoid borrow conflicts with the callback).
12        #[cfg(feature = "shadow-dom")]
13        if $node_expr.flattened_children.is_some() {
14            let children = $node_expr.flattened_children.clone().unwrap();
15            for child_id in children {
16                $cb(child_id)
17            }
18        } else {
19            let node = &mut $node_expr;
20            let children = core::mem::take(&mut node.children);
21            for child_id in children.iter().copied() {
22                $cb(child_id)
23            }
24            $node_expr.children = children;
25        }
26        #[cfg(not(feature = "shadow-dom"))]
27        {
28            let node = &mut $node_expr;
29            let children = core::mem::take(&mut node.children);
30            for child_id in children.iter().copied() {
31                $cb(child_id)
32            }
33            $node_expr.children = children;
34        }
35    }};
36}
37pub(crate) use iter_children;
38
39macro_rules! iter_children_and_pseudos {
40    ($node_expr:expr, $cb:expr) => {{
41        // Load node
42        let node = &mut $node_expr;
43
44        // Copy before and after. Children are taken and put back below, once
45        // per branch, because which list to walk depends on whether this node
46        // has a flattened (shadow-composed) child list.
47        let before = node.before();
48        let after = node.after();
49
50        if let Some(before) = before {
51            $cb(before)
52        }
53
54        // For shadow hosts and slots, iterate the flattened-tree children.
55        #[cfg(feature = "shadow-dom")]
56        if $node_expr.flattened_children.is_some() {
57            let children = $node_expr.flattened_children.clone().unwrap();
58            for child_id in children {
59                $cb(child_id)
60            }
61        } else {
62            let children = core::mem::take(&mut $node_expr.children);
63            for child_id in children.iter().copied() {
64                $cb(child_id)
65            }
66            $node_expr.children = children;
67        }
68        #[cfg(not(feature = "shadow-dom"))]
69        {
70            let children = core::mem::take(&mut $node_expr.children);
71            for child_id in children.iter().copied() {
72                $cb(child_id)
73            }
74            $node_expr.children = children;
75        }
76
77        if let Some(after) = after {
78            $cb(after)
79        }
80    }};
81}
82pub(crate) use iter_children_and_pseudos;
83
84#[derive(Clone)]
85/// An pre-order tree traverser for a [BaseDocument](crate::document::BaseDocument).
86pub struct TreeTraverser<'a> {
87    doc: &'a BaseDocument,
88    stack: Vec<NodeId>,
89}
90
91impl<'a> TreeTraverser<'a> {
92    /// Creates a new tree traverser for the given document which starts at the root node.
93    pub fn new(doc: &'a BaseDocument) -> Self {
94        Self::new_with_root(doc, doc.root_node().id)
95    }
96
97    /// Creates a new tree traverser for the given document which starts at the specified node.
98    pub fn new_with_root(doc: &'a BaseDocument, root: NodeId) -> Self {
99        let mut stack = Vec::with_capacity(32);
100        stack.push(root);
101        TreeTraverser { doc, stack }
102    }
103}
104impl Iterator for TreeTraverser<'_> {
105    type Item = NodeId;
106
107    fn next(&mut self) -> Option<Self::Item> {
108        let id = self.stack.pop()?;
109        let node = self.doc.get_node(id)?;
110        self.stack.extend(node.children.iter().rev());
111        Some(id)
112    }
113}
114
115#[derive(Clone)]
116/// An ancestor traverser for a [BaseDocument](crate::document::BaseDocument).
117pub struct AncestorTraverser<'a> {
118    doc: &'a BaseDocument,
119    current: NodeId,
120}
121impl<'a> AncestorTraverser<'a> {
122    /// Creates a new ancestor traverser for the given document and node ID.
123    pub fn new(doc: &'a BaseDocument, node_id: NodeId) -> Self {
124        AncestorTraverser {
125            doc,
126            current: node_id,
127        }
128    }
129}
130impl Iterator for AncestorTraverser<'_> {
131    type Item = NodeId;
132
133    fn next(&mut self) -> Option<Self::Item> {
134        let current_node = self.doc.get_node(self.current)?;
135        self.current = current_node.parent?;
136        Some(self.current)
137    }
138}
139
140impl Node {
141    #[allow(dead_code)]
142    pub(crate) fn should_traverse_layout_children(&mut self) -> bool {
143        let prefer_layout_children = match self.display_constructed_as().inside() {
144            DisplayInside::None => return false,
145            DisplayInside::Contents => false,
146            DisplayInside::Flow | DisplayInside::FlowRoot | DisplayInside::TableCell => {
147                // Prefer layout children for "block" but not "inline" contexts
148                self.element_data()
149                    .is_none_or(|el| el.inline_layout_data.is_none())
150            }
151            DisplayInside::Flex | DisplayInside::Grid => true,
152            DisplayInside::Table => false,
153            DisplayInside::TableRowGroup => false,
154            DisplayInside::TableColumn => false,
155            DisplayInside::TableColumnGroup => false,
156            DisplayInside::TableHeaderGroup => false,
157            DisplayInside::TableFooterGroup => false,
158            DisplayInside::TableRow => false,
159        };
160        let has_layout_children = self.layout_children.get_mut().is_some();
161        prefer_layout_children & has_layout_children
162    }
163}
164
165impl BaseDocument {
166    /// Collect the nodes into a chain by traversing upwards
167    pub fn node_chain(&self, node_id: NodeId) -> Vec<NodeId> {
168        let mut chain = Vec::with_capacity(16);
169        chain.push(node_id);
170        chain.extend(
171            AncestorTraverser::new(self, node_id).filter(|id| self.nodes[*id].is_element()),
172        );
173        // An event bubbles to the Document after the last element, so the
174        // document node belongs on the propagation chain even though it is not
175        // an element and the ancestor filter above drops it. Without this,
176        // a listener registered on `document` never fires and `composedPath`
177        // is short by one.
178        let document_id = self.root_node().id;
179        if chain.last().copied() != Some(document_id) {
180            chain.push(document_id);
181        }
182        chain
183    }
184
185    pub fn visit<F>(&self, mut visit: F)
186    where
187        F: FnMut(NodeId, &Node),
188    {
189        TreeTraverser::new(self).for_each(|node_id| visit(node_id, &self.nodes[node_id]));
190    }
191
192    /// Visit rendered DOM nodes in flattened preorder. The parent is the
193    /// flattened parent, so root children attach to their host and assigned
194    /// nodes attach to their slot. Call after a rendering checkpoint.
195    pub fn visit_flattened<F>(&self, mut visit: F)
196    where
197        F: FnMut(NodeId, Option<NodeId>, &Node),
198    {
199        let mut stack = vec![(self.root_node_id, None)];
200        while let Some((id, parent)) = stack.pop() {
201            let Some(node) = self.get_node(id) else {
202                continue;
203            };
204            visit(id, parent, node);
205            stack.extend(
206                node.layout_dom_children()
207                    .iter()
208                    .rev()
209                    .map(|&child| (child, Some(id))),
210            );
211        }
212    }
213
214    /// If the node is non-anonymous then returns the node's id
215    /// Else find's the first non-anonymous ancester of the node
216    pub fn non_anon_ancestor_if_anon(&self, mut node_id: NodeId) -> NodeId {
217        loop {
218            let node = &self.nodes[node_id];
219
220            if !node.is_anonymous() {
221                return node.id;
222            }
223
224            let Some(parent_id) = node.layout_parent.get() else {
225                // Shouldn't be reachable unless invalid node_id is passed
226                // as root node is always non-anonymous
227                panic!("Node does not exist or does not have a non-anonymous parent");
228            };
229
230            node_id = parent_id;
231        }
232    }
233
234    pub fn iter_children_mut(
235        &mut self,
236        node_id: NodeId,
237        mut cb: impl FnMut(NodeId, &mut BaseDocument),
238    ) {
239        let children = std::mem::take(&mut self.nodes[node_id].children);
240        for child_id in children.iter().cloned() {
241            cb(child_id, self);
242        }
243        self.nodes[node_id].children = children;
244    }
245
246    /// Connection and removal visit shadow descendants as well as DOM
247    /// descendants. Selector and serialization walks remain DOM walks.
248    pub fn iter_shadow_including_subtree_mut(
249        &mut self,
250        node_id: NodeId,
251        mut cb: impl FnMut(NodeId, &mut BaseDocument),
252    ) {
253        let mut stack = vec![node_id];
254        while let Some(id) = stack.pop() {
255            let Some(node) = self.get_node(id) else {
256                continue;
257            };
258            #[cfg_attr(not(feature = "shadow-dom"), allow(unused_mut))]
259            let mut children = node.children.to_vec();
260            #[cfg(feature = "shadow-dom")]
261            if let Some(root_id) = node.shadow_root_id() {
262                children.push(root_id);
263            }
264            cb(id, self);
265            stack.extend(children.into_iter().rev());
266        }
267    }
268
269    pub fn iter_subtree_mut(
270        &mut self,
271        node_id: NodeId,
272        mut cb: impl FnMut(NodeId, &mut BaseDocument),
273    ) {
274        cb(node_id, self);
275        iter_subtree_mut_inner(self, node_id, &mut cb);
276        fn iter_subtree_mut_inner(
277            doc: &mut BaseDocument,
278            node_id: NodeId,
279            cb: &mut impl FnMut(NodeId, &mut BaseDocument),
280        ) {
281            let children = std::mem::take(&mut doc.nodes[node_id].children);
282            for child_id in children.iter().cloned() {
283                cb(child_id, doc);
284                iter_subtree_mut_inner(doc, child_id, cb);
285            }
286            doc.nodes[node_id].children = children;
287        }
288    }
289
290    pub fn iter_children_and_pseudos_mut(
291        &mut self,
292        node_id: NodeId,
293        mut cb: impl FnMut(NodeId, &mut BaseDocument),
294    ) {
295        let before = self.nodes[node_id].before();
296        self.nodes[node_id].set_pe_by_index(1, None);
297        if let Some(before_node_id) = before {
298            cb(before_node_id, self)
299        }
300        self.nodes[node_id].set_pe_by_index(1, before);
301
302        self.iter_children_mut(node_id, &mut cb);
303
304        let after = self.nodes[node_id].after();
305        self.nodes[node_id].set_pe_by_index(0, None);
306        if let Some(after_node_id) = after {
307            cb(after_node_id, self)
308        }
309        self.nodes[node_id].set_pe_by_index(0, after);
310    }
311
312    /// Like [`iter_children_mut`](Self::iter_children_mut) but iterates the
313    /// flattened-tree children (used for box construction, so shadow hosts and
314    /// slots compose correctly).
315    pub fn iter_layout_children_mut(
316        &mut self,
317        node_id: NodeId,
318        mut cb: impl FnMut(NodeId, &mut BaseDocument),
319    ) {
320        let children = self.nodes[node_id].layout_dom_children().to_vec();
321        for child_id in children {
322            cb(child_id, self);
323        }
324    }
325
326    /// Like [`iter_children_and_pseudos_mut`](Self::iter_children_and_pseudos_mut)
327    /// but iterates the flattened-tree children.
328    pub fn iter_layout_children_and_pseudos_mut(
329        &mut self,
330        node_id: NodeId,
331        mut cb: impl FnMut(NodeId, &mut BaseDocument),
332    ) {
333        if let Some(before_node_id) = self.nodes[node_id].before() {
334            cb(before_node_id, self)
335        }
336
337        self.iter_layout_children_mut(node_id, &mut cb);
338
339        if let Some(after_node_id) = self.nodes[node_id].after() {
340            cb(after_node_id, self)
341        }
342    }
343
344    pub fn next_node(&self, start: &Node, mut filter: impl FnMut(&Node) -> bool) -> Option<NodeId> {
345        let start_id = start.id;
346        let mut node = start;
347        let mut look_in_children = true;
348        loop {
349            // Next is first child
350            let next = if look_in_children && !node.children.is_empty() {
351                let node_id = node.children[0];
352                &self.nodes[node_id]
353            }
354            // Next is next sibling or parent
355            else if let Some(parent) = node.parent_node() {
356                let self_idx = parent
357                    .children
358                    .iter()
359                    .position(|id| *id == node.id)
360                    .unwrap();
361                // Next is next sibling
362                if let Some(sibling_id) = parent.children.get(self_idx + 1) {
363                    look_in_children = true;
364                    &self.nodes[*sibling_id]
365                }
366                // Next is parent
367                else {
368                    look_in_children = false;
369                    node = parent;
370                    continue;
371                }
372            }
373            // Continue search from the root
374            else {
375                look_in_children = true;
376                self.root_node()
377            };
378
379            if filter(next) {
380                return Some(next.id);
381            } else if next.id == start_id {
382                return None;
383            }
384
385            node = next;
386        }
387    }
388
389    /// The node that comes last within `node`'s subtree in document order,
390    /// which is what precedes `node`'s successor in reverse order.
391    fn deepest_last_descendant<'a>(&'a self, mut node: &'a Node) -> &'a Node {
392        while let Some(last_child_id) = node.children.last() {
393            node = &self.nodes[*last_child_id];
394        }
395        node
396    }
397
398    /// Mirror of [`Self::next_node`]: walks the tree in reverse document
399    /// order, wrapping around to the end of the document.
400    pub fn prev_node(&self, start: &Node, mut filter: impl FnMut(&Node) -> bool) -> Option<NodeId> {
401        let start_id = start.id;
402        let mut node = start;
403        loop {
404            let prev = if let Some(parent) = node.parent_node() {
405                let self_idx = parent
406                    .children
407                    .iter()
408                    .position(|id| *id == node.id)
409                    .unwrap();
410                // Previous is the deepest last descendant of the previous
411                // sibling, or the parent when there is no previous sibling
412                if self_idx > 0 {
413                    self.deepest_last_descendant(&self.nodes[parent.children[self_idx - 1]])
414                } else {
415                    parent
416                }
417            }
418            // Continue the search from the end of the document
419            else {
420                self.deepest_last_descendant(self.root_node())
421            };
422
423            if filter(prev) {
424                return Some(prev.id);
425            } else if prev.id == start_id {
426                return None;
427            }
428
429            node = prev;
430        }
431    }
432
433    pub fn node_layout_ancestors(&self, node_id: NodeId) -> Vec<NodeId> {
434        let mut ancestors = Vec::with_capacity(12);
435        let mut maybe_id = Some(node_id);
436        while let Some(id) = maybe_id {
437            ancestors.push(id);
438            maybe_id = self.nodes[id].layout_parent.get();
439        }
440        ancestors.reverse();
441        ancestors
442    }
443
444    pub fn maybe_node_layout_ancestors(&self, node_id: Option<NodeId>) -> Vec<NodeId> {
445        node_id
446            .map(|id| self.node_layout_ancestors(id))
447            .unwrap_or_default()
448    }
449
450    /// Compare the document order of two nodes.
451    /// Returns Ordering::Less if node_a comes before node_b in document order.
452    /// Returns Ordering::Greater if node_a comes after node_b.
453    /// Returns Ordering::Equal if they are the same node.
454    pub fn compare_document_order(&self, node_a: NodeId, node_b: NodeId) -> Ordering {
455        if node_a == node_b {
456            return Ordering::Equal;
457        }
458
459        // Build ancestor chains from root to node (inclusive)
460        let chain_a = self.ancestor_chain_from_root(node_a);
461        let chain_b = self.ancestor_chain_from_root(node_b);
462
463        // Find where the chains diverge
464        let mut common_depth = 0;
465        for (a, b) in chain_a.iter().zip(chain_b.iter()) {
466            if a != b {
467                break;
468            }
469            common_depth += 1;
470        }
471
472        // If one is an ancestor of the other
473        if common_depth == chain_a.len() {
474            return Ordering::Less; // node_a is ancestor of node_b
475        }
476        if common_depth == chain_b.len() {
477            return Ordering::Greater; // node_b is ancestor of node_a
478        }
479
480        // Safety: common_depth must be > 0 here because both chains start from the same
481        // root node (node 0), so they share at least that node. If common_depth were 0,
482        // chain_a[0] != chain_b[0], but both start from root, so this is impossible.
483        debug_assert!(
484            common_depth > 0,
485            "nodes must share a common ancestor (the root)"
486        );
487
488        // Compare position among siblings at the divergence point
489        let divergent_a = chain_a[common_depth];
490        let divergent_b = chain_b[common_depth];
491        let parent_id = chain_a[common_depth - 1];
492        let parent = &self.nodes[parent_id];
493
494        for &child_id in &parent.children {
495            if child_id == divergent_a {
496                return Ordering::Less;
497            }
498            if child_id == divergent_b {
499                return Ordering::Greater;
500            }
501        }
502
503        // Should not reach here if tree is well-formed
504        Ordering::Equal
505    }
506
507    /// Build ancestor chain from root to node (inclusive), ordered [root, ..., node].
508    fn ancestor_chain_from_root(&self, node_id: NodeId) -> Vec<NodeId> {
509        let mut ancestors = Vec::with_capacity(16);
510        let mut current = Some(node_id);
511        while let Some(id) = current {
512            ancestors.push(id);
513            current = self.nodes[id].parent;
514        }
515        ancestors.reverse();
516        ancestors
517    }
518
519    /// Collect all inline root nodes between start_node and end_node in document order.
520    /// Both start and end are assumed to be inline roots.
521    /// Returns the nodes in document order (from first to last).
522    pub fn collect_inline_roots_in_range(
523        &self,
524        start_node: NodeId,
525        end_node: NodeId,
526    ) -> Vec<NodeId> {
527        // Resolve nodes: for anonymous blocks, get (parent_id, Some(anon_id)); for regular, (node_id, None)
528        let (start_anchor, start_anon) = self.resolve_for_traversal(start_node);
529        let (end_anchor, end_anon) = self.resolve_for_traversal(end_node);
530
531        // If both are anonymous blocks with the same parent, just collect from layout_children
532        if start_anon.is_some() && end_anon.is_some() && start_anchor == end_anchor {
533            return self.collect_anonymous_siblings(start_anchor, start_node, end_node);
534        }
535
536        // Determine first/last based on document order (using anchors for comparison)
537        let (first_anchor, first_anon, last_anchor, last_anon) = match self
538            .compare_document_order(start_anchor, end_anchor)
539        {
540            Ordering::Less | Ordering::Equal => (start_anchor, start_anon, end_anchor, end_anon),
541            Ordering::Greater => (end_anchor, end_anon, start_anchor, start_anon),
542        };
543
544        let mut result = Vec::new();
545        let mut found_first = false;
546
547        // Traverse tree in document order
548        for node_id in TreeTraverser::new(self) {
549            if !found_first {
550                if node_id == first_anchor {
551                    found_first = true;
552                    if let Some(anon_id) = first_anon {
553                        // First is anonymous: collect from this parent starting at anon_id
554                        // Stop at last_anchor if different parent, or last_anon if same parent
555                        let stop_at = if first_anchor == last_anchor {
556                            // Same parent: stop at last_anon
557                            last_anon
558                        } else {
559                            // Different parents: stop at last_anchor (which is a child of first_anchor)
560                            Some(last_anchor)
561                        };
562                        self.collect_layout_children_inline_roots(
563                            node_id,
564                            Some(anon_id),
565                            stop_at,
566                            &mut result,
567                        );
568                        // If we collected up to last, we're done
569                        if result.last() == Some(&last_anchor)
570                            || last_anon.is_some_and(|la| result.last() == Some(&la))
571                        {
572                            break;
573                        }
574                        continue;
575                    }
576                }
577            }
578
579            if found_first {
580                if node_id == last_anchor {
581                    if let Some(anon_id) = last_anon {
582                        // Last is anonymous: collect up to anon_id (exclusive), then include anon_id
583                        self.collect_layout_children_inline_roots(
584                            node_id,
585                            None,
586                            Some(anon_id),
587                            &mut result,
588                        );
589                        // Include the last_anon itself (until is exclusive, so we add it here)
590                        if !result.contains(&anon_id) {
591                            result.push(anon_id);
592                        }
593                    } else {
594                        // Last is regular: include it if it's an inline root and not already collected
595                        let node = &self.nodes[node_id];
596                        if node.flags.is_inline_root() && !result.contains(&node_id) {
597                            result.push(node_id);
598                        }
599                    }
600                    break;
601                }
602
603                let node = &self.nodes[node_id];
604                if node.flags.is_inline_root() && !result.contains(&node_id) {
605                    result.push(node_id);
606                } else {
607                    // For non-inline-root nodes, collect any inline roots from their layout_children
608                    // This handles intermediate block containers with anonymous block children
609                    self.collect_layout_children_inline_roots(
610                        node_id,
611                        None,
612                        Some(last_anchor),
613                        &mut result,
614                    );
615                }
616            }
617        }
618
619        result
620    }
621
622    /// Resolve a node for traversal purposes.
623    /// For anonymous blocks: returns (parent_id, Some(node_id))
624    /// For regular nodes: returns (node_id, None)
625    fn resolve_for_traversal(&self, node_id: NodeId) -> (NodeId, Option<NodeId>) {
626        let node = &self.nodes[node_id];
627        if node.is_anonymous() {
628            (node.parent.unwrap_or(node_id), Some(node_id))
629        } else {
630            (node_id, None)
631        }
632    }
633
634    /// Collect anonymous block siblings between start and end (inclusive)
635    /// Also recursively collects inline roots from any block children in between
636    fn collect_anonymous_siblings(
637        &self,
638        parent_id: NodeId,
639        start: NodeId,
640        end: NodeId,
641    ) -> Vec<NodeId> {
642        let parent = &self.nodes[parent_id];
643        let layout_children = parent.layout_children.borrow();
644        let Some(children) = layout_children.as_ref() else {
645            return Vec::new();
646        };
647
648        let start_idx = children.iter().position(|&id| id == start);
649        let end_idx = children.iter().position(|&id| id == end);
650
651        let (first_idx, last_idx) = match (start_idx, end_idx) {
652            (Some(s), Some(e)) if s <= e => (s, e),
653            (Some(s), Some(e)) => (e, s),
654            _ => return Vec::new(),
655        };
656
657        let mut result = Vec::new();
658        for &child_id in &children[first_idx..=last_idx] {
659            let child = &self.nodes[child_id];
660            if child.flags.is_inline_root() {
661                result.push(child_id);
662            } else {
663                // For non-inline-root children (block containers), collect all their inline roots
664                self.collect_all_inline_roots_in_subtree(child_id, &mut result);
665            }
666        }
667        result
668    }
669
670    /// Recursively collect all inline roots from a node's layout_children subtree
671    fn collect_all_inline_roots_in_subtree(&self, node_id: NodeId, result: &mut Vec<NodeId>) {
672        let node = &self.nodes[node_id];
673        let layout_children = node.layout_children.borrow();
674        let Some(children) = layout_children.as_ref() else {
675            return;
676        };
677
678        for &child_id in children.iter() {
679            let child = &self.nodes[child_id];
680            if child.flags.is_inline_root() {
681                result.push(child_id);
682            } else {
683                // Recurse into block children
684                self.collect_all_inline_roots_in_subtree(child_id, result);
685            }
686        }
687    }
688
689    /// Collect inline roots from a parent's layout_children.
690    /// - `from`: If Some, start collecting from this node; if None, start from beginning
691    /// - `until`: If Some, stop when we reach this node OR a node that contains it; if None, collect to end
692    fn collect_layout_children_inline_roots(
693        &self,
694        parent_id: NodeId,
695        from: Option<NodeId>,
696        until: Option<NodeId>,
697        result: &mut Vec<NodeId>,
698    ) {
699        let parent = &self.nodes[parent_id];
700        let layout_children = parent.layout_children.borrow();
701        let Some(children) = layout_children.as_ref() else {
702            return;
703        };
704
705        let mut collecting = from.is_none(); // Start immediately if no 'from' specified
706        for &child_id in children.iter() {
707            if from == Some(child_id) {
708                collecting = true;
709            }
710            if collecting {
711                // Stop without adding if this child contains the 'until' node (it will be processed later)
712                if let Some(until_id) = until {
713                    if self.is_ancestor_of(child_id, until_id) {
714                        break;
715                    }
716                }
717                // Stop before processing if this child IS the 'until' node
718                if until == Some(child_id) {
719                    break;
720                }
721                let child = &self.nodes[child_id];
722                if child.flags.is_inline_root() {
723                    result.push(child_id);
724                } else {
725                    // For non-inline-root children (block containers), recursively collect their inline roots
726                    self.collect_all_inline_roots_in_subtree(child_id, result);
727                }
728            }
729        }
730    }
731
732    /// Check if `ancestor_id` is an ancestor of `descendant_id`
733    fn is_ancestor_of(&self, ancestor_id: NodeId, descendant_id: NodeId) -> bool {
734        let mut current = descendant_id;
735        while let Some(parent) = self.nodes[current].parent {
736            if parent == ancestor_id {
737                return true;
738            }
739            current = parent;
740        }
741        false
742    }
743}