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