Skip to main content

style/
data.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//! Per-node data used in style calculation.
6
7use crate::computed_value_flags::ComputedValueFlags;
8use crate::context::{SharedStyleContext, StackLimitChecker};
9use crate::dom::TElement;
10use crate::invalidation::element::invalidator::InvalidationResult;
11use crate::invalidation::element::restyle_hints::RestyleHint;
12use crate::properties::ComputedValues;
13use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT};
14use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle};
15#[cfg(feature = "gecko")]
16use malloc_size_of::MallocSizeOfOps;
17use selectors::matching::SelectorCaches;
18use servo_arc::Arc;
19use std::ops::{Deref, DerefMut};
20use std::{fmt, mem};
21
22#[cfg(debug_assertions)]
23use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
24
25bitflags! {
26    /// Various flags stored on ElementData.
27    #[derive(Debug, Default)]
28    pub struct ElementDataFlags: u8 {
29        /// Whether the styles changed for this restyle.
30        const WAS_RESTYLED = 1 << 0;
31        /// Whether the last traversal of this element did not do
32        /// any style computation. This is not true during the initial
33        /// styling pass, nor is it true when we restyle (in which case
34        /// WAS_RESTYLED is set).
35        ///
36        /// This bit always corresponds to the last time the element was
37        /// traversed, so each traversal simply updates it with the appropriate
38        /// value.
39        const TRAVERSED_WITHOUT_STYLING = 1 << 1;
40
41        /// Whether the primary style of this element data was reused from
42        /// another element via a rule node comparison. This allows us to
43        /// differentiate between elements that shared styles because they met
44        /// all the criteria of the style sharing cache, compared to elements
45        /// that reused style structs via rule node identity.
46        ///
47        /// The former gives us stronger transitive guarantees that allows us to
48        /// apply the style sharing cache to cousins.
49        const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2;
50
51        /// Whether this element may have matched rules inside @starting-style.
52        const MAY_HAVE_STARTING_STYLE = 1 << 3;
53    }
54}
55
56/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements.
57///
58/// We use an Arc so that sharing these styles via the style sharing cache does
59/// not require duplicate allocations. We leverage the copy-on-write semantics of
60/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations)
61/// in servo_arc.
62#[derive(Clone, Debug, Default)]
63pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>);
64
65#[derive(Default)]
66struct EagerPseudoArray(EagerPseudoArrayInner);
67type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT];
68
69impl Deref for EagerPseudoArray {
70    type Target = EagerPseudoArrayInner;
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl DerefMut for EagerPseudoArray {
77    fn deref_mut(&mut self) -> &mut Self::Target {
78        &mut self.0
79    }
80}
81
82// Manually implement `Clone` here because the derived impl of `Clone` for
83// array types assumes the value inside is `Copy`.
84impl Clone for EagerPseudoArray {
85    fn clone(&self) -> Self {
86        let mut clone = Self::default();
87        for i in 0..EAGER_PSEUDO_COUNT {
88            clone[i] = self.0[i].clone();
89        }
90        clone
91    }
92}
93
94// Override Debug to print which pseudos we have, and substitute the rule node
95// for the much-more-verbose ComputedValues stringification.
96impl fmt::Debug for EagerPseudoArray {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        write!(f, "EagerPseudoArray {{ ")?;
99        for i in 0..EAGER_PSEUDO_COUNT {
100            if let Some(ref values) = self[i] {
101                write!(
102                    f,
103                    "{:?}: {:?}, ",
104                    PseudoElement::from_eager_index(i),
105                    &values.rules
106                )?;
107            }
108        }
109        write!(f, "}}")
110    }
111}
112
113// Can't use [None; EAGER_PSEUDO_COUNT] here because it complains
114// about Copy not being implemented for our Arc type.
115#[cfg(feature = "gecko")]
116const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None];
117#[cfg(feature = "servo")]
118const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None];
119
120impl EagerPseudoStyles {
121    /// Returns whether there are any pseudo styles.
122    pub fn is_empty(&self) -> bool {
123        self.0.is_none()
124    }
125
126    /// Grabs a reference to the list of styles, if they exist.
127    pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> {
128        match self.0 {
129            None => None,
130            Some(ref x) => Some(&x.0),
131        }
132    }
133
134    /// Grabs a reference to the list of styles or a list of None if
135    /// there are no styles to be had.
136    pub fn as_array(&self) -> &EagerPseudoArrayInner {
137        self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY)
138    }
139
140    /// Returns a reference to the style for a given eager pseudo, if it exists.
141    pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
142        debug_assert!(pseudo.is_eager());
143        self.0
144            .as_ref()
145            .and_then(|p| p[pseudo.eager_index()].as_ref())
146    }
147
148    /// Sets the style for the eager pseudo.
149    pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
150        if self.0.is_none() {
151            self.0 = Some(Arc::new(Default::default()));
152        }
153        let arr = Arc::make_mut(self.0.as_mut().unwrap());
154        arr[pseudo.eager_index()] = Some(value);
155    }
156}
157
158/// The styles associated with a node, including the styles for any
159/// pseudo-elements.
160#[derive(Clone, Default)]
161pub struct ElementStyles {
162    /// The element's style.
163    pub primary: Option<Arc<ComputedValues>>,
164    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
165    pub pseudos: EagerPseudoStyles,
166}
167
168// There's one of these per rendered elements so it better be small.
169size_of_test!(ElementStyles, 16);
170
171/// Information on how this element uses viewport units.
172#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
173pub enum ViewportUnitUsage {
174    /// No viewport units are used.
175    None = 0,
176    /// There are viewport units used from regular style rules (which means we
177    /// should re-cascade).
178    FromDeclaration,
179    /// There are viewport units used from container queries (which means we
180    /// need to re-selector-match).
181    FromQuery,
182}
183
184impl ElementStyles {
185    /// Returns the primary style.
186    pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
187        self.primary.as_ref()
188    }
189
190    /// Returns the primary style.  Panic if no style available.
191    pub fn primary(&self) -> &Arc<ComputedValues> {
192        self.primary.as_ref().unwrap()
193    }
194
195    /// Whether this element `display` value is `none`.
196    pub fn is_display_none(&self) -> bool {
197        self.primary().get_box().clone_display().is_none()
198    }
199
200    /// Whether this element uses viewport units.
201    pub fn viewport_unit_usage(&self) -> ViewportUnitUsage {
202        fn usage_from_flags(flags: ComputedValueFlags) -> ViewportUnitUsage {
203            if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES) {
204                return ViewportUnitUsage::FromQuery;
205            }
206            if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) {
207                return ViewportUnitUsage::FromDeclaration;
208            }
209            ViewportUnitUsage::None
210        }
211
212        let primary = self.primary();
213        let mut usage = usage_from_flags(primary.flags);
214
215        // Check cached lazy pseudos on the primary style.
216        primary.each_cached_lazy_pseudo(|style| {
217            usage = std::cmp::max(usage, usage_from_flags(style.flags));
218        });
219
220        for pseudo_style in self.pseudos.as_array() {
221            if let Some(ref pseudo_style) = pseudo_style {
222                usage = std::cmp::max(usage, usage_from_flags(pseudo_style.flags));
223                // Also check cached lazy pseudos on eager pseudo styles.
224                pseudo_style.each_cached_lazy_pseudo(|style| {
225                    usage = std::cmp::max(usage, usage_from_flags(style.flags));
226                });
227            }
228        }
229
230        usage
231    }
232
233    #[cfg(feature = "gecko")]
234    fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize {
235        // As the method name suggests, we don't measures the ComputedValues
236        // here, because they are measured on the C++ side.
237
238        // XXX: measure the EagerPseudoArray itself, but not the ComputedValues
239        // within it.
240
241        0
242    }
243}
244
245// We manually implement Debug for ElementStyles so that we can avoid the
246// verbose stringification of every property in the ComputedValues. We
247// substitute the rule node instead.
248impl fmt::Debug for ElementStyles {
249    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250        write!(
251            f,
252            "ElementStyles {{ primary: {:?}, pseudos: {:?} }}",
253            self.primary.as_ref().map(|x| &x.rules),
254            self.pseudos
255        )
256    }
257}
258
259/// Style system data associated with an Element.
260///
261/// In Gecko, this hangs directly off the Element. Servo, this is embedded
262/// inside of layout data, which itself hangs directly off the Element. In
263/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
264#[derive(Debug, Default)]
265pub struct ElementData {
266    /// The styles for the element and its pseudo-elements.
267    pub styles: ElementStyles,
268
269    /// The restyle damage, indicating what kind of layout changes are required
270    /// afte restyling.
271    pub damage: RestyleDamage,
272
273    /// The restyle hint, which indicates whether selectors need to be rematched
274    /// for this element, its children, and its descendants.
275    pub hint: RestyleHint,
276
277    /// Flags.
278    pub flags: ElementDataFlags,
279}
280
281/// A struct that wraps ElementData, giving it the ability of doing thread-safety checks.
282#[derive(Debug, Default)]
283pub struct ElementDataWrapper {
284    inner: std::cell::UnsafeCell<ElementData>,
285    /// Implements optional (debug_assertions-only) thread-safety checking.
286    #[cfg(debug_assertions)]
287    refcell: AtomicRefCell<()>,
288}
289
290/// A read-only reference to ElementData.
291#[derive(Debug)]
292pub struct ElementDataMut<'a> {
293    v: &'a mut ElementData,
294    #[cfg(debug_assertions)]
295    _borrow: AtomicRefMut<'a, ()>,
296}
297
298/// A mutable reference to ElementData.
299#[derive(Debug)]
300pub struct ElementDataRef<'a> {
301    v: &'a ElementData,
302    #[cfg(debug_assertions)]
303    _borrow: AtomicRef<'a, ()>,
304}
305
306impl ElementDataWrapper {
307    /// Gets a non-exclusive reference to this ElementData.
308    #[inline(always)]
309    pub fn borrow(&self) -> ElementDataRef<'_> {
310        #[cfg(debug_assertions)]
311        let borrow = self.refcell.borrow();
312        ElementDataRef {
313            v: unsafe { &*self.inner.get() },
314            #[cfg(debug_assertions)]
315            _borrow: borrow,
316        }
317    }
318
319    /// Gets an exclusive reference to this ElementData.
320    #[inline(always)]
321    pub fn borrow_mut(&self) -> ElementDataMut<'_> {
322        #[cfg(debug_assertions)]
323        let borrow = self.refcell.borrow_mut();
324        ElementDataMut {
325            v: unsafe { &mut *self.inner.get() },
326            #[cfg(debug_assertions)]
327            _borrow: borrow,
328        }
329    }
330}
331
332impl<'a> Deref for ElementDataRef<'a> {
333    type Target = ElementData;
334    #[inline]
335    fn deref(&self) -> &Self::Target {
336        &*self.v
337    }
338}
339
340impl<'a> Deref for ElementDataMut<'a> {
341    type Target = ElementData;
342    #[inline]
343    fn deref(&self) -> &Self::Target {
344        &*self.v
345    }
346}
347
348impl<'a> DerefMut for ElementDataMut<'a> {
349    fn deref_mut(&mut self) -> &mut Self::Target {
350        &mut *self.v
351    }
352}
353
354// There's one of these per rendered elements so it better be small.
355size_of_test!(ElementData, 24);
356
357/// The kind of restyle that a single element should do.
358#[derive(Debug)]
359pub enum RestyleKind {
360    /// We need to run selector matching plus re-cascade, that is, a full
361    /// restyle.
362    MatchAndCascade,
363    /// We need to recascade with some replacement rule, such as the style
364    /// attribute, or animation rules.
365    CascadeWithReplacements(RestyleHint),
366    /// We only need to recascade, for example, because only inherited
367    /// properties in the parent changed.
368    CascadeOnly,
369}
370
371impl ElementData {
372    /// Invalidates style for this element, its descendants, and later siblings,
373    /// based on the snapshot of the element that we took when attributes or
374    /// state changed.
375    pub fn invalidate_style_if_needed<'a, E: TElement>(
376        &mut self,
377        element: E,
378        shared_context: &SharedStyleContext,
379        stack_limit_checker: Option<&StackLimitChecker>,
380        selector_caches: &'a mut SelectorCaches,
381    ) -> InvalidationResult {
382        // In animation-only restyle we shouldn't touch snapshot at all.
383        if shared_context.traversal_flags.for_animation_only() {
384            return InvalidationResult::empty();
385        }
386
387        use crate::invalidation::element::invalidator::TreeStyleInvalidator;
388        use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor;
389
390        debug!(
391            "invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
392             handled_snapshot: {}, pseudo: {:?}",
393            element,
394            shared_context.traversal_flags,
395            element.has_snapshot(),
396            element.handled_snapshot(),
397            element.implemented_pseudo_element()
398        );
399
400        if !element.has_snapshot() || element.handled_snapshot() {
401            return InvalidationResult::empty();
402        }
403
404        let mut processor =
405            StateAndAttrInvalidationProcessor::new(shared_context, element, self, selector_caches);
406
407        let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor);
408
409        let result = invalidator.invalidate();
410
411        unsafe { element.set_handled_snapshot() }
412        debug_assert!(element.handled_snapshot());
413
414        result
415    }
416
417    /// Returns true if this element has styles.
418    #[inline]
419    pub fn has_styles(&self) -> bool {
420        self.styles.primary.is_some()
421    }
422
423    /// Returns this element's styles as resolved styles to use for sharing.
424    pub fn share_styles(&self) -> ResolvedElementStyles {
425        ResolvedElementStyles {
426            primary: self.share_primary_style(),
427            pseudos: self.styles.pseudos.clone(),
428        }
429    }
430
431    /// Returns this element's primary style as a resolved style to use for sharing.
432    pub fn share_primary_style(&self) -> PrimaryStyle {
433        let reused_via_rule_node = self
434            .flags
435            .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
436        let may_have_starting_style = self
437            .flags
438            .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE);
439
440        PrimaryStyle {
441            style: ResolvedStyle(self.styles.primary().clone()),
442            reused_via_rule_node,
443            may_have_starting_style,
444        }
445    }
446
447    /// Sets a new set of styles, returning the old ones.
448    pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
449        self.flags.set(
450            ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
451            new_styles.primary.reused_via_rule_node,
452        );
453        self.flags.set(
454            ElementDataFlags::MAY_HAVE_STARTING_STYLE,
455            new_styles.primary.may_have_starting_style,
456        );
457
458        mem::replace(&mut self.styles, new_styles.into())
459    }
460
461    /// Returns the kind of restyling that we're going to need to do on this
462    /// element, based of the stored restyle hint.
463    pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option<RestyleKind> {
464        let style = match self.styles.primary {
465            Some(ref s) => s,
466            None => return Some(RestyleKind::MatchAndCascade),
467        };
468
469        if shared_context.traversal_flags.for_animation_only() {
470            return self.restyle_kind_for_animation(shared_context);
471        }
472
473        let hint = self.hint;
474        if hint.is_empty() {
475            return None;
476        }
477
478        let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF)
479            || (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style());
480        if needs_to_match_self {
481            return Some(RestyleKind::MatchAndCascade);
482        }
483
484        if hint.has_replacements() {
485            debug_assert!(
486                !hint.has_animation_hint(),
487                "Animation only restyle hint should have already processed"
488            );
489            return Some(RestyleKind::CascadeWithReplacements(
490                hint & RestyleHint::replacements(),
491            ));
492        }
493
494        let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF)
495            || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE)
496                && style
497                    .flags
498                    .contains(ComputedValueFlags::INHERITS_RESET_STYLE));
499        if needs_to_recascade_self {
500            return Some(RestyleKind::CascadeOnly);
501        }
502
503        None
504    }
505
506    /// Returns the kind of restyling for animation-only restyle.
507    fn restyle_kind_for_animation(
508        &self,
509        shared_context: &SharedStyleContext,
510    ) -> Option<RestyleKind> {
511        debug_assert!(shared_context.traversal_flags.for_animation_only());
512        debug_assert!(self.has_styles());
513
514        // FIXME: We should ideally restyle here, but it is a hack to work around our weird
515        // animation-only traversal stuff: If we're display: none and the rules we could
516        // match could change, we consider our style up-to-date. This is because re-cascading with
517        // and old style doesn't guarantee returning the correct animation style (that's
518        // bug 1393323). So if our display changed, and it changed from display: none, we would
519        // incorrectly forget about it and wouldn't be able to correctly style our descendants
520        // later.
521        // XXX Figure out if this still makes sense.
522        let hint = self.hint;
523        if self.styles.is_display_none() && hint.intersects(RestyleHint::RESTYLE_SELF) {
524            return None;
525        }
526
527        let style = self.styles.primary();
528        // Return either CascadeWithReplacements or CascadeOnly in case of animation-only restyle.
529        // I.e. animation-only restyle never does selector matching.
530        if hint.has_animation_hint() {
531            return Some(RestyleKind::CascadeWithReplacements(
532                hint & RestyleHint::for_animations(),
533            ));
534        }
535
536        let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF)
537            || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE)
538                && style
539                    .flags
540                    .contains(ComputedValueFlags::INHERITS_RESET_STYLE));
541        if needs_to_recascade_self {
542            return Some(RestyleKind::CascadeOnly);
543        }
544        return None;
545    }
546
547    /// Drops any restyle state from the element.
548    ///
549    /// FIXME(bholley): The only caller of this should probably just assert that the hint is empty
550    /// and call clear_flags_and_damage().
551    #[inline]
552    pub fn clear_restyle_state(&mut self) {
553        self.hint = RestyleHint::empty();
554        self.clear_restyle_flags_and_damage();
555    }
556
557    /// Drops restyle flags and damage from the element.
558    #[inline]
559    pub fn clear_restyle_flags_and_damage(&mut self) {
560        self.damage = RestyleDamage::empty();
561        self.flags.remove(ElementDataFlags::WAS_RESTYLED);
562    }
563
564    /// Mark this element as restyled, which is useful to know whether we need
565    /// to do a post-traversal.
566    pub fn set_restyled(&mut self) {
567        self.flags.insert(ElementDataFlags::WAS_RESTYLED);
568        self.flags
569            .remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
570    }
571
572    /// Returns true if this element was restyled.
573    #[inline]
574    pub fn is_restyle(&self) -> bool {
575        self.flags.contains(ElementDataFlags::WAS_RESTYLED)
576    }
577
578    /// Mark that we traversed this element without computing any style for it.
579    pub fn set_traversed_without_styling(&mut self) {
580        self.flags
581            .insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
582    }
583
584    /// Returns whether this element has been part of a restyle.
585    #[inline]
586    pub fn contains_restyle_data(&self) -> bool {
587        self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
588    }
589
590    /// Returns whether it is safe to perform cousin sharing based on the ComputedValues
591    /// identity of the primary style in this ElementData. There are a few subtle things
592    /// to check.
593    ///
594    /// First, if a parent element was already styled and we traversed past it without
595    /// restyling it, that may be because our clever invalidation logic was able to prove
596    /// that the styles of that element would remain unchanged despite changes to the id
597    /// or class attributes. However, style sharing relies on the strong guarantee that all
598    /// the classes and ids up the respective parent chains are identical. As such, if we
599    /// skipped styling for one (or both) of the parents on this traversal, we can't share
600    /// styles across cousins. Note that this is a somewhat conservative check. We could
601    /// tighten it by having the invalidation logic explicitly flag elements for which it
602    /// ellided styling.
603    ///
604    /// Second, we want to only consider elements whose ComputedValues match due to a hit
605    /// in the style sharing cache, rather than due to the rule-node-based reuse that
606    /// happens later in the styling pipeline. The former gives us the stronger guarantees
607    /// we need for style sharing, the latter does not.
608    pub fn safe_for_cousin_sharing(&self) -> bool {
609        if self.flags.intersects(
610            ElementDataFlags::TRAVERSED_WITHOUT_STYLING
611                | ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
612        ) {
613            return false;
614        }
615        if !self
616            .styles
617            .primary()
618            .get_box()
619            .clone_container_type()
620            .is_normal()
621        {
622            return false;
623        }
624        true
625    }
626
627    /// Measures memory usage.
628    #[cfg(feature = "gecko")]
629    pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize {
630        let n = self.styles.size_of_excluding_cvs(ops);
631
632        // We may measure more fields in the future if DMD says it's worth it.
633
634        n
635    }
636
637    /// Returns true if this element data may need to compute the starting style for CSS
638    /// transitions.
639    #[inline]
640    pub fn may_have_starting_style(&self) -> bool {
641        self.flags
642            .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE)
643    }
644}