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 http://mozilla.org/MPL/2.0/. */
4
5 //! Restyle hints: an optimization to avoid unnecessarily matching selectors.
6
7 #[cfg(feature = "gecko")]
8 use gecko_bindings::structs::nsRestyleHint;
9 use traversal_flags::TraversalFlags;
10
11 bitflags! {
12 /// The kind of restyle we need to do for a given element.
13 pub struct RestyleHint: u8 {
14 /// Do a selector match of the element.
15 const RESTYLE_SELF = 1 << 0;
16
17 /// Do a selector match of the element's descendants.
18 const RESTYLE_DESCENDANTS = 1 << 1;
19
20 /// Recascade the current element.
21 const RECASCADE_SELF = 1 << 2;
22
23 /// Recascade all descendant elements.
24 const RECASCADE_DESCENDANTS = 1 << 3;
25
26 /// Replace the style data coming from CSS transitions without updating
27 /// any other style data. This hint is only processed in animation-only
28 /// traversal which is prior to normal traversal.
29 const RESTYLE_CSS_TRANSITIONS = 1 << 4;
30
31 /// Replace the style data coming from CSS animations without updating
32 /// any other style data. This hint is only processed in animation-only
33 /// traversal which is prior to normal traversal.
34 const RESTYLE_CSS_ANIMATIONS = 1 << 5;
35
36 /// Don't re-run selector-matching on the element, only the style
37 /// attribute has changed, and this change didn't have any other
38 /// dependencies.
39 const RESTYLE_STYLE_ATTRIBUTE = 1 << 6;
40
41 /// Replace the style data coming from SMIL 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_SMIL = 1 << 7;
45 }
46 }
47
48 impl RestyleHint {
49 /// Creates a new `RestyleHint` indicating that the current element and all
50 /// its descendants must be fully restyled.
restyle_subtree() -> Self51 pub fn restyle_subtree() -> Self {
52 RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS
53 }
54
55 /// Creates a new `RestyleHint` indicating that the current element and all
56 /// its descendants must be recascaded.
recascade_subtree() -> Self57 pub fn recascade_subtree() -> Self {
58 RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS
59 }
60
61 /// Returns whether this hint invalidates the element and all its
62 /// descendants.
contains_subtree(&self) -> bool63 pub fn contains_subtree(&self) -> bool {
64 self.contains(RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS)
65 }
66
67 /// Returns whether we need to restyle this element.
has_non_animation_invalidations(&self) -> bool68 pub fn has_non_animation_invalidations(&self) -> bool {
69 self.intersects(
70 RestyleHint::RESTYLE_SELF |
71 RestyleHint::RECASCADE_SELF |
72 (Self::replacements() & !Self::for_animations())
73 )
74 }
75
76 /// Propagates this restyle hint to a child element.
propagate(&mut self, traversal_flags: &TraversalFlags) -> Self77 pub fn propagate(&mut self, traversal_flags: &TraversalFlags) -> Self {
78 use std::mem;
79
80 // In the middle of an animation only restyle, we don't need to
81 // propagate any restyle hints, and we need to remove ourselves.
82 if traversal_flags.for_animation_only() {
83 self.remove_animation_hints();
84 return Self::empty();
85 }
86
87 debug_assert!(!self.has_animation_hint(),
88 "There should not be any animation restyle hints \
89 during normal traversal");
90
91 // Else we should clear ourselves, and return the propagated hint.
92 mem::replace(self, Self::empty())
93 .propagate_for_non_animation_restyle()
94 }
95
96 /// Returns a new `CascadeHint` appropriate for children of the current
97 /// element.
propagate_for_non_animation_restyle(&self) -> Self98 fn propagate_for_non_animation_restyle(&self) -> Self {
99 if self.contains(RestyleHint::RESTYLE_DESCENDANTS) {
100 return Self::restyle_subtree()
101 }
102 if self.contains(RestyleHint::RECASCADE_DESCENDANTS) {
103 return Self::recascade_subtree()
104 }
105 Self::empty()
106 }
107
108 /// Creates a new `RestyleHint` that indicates the element must be
109 /// recascaded.
recascade_self() -> Self110 pub fn recascade_self() -> Self {
111 RestyleHint::RECASCADE_SELF
112 }
113
114 /// Returns a hint that contains all the replacement hints.
replacements() -> Self115 pub fn replacements() -> Self {
116 RestyleHint::RESTYLE_STYLE_ATTRIBUTE | Self::for_animations()
117 }
118
119 /// The replacements for the animation cascade levels.
120 #[inline]
for_animations() -> Self121 pub fn for_animations() -> Self {
122 RestyleHint::RESTYLE_SMIL | RestyleHint::RESTYLE_CSS_ANIMATIONS | RestyleHint::RESTYLE_CSS_TRANSITIONS
123 }
124
125 /// Returns whether the hint specifies that the currently element must be
126 /// recascaded.
has_recascade_self(&self) -> bool127 pub fn has_recascade_self(&self) -> bool {
128 self.contains(RestyleHint::RECASCADE_SELF)
129 }
130
131 /// Returns whether the hint specifies that an animation cascade level must
132 /// be replaced.
133 #[inline]
has_animation_hint(&self) -> bool134 pub fn has_animation_hint(&self) -> bool {
135 self.intersects(Self::for_animations())
136 }
137
138 /// Returns whether the hint specifies that an animation cascade level must
139 /// be replaced.
140 #[inline]
has_animation_hint_or_recascade(&self) -> bool141 pub fn has_animation_hint_or_recascade(&self) -> bool {
142 self.intersects(Self::for_animations() | RestyleHint::RECASCADE_SELF)
143 }
144
145 /// Returns whether the hint specifies some restyle work other than an
146 /// animation cascade level replacement.
147 #[inline]
has_non_animation_hint(&self) -> bool148 pub fn has_non_animation_hint(&self) -> bool {
149 !(*self & !Self::for_animations()).is_empty()
150 }
151
152 /// Returns whether the hint specifies that selector matching must be re-run
153 /// for the element.
154 #[inline]
match_self(&self) -> bool155 pub fn match_self(&self) -> bool {
156 self.intersects(RestyleHint::RESTYLE_SELF)
157 }
158
159 /// Returns whether the hint specifies that some cascade levels must be
160 /// replaced.
161 #[inline]
has_replacements(&self) -> bool162 pub fn has_replacements(&self) -> bool {
163 self.intersects(Self::replacements())
164 }
165
166 /// Removes all of the animation-related hints.
167 #[inline]
remove_animation_hints(&mut self)168 pub fn remove_animation_hints(&mut self) {
169 self.remove(Self::for_animations());
170
171 // While RECASCADE_SELF is not animation-specific, we only ever add and
172 // process it during traversal. If we are here, removing animation
173 // hints, then we are in an animation-only traversal, and we know that
174 // any RECASCADE_SELF flag must have been set due to changes in
175 // inherited values after restyling for animations, and thus we want to
176 // remove it so that we don't later try to restyle the element during a
177 // normal restyle. (We could have separate RECASCADE_SELF_NORMAL and
178 // RECASCADE_SELF_ANIMATIONS flags to make it clear, but this isn't
179 // currently necessary.)
180 self.remove(RestyleHint::RECASCADE_SELF);
181 }
182 }
183
184 impl Default for RestyleHint {
default() -> Self185 fn default() -> Self {
186 Self::empty()
187 }
188 }
189
190 #[cfg(feature = "gecko")]
191 impl From<nsRestyleHint> for RestyleHint {
from(mut raw: nsRestyleHint) -> Self192 fn from(mut raw: nsRestyleHint) -> Self {
193 use gecko_bindings::structs::nsRestyleHint_eRestyle_Force as eRestyle_Force;
194 use gecko_bindings::structs::nsRestyleHint_eRestyle_ForceDescendants as eRestyle_ForceDescendants;
195 use gecko_bindings::structs::nsRestyleHint_eRestyle_LaterSiblings as eRestyle_LaterSiblings;
196 use gecko_bindings::structs::nsRestyleHint_eRestyle_Self as eRestyle_Self;
197 use gecko_bindings::structs::nsRestyleHint_eRestyle_SomeDescendants as eRestyle_SomeDescendants;
198 use gecko_bindings::structs::nsRestyleHint_eRestyle_Subtree as eRestyle_Subtree;
199
200 let mut hint = RestyleHint::empty();
201
202 debug_assert!(raw.0 & eRestyle_LaterSiblings.0 == 0,
203 "Handle later siblings manually if necessary plz.");
204
205 if (raw.0 & (eRestyle_Self.0 | eRestyle_Subtree.0)) != 0 {
206 raw.0 &= !eRestyle_Self.0;
207 hint.insert(RestyleHint::RESTYLE_SELF);
208 }
209
210 if (raw.0 & (eRestyle_Subtree.0 | eRestyle_SomeDescendants.0)) != 0 {
211 raw.0 &= !eRestyle_Subtree.0;
212 raw.0 &= !eRestyle_SomeDescendants.0;
213 hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
214 }
215
216 if (raw.0 & (eRestyle_ForceDescendants.0 | eRestyle_Force.0)) != 0 {
217 raw.0 &= !eRestyle_Force.0;
218 hint.insert(RestyleHint::RECASCADE_SELF);
219 }
220
221 if (raw.0 & eRestyle_ForceDescendants.0) != 0 {
222 raw.0 &= !eRestyle_ForceDescendants.0;
223 hint.insert(RestyleHint::RECASCADE_DESCENDANTS);
224 }
225
226 hint.insert(RestyleHint::from_bits_truncate(raw.0 as u8));
227
228 hint
229 }
230 }
231
232 #[cfg(feature = "servo")]
233 malloc_size_of_is_0!(RestyleHint);
234
235 /// Asserts that all replacement hints have a matching nsRestyleHint value.
236 #[cfg(feature = "gecko")]
237 #[inline]
assert_restyle_hints_match()238 pub fn assert_restyle_hints_match() {
239 use gecko_bindings::structs;
240
241 macro_rules! check_restyle_hints {
242 ( $( $a:ident => $b:path),*, ) => {
243 if cfg!(debug_assertions) {
244 let mut replacements = RestyleHint::replacements();
245 $(
246 assert_eq!(structs::$a.0 as usize, $b.bits() as usize, stringify!($b));
247 replacements.remove($b);
248 )*
249 assert_eq!(replacements, RestyleHint::empty(),
250 "all RestyleHint replacement bits should have an \
251 assertion");
252 }
253 }
254 }
255
256 check_restyle_hints! {
257 nsRestyleHint_eRestyle_CSSTransitions => RestyleHint::RESTYLE_CSS_TRANSITIONS,
258 nsRestyleHint_eRestyle_CSSAnimations => RestyleHint::RESTYLE_CSS_ANIMATIONS,
259 nsRestyleHint_eRestyle_StyleAttribute => RestyleHint::RESTYLE_STYLE_ATTRIBUTE,
260 nsRestyleHint_eRestyle_StyleAttribute_Animations => RestyleHint::RESTYLE_SMIL,
261 }
262 }
263