Skip to main content

style/servo/
restyle_damage.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//! The restyle damage is a hint that tells layout which kind of operations may
6//! be needed in presence of incremental style changes.
7
8use bitflags::Flags;
9
10use crate::computed_values::isolation::T as Isolation;
11use crate::computed_values::mix_blend_mode::T as MixBlendMode;
12use crate::computed_values::transform_style::T as TransformStyle;
13use crate::dom::TElement;
14use crate::matching::{StyleChange, StyleDifference};
15use crate::properties::{
16    restyle_damage_rebuild_box, restyle_damage_rebuild_stacking_context,
17    restyle_damage_recalculate_overflow, restyle_damage_repaint, style_structs, ComputedValues,
18};
19use crate::values::computed::basic_shape::ClipPath;
20use crate::values::computed::Perspective;
21use crate::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
22use std::fmt;
23
24bitflags! {
25    /// Major phases of layout that need to be run due to the damage to a node during restyling. In
26    /// addition to the 4 bytes used for that, the rest of the `u16` is exposed as an extension point
27    /// for users of the crate to add their own custom types of damage that correspond to the
28    /// layout system they are implementing.
29    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
30    pub struct ServoRestyleDamage: u16 {
31        /// Repaint the node itself.
32        ///
33        /// Propagates both up and down the flow tree.
34        const REPAINT = 0b0001;
35
36        /// Rebuilds the stacking contexts.
37        ///
38        /// Propagates both up and down the flow tree.
39        const REBUILD_STACKING_CONTEXT = 0b0011;
40
41        /// Recalculates the scrollable overflow.
42        ///
43        /// Propagates both up and down the flow tree.
44        const RECALCULATE_OVERFLOW = 0b0111;
45
46        /// Any other type of damage, which requires running layout again.
47        ///
48        /// Propagates both up and down the flow tree.
49        const RELAYOUT = 0b1111;
50    }
51}
52
53malloc_size_of::malloc_size_of_is_0!(ServoRestyleDamage);
54
55impl ServoRestyleDamage {
56    /// Compute the `StyleDifference` (including the appropriate restyle damage)
57    /// for a given style change between `old` and `new`.
58    pub fn compute_style_difference<E: TElement>(
59        old: &ComputedValues,
60        new: &ComputedValues,
61    ) -> StyleDifference {
62        if std::ptr::eq(old, new) {
63            return StyleDifference {
64                damage: ServoRestyleDamage::empty(),
65                change: StyleChange::Unchanged,
66            };
67        }
68
69        let mut damage = compute_damage(old, new);
70        if damage.contains(ServoRestyleDamage::RELAYOUT) {
71            damage |= E::compute_layout_damage(old, new);
72        }
73
74        let mut any_style_changed = !damage.is_empty();
75        // FIXME(emilio): Differentiate between reset and inherited
76        // properties here, and set `reset_only` appropriately so the
77        // optimization to skip the cascade in those cases applies.
78        let mut reset_only = !any_style_changed;
79
80        let old_custom_props = old.custom_properties();
81        let new_custom_props = new.custom_properties();
82        let mut custom_properties_changed = false;
83        if old_custom_props.inherited != new_custom_props.inherited {
84            any_style_changed = true;
85            custom_properties_changed = true;
86            reset_only = false;
87        } else if old_custom_props.non_inherited != new_custom_props.non_inherited {
88            any_style_changed = true;
89            custom_properties_changed = true;
90        };
91        if custom_properties_changed {
92            // Paint worklets may depend on custom properties, so if they have changed we should repaint.
93            damage.insert(ServoRestyleDamage::REPAINT);
94        }
95
96        let change = if any_style_changed {
97            StyleChange::Changed {
98                reset_only,
99                custom_properties_changed,
100            }
101        } else {
102            StyleChange::Unchanged
103        };
104
105        StyleDifference { damage, change }
106    }
107
108    /// Returns a bitmask indicating that the frame needs to be reconstructed.
109    pub fn reconstruct() -> ServoRestyleDamage {
110        // There's no way of knowing what kind of damage system the embedder will use, but part of
111        // this interface is that a fully saturated restyle damage means to rebuild everything.
112        ServoRestyleDamage::from_bits_retain(<ServoRestyleDamage as Flags>::Bits::MAX)
113    }
114}
115
116impl Default for ServoRestyleDamage {
117    fn default() -> Self {
118        Self::empty()
119    }
120}
121
122impl fmt::Display for ServoRestyleDamage {
123    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
124        let mut first_elem = true;
125
126        let to_iter = [
127            (ServoRestyleDamage::REPAINT, "Repaint"),
128            (
129                ServoRestyleDamage::REBUILD_STACKING_CONTEXT,
130                "Rebuild stacking context",
131            ),
132            (
133                ServoRestyleDamage::RECALCULATE_OVERFLOW,
134                "Recalculate overflow",
135            ),
136            (ServoRestyleDamage::RELAYOUT, "Relayout"),
137        ];
138
139        for &(damage, damage_str) in &to_iter {
140            if self.contains(damage) {
141                if !first_elem {
142                    write!(f, " | ")?;
143                }
144                write!(f, "{}", damage_str)?;
145                first_elem = false;
146            }
147        }
148
149        if first_elem {
150            write!(f, "NoDamage")?;
151        }
152
153        Ok(())
154    }
155}
156
157fn augmented_restyle_damage_rebuild_box(old: &ComputedValues, new: &ComputedValues) -> bool {
158    let old_box = old.get_box();
159    let new_box = new.get_box();
160    restyle_damage_rebuild_box(old, new)
161        || old_box.original_display != new_box.original_display
162        || old_box.has_transform_or_perspective() != new_box.has_transform_or_perspective()
163        || old.get_effects().filter.0.is_empty() != new.get_effects().filter.0.is_empty()
164}
165
166fn augmented_restyle_damage_rebuild_stacking_context(
167    old: &ComputedValues,
168    new: &ComputedValues,
169) -> bool {
170    restyle_damage_rebuild_stacking_context(old, new)
171        || old.guarantees_stacking_context() != new.guarantees_stacking_context()
172}
173
174fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage {
175    // Damage flags higher up the if-else chain imply damage flags lower down the if-else chain,
176    // so we can skip the diffing process for later flags if an earlier flag is true
177    if augmented_restyle_damage_rebuild_box(old, new) {
178        ServoRestyleDamage::RELAYOUT
179    } else if restyle_damage_recalculate_overflow(old, new) {
180        ServoRestyleDamage::RECALCULATE_OVERFLOW
181    } else if augmented_restyle_damage_rebuild_stacking_context(old, new) {
182        ServoRestyleDamage::REBUILD_STACKING_CONTEXT
183    } else if restyle_damage_repaint(old, new) {
184        ServoRestyleDamage::REPAINT
185    } else {
186        ServoRestyleDamage::empty()
187    }
188}
189
190impl ComputedValues {
191    /// Some properties establish a stacking context when they are set to a non-initial value.
192    /// In that case, the damage is only set to `ServoRestyleDamage::REPAINT` because we don't
193    /// need to rebuild stacking contexts when the style changes between different non-initial
194    /// values. This function checks whether any of these properties is set to a value that
195    /// guarantees a stacking context, so that we only do the work when this changes.
196    /// Note that it's still possible to establish a stacking context when this returns false.
197    pub fn guarantees_stacking_context(&self) -> bool {
198        self.get_effects().opacity != 1.0
199            || self.get_effects().mix_blend_mode != MixBlendMode::Normal
200            || self.get_svg().clip_path != ClipPath::None
201            || self.get_box().isolation == Isolation::Isolate
202    }
203}
204
205impl style_structs::Box {
206    /// Whether there is a non-default transform or perspective style set
207    pub fn has_transform_or_perspective(&self) -> bool {
208        !self.transform.0.is_empty()
209            || self.scale != GenericScale::None
210            || self.rotate != GenericRotate::None
211            || self.translate != GenericTranslate::None
212            || self.perspective != Perspective::None
213            || self.transform_style == TransformStyle::Preserve3d
214    }
215}