/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The restyle damage is a hint that tells layout which kind of operations may //! be needed in presence of incremental style changes. use crate::computed_values::display::T as Display; use crate::matching::{StyleChange, StyleDifference}; use crate::properties::ComputedValues; use std::fmt; bitflags! { /// Individual layout actions that may be necessary after restyling. pub struct ServoRestyleDamage: u8 { /// Repaint the node itself. /// /// Currently unused; need to decide how this propagates. const REPAINT = 0x01; /// The stacking-context-relative position of this node or its /// descendants has changed. /// /// Propagates both up and down the flow tree. const REPOSITION = 0x02; /// Recompute the overflow regions (bounding box of object and all descendants). /// /// Propagates down the flow tree because the computation is bottom-up. const STORE_OVERFLOW = 0x04; /// Recompute intrinsic inline_sizes (minimum and preferred). /// /// Propagates down the flow tree because the computation is. /// bottom-up. const BUBBLE_ISIZES = 0x08; /// Recompute actual inline-sizes and block-sizes, only taking /// out-of-flow children into account. /// /// Propagates up the flow tree because the computation is top-down. const REFLOW_OUT_OF_FLOW = 0x10; /// Recompute actual inline_sizes and block_sizes. /// /// Propagates up the flow tree because the computation is top-down. const REFLOW = 0x20; /// Re-resolve generated content. /// /// Propagates up the flow tree because the computation is inorder. const RESOLVE_GENERATED_CONTENT = 0x40; /// The entire flow needs to be reconstructed. const RECONSTRUCT_FLOW = 0x80; } } malloc_size_of_is_0!(ServoRestyleDamage); impl ServoRestyleDamage { /// Compute the `StyleDifference` (including the appropriate restyle damage) /// for a given style change between `old` and `new`. pub fn compute_style_difference(old: &ComputedValues, new: &ComputedValues) -> StyleDifference { let damage = compute_damage(old, new); let change = if damage.is_empty() { StyleChange::Unchanged } else { // FIXME(emilio): Differentiate between reset and inherited // properties here, and set `reset_only` appropriately so the // optimization to skip the cascade in those cases applies. StyleChange::Changed { reset_only: false } }; StyleDifference { damage, change } } /// Returns a bitmask that represents a flow that needs to be rebuilt and /// reflowed. /// /// FIXME(bholley): Do we ever actually need this? Shouldn't /// RECONSTRUCT_FLOW imply everything else? pub fn rebuild_and_reflow() -> ServoRestyleDamage { ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION | ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::BUBBLE_ISIZES | ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW | ServoRestyleDamage::RECONSTRUCT_FLOW } /// Returns a bitmask indicating that the frame needs to be reconstructed. pub fn reconstruct() -> ServoRestyleDamage { ServoRestyleDamage::RECONSTRUCT_FLOW } /// Supposing a flow has the given `position` property and this damage, /// returns the damage that we should add to the *parent* of this flow. pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> ServoRestyleDamage { if child_is_absolutely_positioned { self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION | ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) } else { self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION | ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) } } /// Supposing the *parent* of a flow with the given `position` property has /// this damage, returns the damage that we should add to this flow. pub fn damage_for_child( self, parent_is_absolutely_positioned: bool, child_is_absolutely_positioned: bool, ) -> ServoRestyleDamage { match ( parent_is_absolutely_positioned, child_is_absolutely_positioned, ) { (false, true) => { // Absolute children are out-of-flow and therefore insulated from changes. // // FIXME(pcwalton): Au contraire, if the containing block dimensions change! self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION) }, (true, false) => { // Changing the position of an absolutely-positioned block requires us to reflow // its kids. if self.contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) { self | ServoRestyleDamage::REFLOW } else { self } }, _ => { // TODO(pcwalton): Take floatedness into account. self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION | ServoRestyleDamage::REFLOW) }, } } } impl Default for ServoRestyleDamage { fn default() -> Self { Self::empty() } } impl fmt::Display for ServoRestyleDamage { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { let mut first_elem = true; let to_iter = [ (ServoRestyleDamage::REPAINT, "Repaint"), (ServoRestyleDamage::REPOSITION, "Reposition"), (ServoRestyleDamage::STORE_OVERFLOW, "StoreOverflow"), (ServoRestyleDamage::BUBBLE_ISIZES, "BubbleISizes"), (ServoRestyleDamage::REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow"), (ServoRestyleDamage::REFLOW, "Reflow"), ( ServoRestyleDamage::RESOLVE_GENERATED_CONTENT, "ResolveGeneratedContent", ), (ServoRestyleDamage::RECONSTRUCT_FLOW, "ReconstructFlow"), ]; for &(damage, damage_str) in &to_iter { if self.contains(damage) { if !first_elem { write!(f, " | ")?; } write!(f, "{}", damage_str)?; first_elem = false; } } if first_elem { write!(f, "NoDamage")?; } Ok(()) } } fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage { let mut damage = ServoRestyleDamage::empty(); // This should check every CSS property, as enumerated in the fields of // https://doc.servo.org/style/properties/struct.ComputedValues.html // This uses short-circuiting boolean OR for its side effects and ignores the result. let _ = restyle_damage_rebuild_and_reflow!( old, new, damage, [ ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION, ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES, ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW, ServoRestyleDamage::RECONSTRUCT_FLOW ], old.get_box().original_display != new.get_box().original_display ) || (new.get_box().display == Display::Inline && restyle_damage_rebuild_and_reflow_inline!( old, new, damage, [ ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION, ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES, ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW, ServoRestyleDamage::RECONSTRUCT_FLOW ] )) || restyle_damage_reflow!( old, new, damage, [ ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION, ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES, ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW ] ) || restyle_damage_reflow_out_of_flow!( old, new, damage, [ ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION, ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::REFLOW_OUT_OF_FLOW ] ) || restyle_damage_repaint!(old, new, damage, [ServoRestyleDamage::REPAINT]); // Paint worklets may depend on custom properties, // so if they have changed we should repaint. if !old.custom_properties_equal(new) { damage.insert(ServoRestyleDamage::REPAINT); } // If the layer requirements of this flow have changed due to the value // of the transform, then reflow is required to rebuild the layers. if old.transform_requires_layer() != new.transform_requires_layer() { damage.insert(ServoRestyleDamage::rebuild_and_reflow()); } damage }