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