style/invalidation/element/restyle_hints.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
5//! Restyle hints: an optimization to avoid unnecessarily matching selectors.
6
7use crate::traversal_flags::TraversalFlags;
8
9bitflags! {
10 /// The kind of restyle we need to do for a given element.
11 #[repr(C)]
12 #[derive(Clone, Copy, Debug)]
13 pub struct RestyleHint: u16 {
14 /// Do a selector match of the element.
15 const RESTYLE_SELF = 1 << 0;
16
17 /// Do a selector match of the element's pseudo-elements. Always to be combined with
18 /// RESTYLE_SELF.
19 const RESTYLE_PSEUDOS = 1 << 1;
20
21 /// Do a selector match if the element is a pseudo-element.
22 const RESTYLE_SELF_IF_PSEUDO = 1 << 2;
23
24 /// Do a selector match of the element's descendants.
25 const RESTYLE_DESCENDANTS = 1 << 3;
26
27 /// Recascade the current element.
28 const RECASCADE_SELF = 1 << 4;
29
30 /// Recascade the current element if it inherits any reset style.
31 const RECASCADE_SELF_IF_INHERIT_RESET_STYLE = 1 << 5;
32
33 /// Recascade all descendant elements.
34 const RECASCADE_DESCENDANTS = 1 << 6;
35
36 /// Replace the style data coming from CSS transitions without updating
37 /// any other style data. This hint is only processed in animation-only
38 /// traversal which is prior to normal traversal.
39 const RESTYLE_CSS_TRANSITIONS = 1 << 7;
40
41 /// Replace the style data coming from CSS animations without updating
42 /// any other style data. This hint is only processed in animation-only
43 /// traversal which is prior to normal traversal.
44 const RESTYLE_CSS_ANIMATIONS = 1 << 8;
45
46 /// Don't re-run selector-matching on the element, only the style
47 /// attribute has changed, and this change didn't have any other
48 /// dependencies.
49 const RESTYLE_STYLE_ATTRIBUTE = 1 << 9;
50
51 /// Replace the style data coming from SMIL animations without updating
52 /// any other style data. This hint is only processed in animation-only
53 /// traversal which is prior to normal traversal.
54 const RESTYLE_SMIL = 1 << 10;
55
56 /// Match self if this element is dependent on a style query.
57 const RESTYLE_IF_AFFECTED_BY_STYLE_QUERIES = 1 << 11;
58
59 /// Match self or descendants if dependent on a named style query.
60 const RESTYLE_IF_AFFECTED_BY_NAMED_STYLE_CONTAINER = 1 << 12;
61
62 /// Do a selector match of the element if it depends on an ancestor's
63 /// font metrics.
64 const RESTYLE_IF_AFFECTED_BY_ANCESTOR_FONT_METRICS = 1 << 13;
65 }
66}
67
68impl RestyleHint {
69 /// Creates a new `RestyleHint` indicating that the current element and all
70 /// its descendants must be fully restyled.
71 #[inline]
72 pub fn restyle_subtree() -> Self {
73 RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS
74 }
75
76 /// Creates a new `RestyleHint` indicating that the current element and all
77 /// its descendants must be recascaded.
78 #[inline]
79 pub fn recascade_subtree() -> Self {
80 RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS
81 }
82
83 /// Returns whether this hint invalidates the element and all its
84 /// descendants.
85 #[inline]
86 pub fn contains_subtree(&self) -> bool {
87 self.contains(Self::restyle_subtree())
88 }
89
90 /// Returns whether we'll recascade all of the descendants.
91 #[inline]
92 pub fn will_recascade_subtree(&self) -> bool {
93 self.contains_subtree() || self.contains(Self::recascade_subtree())
94 }
95
96 /// Returns whether we need to restyle this element.
97 pub fn has_non_animation_invalidations(&self) -> bool {
98 !(*self & !Self::for_animations()).is_empty()
99 }
100
101 /// Propagates this restyle hint to a child element.
102 pub fn propagate(&mut self, traversal_flags: &TraversalFlags) -> Self {
103 use std::mem;
104
105 // In the middle of an animation only restyle, we don't need to
106 // propagate any restyle hints, and we need to remove ourselves.
107 if traversal_flags.for_animation_only() {
108 self.remove_animation_hints();
109 return Self::empty();
110 }
111
112 debug_assert!(
113 !self.has_animation_hint(),
114 "There should not be any animation restyle hints \
115 during normal traversal"
116 );
117
118 // Else we should clear ourselves, and return the propagated hint.
119 mem::replace(self, Self::empty()).propagate_for_non_animation_restyle()
120 }
121
122 /// Returns a new `RestyleHint` appropriate for children of the current element.
123 fn propagate_for_non_animation_restyle(&self) -> Self {
124 if self.contains(RestyleHint::RESTYLE_DESCENDANTS) {
125 return Self::restyle_subtree();
126 }
127 let mut result = Self::empty();
128 if self.contains(RestyleHint::RESTYLE_PSEUDOS) {
129 result |= Self::RESTYLE_SELF_IF_PSEUDO;
130 }
131 if self.contains(RestyleHint::RECASCADE_DESCENDANTS) {
132 result |= Self::recascade_subtree();
133 }
134 if self.contains(RestyleHint::RESTYLE_IF_AFFECTED_BY_ANCESTOR_FONT_METRICS) {
135 result |= Self::RESTYLE_IF_AFFECTED_BY_ANCESTOR_FONT_METRICS;
136 }
137 if self.contains(RestyleHint::RESTYLE_IF_AFFECTED_BY_NAMED_STYLE_CONTAINER) {
138 // We may need to restyle further down the tree if rules are
139 // declared for a named container.
140 // e.g @container my-name {#b {...}}
141 // and <div id=a> <div> <div id=b> </div> </div> </div>
142 // If a toggles `container-name: my-name` the rules for #b
143 // also invalidate. This is why we need one hint for unnamed
144 // container and one for named containers.
145 result |= RestyleHint::RESTYLE_IF_AFFECTED_BY_NAMED_STYLE_CONTAINER;
146 }
147
148 result
149 }
150
151 /// Returns a hint that contains all the replacement hints.
152 pub fn replacements() -> Self {
153 RestyleHint::RESTYLE_STYLE_ATTRIBUTE | Self::for_animations()
154 }
155
156 /// The replacements for the animation cascade levels.
157 #[inline]
158 pub fn for_animations() -> Self {
159 RestyleHint::RESTYLE_SMIL
160 | RestyleHint::RESTYLE_CSS_ANIMATIONS
161 | RestyleHint::RESTYLE_CSS_TRANSITIONS
162 }
163
164 /// Returns whether the hint specifies that an animation cascade level must
165 /// be replaced.
166 #[inline]
167 pub fn has_animation_hint(&self) -> bool {
168 self.intersects(Self::for_animations())
169 }
170
171 /// Returns whether the hint specifies that an animation cascade level must
172 /// be replaced.
173 #[inline]
174 pub fn has_animation_hint_or_recascade(&self) -> bool {
175 self.intersects(
176 Self::for_animations()
177 | Self::RECASCADE_SELF
178 | Self::RECASCADE_SELF_IF_INHERIT_RESET_STYLE,
179 )
180 }
181
182 /// Returns whether the hint specifies some restyle work other than an
183 /// animation cascade level replacement.
184 #[inline]
185 pub fn has_non_animation_hint(&self) -> bool {
186 !(*self & !Self::for_animations()).is_empty()
187 }
188
189 /// Returns whether the hint specifies that some cascade levels must be
190 /// replaced.
191 #[inline]
192 pub fn has_replacements(&self) -> bool {
193 self.intersects(Self::replacements())
194 }
195
196 /// Removes all of the animation-related hints.
197 #[inline]
198 pub fn remove_animation_hints(&mut self) {
199 self.remove(Self::for_animations());
200
201 // While RECASCADE_SELF is not animation-specific, we only ever add and process it during
202 // traversal. If we are here, removing animation hints, then we are in an animation-only
203 // traversal, and we know that any RECASCADE_SELF flag must have been set due to changes in
204 // inherited values after restyling for animations, and thus we want to remove it so that
205 // we don't later try to restyle the element during a normal restyle.
206 // (We could have separate RECASCADE_SELF_NORMAL and RECASCADE_SELF_ANIMATIONS flags to
207 // make it clear, but this isn't currently necessary.)
208 self.remove(Self::RECASCADE_SELF | Self::RECASCADE_SELF_IF_INHERIT_RESET_STYLE);
209 }
210}
211
212impl Default for RestyleHint {
213 fn default() -> Self {
214 Self::empty()
215 }
216}
217
218#[cfg(feature = "servo")]
219malloc_size_of::malloc_size_of_is_0!(RestyleHint);