Skip to main content

script/dom/node/
children_mutation.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 js::context::NoGC;
6
7use crate::dom::Node;
8use crate::dom::bindings::inheritance::Castable;
9use crate::dom::bindings::root::DomRoot;
10use crate::dom::element::Element;
11
12pub(crate) enum ChildrenMutation<'a> {
13    Append {
14        prev: &'a Node,
15    },
16    Insert {
17        prev: &'a Node,
18        next: &'a Node,
19    },
20    Prepend {
21        next: &'a Node,
22    },
23    Replace {
24        prev: Option<&'a Node>,
25        next: Option<&'a Node>,
26    },
27    ReplaceAll,
28    /// Mutation for when a Text node's data is modified.
29    /// This doesn't change the structure of the list, which is what the other
30    /// variants' fields are stored for at the moment, so this can just have no
31    /// fields.
32    ChangeText,
33}
34
35impl<'a> ChildrenMutation<'a> {
36    pub(super) fn insert(prev: Option<&'a Node>, next: Option<&'a Node>) -> ChildrenMutation<'a> {
37        match (prev, next) {
38            (None, None) => ChildrenMutation::ReplaceAll,
39            (Some(prev), None) => ChildrenMutation::Append { prev },
40            (None, Some(next)) => ChildrenMutation::Prepend { next },
41            (Some(prev), Some(next)) => ChildrenMutation::Insert { prev, next },
42        }
43    }
44
45    pub(super) fn replace(
46        prev: Option<&'a Node>,
47        removed: &'a Option<&'a Node>,
48        next: Option<&'a Node>,
49    ) -> ChildrenMutation<'a> {
50        if removed.is_some() {
51            if let (None, None) = (prev, next) {
52                ChildrenMutation::ReplaceAll
53            } else {
54                ChildrenMutation::Replace { prev, next }
55            }
56        } else {
57            ChildrenMutation::insert(prev, next)
58        }
59    }
60
61    /// Get the child that follows the added or removed children.
62    /// Currently only used when this mutation might force us to
63    /// restyle later children (see HAS_SLOW_SELECTOR_LATER_SIBLINGS and
64    /// Element's implementation of VirtualMethods::children_changed).
65    pub(crate) fn next_child(&self) -> Option<&Node> {
66        match *self {
67            ChildrenMutation::Append { .. } => None,
68            ChildrenMutation::Insert { next, .. } => Some(next),
69            ChildrenMutation::Prepend { next, .. } => Some(next),
70            ChildrenMutation::Replace { next, .. } => next,
71            ChildrenMutation::ReplaceAll => None,
72            ChildrenMutation::ChangeText => None,
73        }
74    }
75
76    /// If nodes were added or removed at the start or end of a container, return any
77    /// previously-existing child whose ":first-child" or ":last-child" status *may* have changed.
78    ///
79    /// NOTE: This does not check whether the inserted/removed nodes were elements, so in some
80    /// cases it will return a false positive.  This doesn't matter for correctness, because at
81    /// worst the returned element will be restyled unnecessarily.
82    pub(crate) fn modified_edge_element(&self, no_gc: &NoGC) -> Option<DomRoot<Node>> {
83        match *self {
84            // Add/remove at start of container: Return the first following element.
85            ChildrenMutation::Prepend { next, .. } |
86            ChildrenMutation::Replace {
87                prev: None,
88                next: Some(next),
89                ..
90            } => next
91                .inclusively_following_siblings_unrooted(no_gc)
92                .find(|node| node.is::<Element>())
93                .map(|node| node.as_rooted()),
94            // Add/remove at end of container: Return the last preceding element.
95            ChildrenMutation::Append { prev } |
96            ChildrenMutation::Replace {
97                prev: Some(prev),
98                next: None,
99                ..
100            } => prev
101                .inclusively_preceding_siblings_unrooted(no_gc)
102                .find(|node| node.is::<Element>())
103                .map(|node| node.as_rooted()),
104            // Insert or replace in the middle:
105            ChildrenMutation::Insert { prev, next, .. } |
106            ChildrenMutation::Replace {
107                prev: Some(prev),
108                next: Some(next),
109                ..
110            } => {
111                if prev
112                    .inclusively_preceding_siblings_unrooted(no_gc)
113                    .all(|node| !node.is::<Element>())
114                {
115                    // Before the first element: Return the first following element.
116                    next.inclusively_following_siblings_unrooted(no_gc)
117                        .find(|node| node.is::<Element>())
118                        .map(|node| node.as_rooted())
119                } else if next
120                    .inclusively_following_siblings_unrooted(no_gc)
121                    .all(|node| !node.is::<Element>())
122                {
123                    // After the last element: Return the last preceding element.
124                    prev.inclusively_preceding_siblings_unrooted(no_gc)
125                        .find(|node| node.is::<Element>())
126                        .map(|node| node.as_rooted())
127                } else {
128                    None
129                }
130            },
131
132            ChildrenMutation::Replace {
133                prev: None,
134                next: None,
135                ..
136            } => unreachable!(),
137            ChildrenMutation::ReplaceAll => None,
138            ChildrenMutation::ChangeText => None,
139        }
140    }
141}