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    /// If the node is non-anonymous then returns the node's id
193    /// Else find's the first non-anonymous ancester of the node
194    pub fn non_anon_ancestor_if_anon(&self, mut node_id: NodeId) -> NodeId {
195        loop {
196            let node = &self.nodes[node_id];
197
198            if !node.is_anonymous() {
199                return node.id;
200            }
201
202            let Some(parent_id) = node.layout_parent.get() else {
203                // Shouldn't be reachable unless invalid node_id is passed
204                // as root node is always non-anonymous
205                panic!("Node does not exist or does not have a non-anonymous parent");
206            };
207
208            node_id = parent_id;
209        }
210    }
211
212    pub fn iter_children_mut(
213        &mut self,
214        node_id: NodeId,
215        mut cb: impl FnMut(NodeId, &mut BaseDocument),
216    ) {
217        let children = std::mem::take(&mut self.nodes[node_id].children);
218        for child_id in children.iter().cloned() {
219            cb(child_id, self);
220        }
221        self.nodes[node_id].children = children;
222    }
223
224    pub fn iter_subtree_mut(
225        &mut self,
226        node_id: NodeId,
227        mut cb: impl FnMut(NodeId, &mut BaseDocument),
228    ) {
229        cb(node_id, self);
230        iter_subtree_mut_inner(self, node_id, &mut cb);
231        fn iter_subtree_mut_inner(
232            doc: &mut BaseDocument,
233            node_id: NodeId,
234            cb: &mut impl FnMut(NodeId, &mut BaseDocument),
235        ) {
236            let children = std::mem::take(&mut doc.nodes[node_id].children);
237            for child_id in children.iter().cloned() {
238                cb(child_id, doc);
239                iter_subtree_mut_inner(doc, child_id, cb);
240            }
241            doc.nodes[node_id].children = children;
242        }
243    }
244
245    pub fn iter_children_and_pseudos_mut(
246        &mut self,
247        node_id: NodeId,
248        mut cb: impl FnMut(NodeId, &mut BaseDocument),
249    ) {
250        let before = self.nodes[node_id].before();
251        self.nodes[node_id].set_pe_by_index(1, None);
252        if let Some(before_node_id) = before {
253            cb(before_node_id, self)
254        }
255        self.nodes[node_id].set_pe_by_index(1, before);
256
257        self.iter_children_mut(node_id, &mut cb);
258
259        let after = self.nodes[node_id].after();
260        self.nodes[node_id].set_pe_by_index(0, None);
261        if let Some(after_node_id) = after {
262            cb(after_node_id, self)
263        }
264        self.nodes[node_id].set_pe_by_index(0, after);
265    }
266
267    /// Like [`iter_children_mut`](Self::iter_children_mut) but iterates the
268    /// flattened-tree children (used for box construction, so shadow hosts and
269    /// slots compose correctly).
270    pub fn iter_layout_children_mut(
271        &mut self,
272        node_id: NodeId,
273        mut cb: impl FnMut(NodeId, &mut BaseDocument),
274    ) {
275        let children = self.nodes[node_id].layout_dom_children().to_vec();
276        for child_id in children {
277            cb(child_id, self);
278        }
279    }
280
281    /// Like [`iter_children_and_pseudos_mut`](Self::iter_children_and_pseudos_mut)
282    /// but iterates the flattened-tree children.
283    pub fn iter_layout_children_and_pseudos_mut(
284        &mut self,
285        node_id: NodeId,
286        mut cb: impl FnMut(NodeId, &mut BaseDocument),
287    ) {
288        if let Some(before_node_id) = self.nodes[node_id].before() {
289            cb(before_node_id, self)
290        }
291
292        self.iter_layout_children_mut(node_id, &mut cb);
293
294        if let Some(after_node_id) = self.nodes[node_id].after() {
295            cb(after_node_id, self)
296        }
297    }
298
299    pub fn next_node(&self, start: &Node, mut filter: impl FnMut(&Node) -> bool) -> Option<NodeId> {
300        let start_id = start.id;
301        let mut node = start;
302        let mut look_in_children = true;
303        loop {
304            // Next is first child
305            let next = if look_in_children && !node.children.is_empty() {
306                let node_id = node.children[0];
307                &self.nodes[node_id]
308            }
309            // Next is next sibling or parent
310            else if let Some(parent) = node.parent_node() {
311                let self_idx = parent
312                    .children
313                    .iter()
314                    .position(|id| *id == node.id)
315                    .unwrap();
316                // Next is next sibling
317                if let Some(sibling_id) = parent.children.get(self_idx + 1) {
318                    look_in_children = true;
319                    &self.nodes[*sibling_id]
320                }
321                // Next is parent
322                else {
323                    look_in_children = false;
324                    node = parent;
325                    continue;
326                }
327            }
328            // Continue search from the root
329            else {
330                look_in_children = true;
331                self.root_node()
332            };
333
334            if filter(next) {
335                return Some(next.id);
336            } else if next.id == start_id {
337                return None;
338            }
339
340            node = next;
341        }
342    }
343
344    /// The node that comes last within `node`'s subtree in document order,
345    /// which is what precedes `node`'s successor in reverse order.
346    fn deepest_last_descendant<'a>(&'a self, mut node: &'a Node) -> &'a Node {
347        while let Some(last_child_id) = node.children.last() {
348            node = &self.nodes[*last_child_id];
349        }
350        node
351    }
352
353    /// Mirror of [`Self::next_node`]: walks the tree in reverse document
354    /// order, wrapping around to the end of the document.
355    pub fn prev_node(&self, start: &Node, mut filter: impl FnMut(&Node) -> bool) -> Option<NodeId> {
356        let start_id = start.id;
357        let mut node = start;
358        loop {
359            let prev = if let Some(parent) = node.parent_node() {
360                let self_idx = parent
361                    .children
362                    .iter()
363                    .position(|id| *id == node.id)
364                    .unwrap();
365                // Previous is the deepest last descendant of the previous
366                // sibling, or the parent when there is no previous sibling
367                if self_idx > 0 {
368                    self.deepest_last_descendant(&self.nodes[parent.children[self_idx - 1]])
369                } else {
370                    parent
371                }
372            }
373            // Continue the search from the end of the document
374            else {
375                self.deepest_last_descendant(self.root_node())
376            };
377
378            if filter(prev) {
379                return Some(prev.id);
380            } else if prev.id == start_id {
381                return None;
382            }
383
384            node = prev;
385        }
386    }
387
388    pub fn node_layout_ancestors(&self, node_id: NodeId) -> Vec<NodeId> {
389        let mut ancestors = Vec::with_capacity(12);
390        let mut maybe_id = Some(node_id);
391        while let Some(id) = maybe_id {
392            ancestors.push(id);
393            maybe_id = self.nodes[id].layout_parent.get();
394        }
395        ancestors.reverse();
396        ancestors
397    }
398
399    pub fn maybe_node_layout_ancestors(&self, node_id: Option<NodeId>) -> Vec<NodeId> {
400        node_id
401            .map(|id| self.node_layout_ancestors(id))
402            .unwrap_or_default()
403    }
404
405    /// Compare the document order of two nodes.
406    /// Returns Ordering::Less if node_a comes before node_b in document order.
407    /// Returns Ordering::Greater if node_a comes after node_b.
408    /// Returns Ordering::Equal if they are the same node.
409    pub fn compare_document_order(&self, node_a: NodeId, node_b: NodeId) -> Ordering {
410        if node_a == node_b {
411            return Ordering::Equal;
412        }
413
414        // Build ancestor chains from root to node (inclusive)
415        let chain_a = self.ancestor_chain_from_root(node_a);
416        let chain_b = self.ancestor_chain_from_root(node_b);
417
418        // Find where the chains diverge
419        let mut common_depth = 0;
420        for (a, b) in chain_a.iter().zip(chain_b.iter()) {
421            if a != b {
422                break;
423            }
424            common_depth += 1;
425        }
426
427        // If one is an ancestor of the other
428        if common_depth == chain_a.len() {
429            return Ordering::Less; // node_a is ancestor of node_b
430        }
431        if common_depth == chain_b.len() {
432            return Ordering::Greater; // node_b is ancestor of node_a
433        }
434
435        // Safety: common_depth must be > 0 here because both chains start from the same
436        // root node (node 0), so they share at least that node. If common_depth were 0,
437        // chain_a[0] != chain_b[0], but both start from root, so this is impossible.
438        debug_assert!(
439            common_depth > 0,
440            "nodes must share a common ancestor (the root)"
441        );
442
443        // Compare position among siblings at the divergence point
444        let divergent_a = chain_a[common_depth];
445        let divergent_b = chain_b[common_depth];
446        let parent_id = chain_a[common_depth - 1];
447        let parent = &self.nodes[parent_id];
448
449        for &child_id in &parent.children {
450            if child_id == divergent_a {
451                return Ordering::Less;
452            }
453            if child_id == divergent_b {
454                return Ordering::Greater;
455            }
456        }
457
458        // Should not reach here if tree is well-formed
459        Ordering::Equal
460    }
461
462    /// Build ancestor chain from root to node (inclusive), ordered [root, ..., node].
463    fn ancestor_chain_from_root(&self, node_id: NodeId) -> Vec<NodeId> {
464        let mut ancestors = Vec::with_capacity(16);
465        let mut current = Some(node_id);
466        while let Some(id) = current {
467            ancestors.push(id);
468            current = self.nodes[id].parent;
469        }
470        ancestors.reverse();
471        ancestors
472    }
473
474    /// Collect all inline root nodes between start_node and end_node in document order.
475    /// Both start and end are assumed to be inline roots.
476    /// Returns the nodes in document order (from first to last).
477    pub fn collect_inline_roots_in_range(
478        &self,
479        start_node: NodeId,
480        end_node: NodeId,
481    ) -> Vec<NodeId> {
482        // Resolve nodes: for anonymous blocks, get (parent_id, Some(anon_id)); for regular, (node_id, None)
483        let (start_anchor, start_anon) = self.resolve_for_traversal(start_node);
484        let (end_anchor, end_anon) = self.resolve_for_traversal(end_node);
485
486        // If both are anonymous blocks with the same parent, just collect from layout_children
487        if start_anon.is_some() && end_anon.is_some() && start_anchor == end_anchor {
488            return self.collect_anonymous_siblings(start_anchor, start_node, end_node);
489        }
490
491        // Determine first/last based on document order (using anchors for comparison)
492        let (first_anchor, first_anon, last_anchor, last_anon) = match self
493            .compare_document_order(start_anchor, end_anchor)
494        {
495            Ordering::Less | Ordering::Equal => (start_anchor, start_anon, end_anchor, end_anon),
496            Ordering::Greater => (end_anchor, end_anon, start_anchor, start_anon),
497        };
498
499        let mut result = Vec::new();
500        let mut found_first = false;
501
502        // Traverse tree in document order
503        for node_id in TreeTraverser::new(self) {
504            if !found_first {
505                if node_id == first_anchor {
506                    found_first = true;
507                    if let Some(anon_id) = first_anon {
508                        // First is anonymous: collect from this parent starting at anon_id
509                        // Stop at last_anchor if different parent, or last_anon if same parent
510                        let stop_at = if first_anchor == last_anchor {
511                            // Same parent: stop at last_anon
512                            last_anon
513                        } else {
514                            // Different parents: stop at last_anchor (which is a child of first_anchor)
515                            Some(last_anchor)
516                        };
517                        self.collect_layout_children_inline_roots(
518                            node_id,
519                            Some(anon_id),
520                            stop_at,
521                            &mut result,
522                        );
523                        // If we collected up to last, we're done
524                        if result.last() == Some(&last_anchor)
525                            || last_anon.is_some_and(|la| result.last() == Some(&la))
526                        {
527                            break;
528                        }
529                        continue;
530                    }
531                }
532            }
533
534            if found_first {
535                if node_id == last_anchor {
536                    if let Some(anon_id) = last_anon {
537                        // Last is anonymous: collect up to anon_id (exclusive), then include anon_id
538                        self.collect_layout_children_inline_roots(
539                            node_id,
540                            None,
541                            Some(anon_id),
542                            &mut result,
543                        );
544                        // Include the last_anon itself (until is exclusive, so we add it here)
545                        if !result.contains(&anon_id) {
546                            result.push(anon_id);
547                        }
548                    } else {
549                        // Last is regular: include it if it's an inline root and not already collected
550                        let node = &self.nodes[node_id];
551                        if node.flags.is_inline_root() && !result.contains(&node_id) {
552                            result.push(node_id);
553                        }
554                    }
555                    break;
556                }
557
558                let node = &self.nodes[node_id];
559                if node.flags.is_inline_root() && !result.contains(&node_id) {
560                    result.push(node_id);
561                } else {
562                    // For non-inline-root nodes, collect any inline roots from their layout_children
563                    // This handles intermediate block containers with anonymous block children
564                    self.collect_layout_children_inline_roots(
565                        node_id,
566                        None,
567                        Some(last_anchor),
568                        &mut result,
569                    );
570                }
571            }
572        }
573
574        result
575    }
576
577    /// Resolve a node for traversal purposes.
578    /// For anonymous blocks: returns (parent_id, Some(node_id))
579    /// For regular nodes: returns (node_id, None)
580    fn resolve_for_traversal(&self, node_id: NodeId) -> (NodeId, Option<NodeId>) {
581        let node = &self.nodes[node_id];
582        if node.is_anonymous() {
583            (node.parent.unwrap_or(node_id), Some(node_id))
584        } else {
585            (node_id, None)
586        }
587    }
588
589    /// Collect anonymous block siblings between start and end (inclusive)
590    /// Also recursively collects inline roots from any block children in between
591    fn collect_anonymous_siblings(
592        &self,
593        parent_id: NodeId,
594        start: NodeId,
595        end: NodeId,
596    ) -> Vec<NodeId> {
597        let parent = &self.nodes[parent_id];
598        let layout_children = parent.layout_children.borrow();
599        let Some(children) = layout_children.as_ref() else {
600            return Vec::new();
601        };
602
603        let start_idx = children.iter().position(|&id| id == start);
604        let end_idx = children.iter().position(|&id| id == end);
605
606        let (first_idx, last_idx) = match (start_idx, end_idx) {
607            (Some(s), Some(e)) if s <= e => (s, e),
608            (Some(s), Some(e)) => (e, s),
609            _ => return Vec::new(),
610        };
611
612        let mut result = Vec::new();
613        for &child_id in &children[first_idx..=last_idx] {
614            let child = &self.nodes[child_id];
615            if child.flags.is_inline_root() {
616                result.push(child_id);
617            } else {
618                // For non-inline-root children (block containers), collect all their inline roots
619                self.collect_all_inline_roots_in_subtree(child_id, &mut result);
620            }
621        }
622        result
623    }
624
625    /// Recursively collect all inline roots from a node's layout_children subtree
626    fn collect_all_inline_roots_in_subtree(&self, node_id: NodeId, result: &mut Vec<NodeId>) {
627        let node = &self.nodes[node_id];
628        let layout_children = node.layout_children.borrow();
629        let Some(children) = layout_children.as_ref() else {
630            return;
631        };
632
633        for &child_id in children.iter() {
634            let child = &self.nodes[child_id];
635            if child.flags.is_inline_root() {
636                result.push(child_id);
637            } else {
638                // Recurse into block children
639                self.collect_all_inline_roots_in_subtree(child_id, result);
640            }
641        }
642    }
643
644    /// Collect inline roots from a parent's layout_children.
645    /// - `from`: If Some, start collecting from this node; if None, start from beginning
646    /// - `until`: If Some, stop when we reach this node OR a node that contains it; if None, collect to end
647    fn collect_layout_children_inline_roots(
648        &self,
649        parent_id: NodeId,
650        from: Option<NodeId>,
651        until: Option<NodeId>,
652        result: &mut Vec<NodeId>,
653    ) {
654        let parent = &self.nodes[parent_id];
655        let layout_children = parent.layout_children.borrow();
656        let Some(children) = layout_children.as_ref() else {
657            return;
658        };
659
660        let mut collecting = from.is_none(); // Start immediately if no 'from' specified
661        for &child_id in children.iter() {
662            if from == Some(child_id) {
663                collecting = true;
664            }
665            if collecting {
666                // Stop without adding if this child contains the 'until' node (it will be processed later)
667                if let Some(until_id) = until {
668                    if self.is_ancestor_of(child_id, until_id) {
669                        break;
670                    }
671                }
672                // Stop before processing if this child IS the 'until' node
673                if until == Some(child_id) {
674                    break;
675                }
676                let child = &self.nodes[child_id];
677                if child.flags.is_inline_root() {
678                    result.push(child_id);
679                } else {
680                    // For non-inline-root children (block containers), recursively collect their inline roots
681                    self.collect_all_inline_roots_in_subtree(child_id, result);
682                }
683            }
684        }
685    }
686
687    /// Check if `ancestor_id` is an ancestor of `descendant_id`
688    fn is_ancestor_of(&self, ancestor_id: NodeId, descendant_id: NodeId) -> bool {
689        let mut current = descendant_id;
690        while let Some(parent) = self.nodes[current].parent {
691            if parent == ancestor_id {
692                return true;
693            }
694            current = parent;
695        }
696        false
697    }
698}