/* 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/. */ //! High-level interface to CSS selector matching. #![allow(unsafe_code)] #![deny(missing_docs)] use crate::computed_value_flags::ComputedValueFlags; #[cfg(feature = "servo")] use crate::context::CascadeInputs; use crate::context::{ElementCascadeInputs, QuirksMode}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles}; use crate::dom::TElement; #[cfg(feature = "servo")] use crate::dom::TNode; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::properties::longhands::display::computed_value::T as Display; use crate::properties::ComputedValues; use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StrongRuleNode}; use crate::selector_parser::{PseudoElement, RestyleDamage}; use crate::shared_lock::Locked; use crate::style_resolver::StyleResolverForElement; use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles}; use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use servo_arc::{Arc, ArcBorrow}; /// Represents the result of comparing an element's old and new style. #[derive(Debug)] pub struct StyleDifference { /// The resulting damage. pub damage: RestyleDamage, /// Whether any styles changed. pub change: StyleChange, } /// Represents whether or not the style of an element has changed. #[derive(Clone, Copy, Debug)] pub enum StyleChange { /// The style hasn't changed. Unchanged, /// The style has changed. Changed { /// Whether only reset structs changed. reset_only: bool, }, } /// Whether or not newly computed values for an element need to be cascaded to /// children (or children might need to be re-matched, e.g., for container /// queries). #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum ChildRestyleRequirement { /// Old and new computed values were the same, or we otherwise know that /// we won't bother recomputing style for children, so we can skip cascading /// the new values into child elements. CanSkipCascade = 0, /// The same as `MustCascadeChildren`, but we only need to actually /// recascade if the child inherits any explicit reset style. MustCascadeChildrenIfInheritResetStyle = 1, /// Old and new computed values were different, so we must cascade the /// new values to children. MustCascadeChildren = 2, /// The same as `MustCascadeChildren`, but for the entire subtree. This is /// used to handle root font-size updates needing to recascade the whole /// document. MustCascadeDescendants = 3, /// We need to re-match the whole subttree. This is used to handle container /// query relative unit changes for example. Container query size changes /// also trigger re-match, but after layout. MustMatchDescendants = 4, } /// Determines which styles are being cascaded currently. #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum CascadeVisitedMode { /// Cascade the regular, unvisited styles. Unvisited, /// Cascade the styles used when an element's relevant link is visited. A /// "relevant link" is the element being matched if it is a link or the /// nearest ancestor link. Visited, } trait PrivateMatchMethods: TElement { fn replace_single_rule_node( context: &SharedStyleContext, level: CascadeLevel, layer_order: LayerOrder, pdb: Option>>, path: &mut StrongRuleNode, ) -> bool { let stylist = &context.stylist; let guards = &context.guards; let mut important_rules_changed = false; let new_node = stylist.rule_tree().update_rule_at_level( level, layer_order, pdb, path, guards, &mut important_rules_changed, ); if let Some(n) = new_node { *path = n; } important_rules_changed } /// Updates the rule nodes without re-running selector matching, using just /// the rule tree, for a specific visited mode. /// /// Returns true if an !important rule was replaced. fn replace_rules_internal( &self, replacements: RestyleHint, context: &mut StyleContext, cascade_visited: CascadeVisitedMode, cascade_inputs: &mut ElementCascadeInputs, ) -> bool { debug_assert!( replacements.intersects(RestyleHint::replacements()) && (replacements & !RestyleHint::replacements()).is_empty() ); let primary_rules = match cascade_visited { CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(), CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(), }; let primary_rules = match primary_rules { Some(r) => r, None => return false, }; if !context.shared.traversal_flags.for_animation_only() { let mut result = false; if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) { let style_attribute = self.style_attribute(); result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_normal(), LayerOrder::style_attribute(), style_attribute, primary_rules, ); result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_important(), LayerOrder::style_attribute(), style_attribute, primary_rules, ); // FIXME(emilio): Still a hack! self.unset_dirty_style_attribute(); } return result; } // Animation restyle hints are processed prior to other restyle // hints in the animation-only traversal. // // Non-animation restyle hints will be processed in a subsequent // normal traversal. if replacements.intersects(RestyleHint::for_animations()) { debug_assert!(context.shared.traversal_flags.for_animation_only()); if replacements.contains(RestyleHint::RESTYLE_SMIL) { Self::replace_single_rule_node( context.shared, CascadeLevel::SMILOverride, LayerOrder::root(), self.smil_override(), primary_rules, ); } if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { Self::replace_single_rule_node( context.shared, CascadeLevel::Transitions, LayerOrder::root(), self.transition_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), primary_rules, ); } if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { Self::replace_single_rule_node( context.shared, CascadeLevel::Animations, LayerOrder::root(), self.animation_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), primary_rules, ); } } false } /// If there is no transition rule in the ComputedValues, it returns None. fn after_change_style( &self, context: &mut StyleContext, primary_style: &Arc, ) -> Option> { // Actually `PseudoElementResolution` doesn't really matter. StyleResolverForElement::new( *self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable, ) .after_change_style(primary_style) } fn needs_animations_update( &self, context: &mut StyleContext, old_style: Option<&ComputedValues>, new_style: &ComputedValues, pseudo_element: Option, ) -> bool { let new_ui_style = new_style.get_ui(); let new_style_specifies_animations = new_ui_style.specifies_animations(); let has_animations = self.has_css_animations(&context.shared, pseudo_element); if !new_style_specifies_animations && !has_animations { return false; } let old_style = match old_style { Some(old) => old, // If we have no old style but have animations, we may be a // pseudo-element which was re-created without style changes. // // This can happen when we reframe the pseudo-element without // restyling it (due to content insertion on a flex container or // such, for example). See bug 1564366. // // FIXME(emilio): The really right fix for this is keeping the // pseudo-element itself around on reframes, but that's a bit // harder. If we do that we can probably remove quite a lot of the // EffectSet complexity though, since right now it's stored on the // parent element for pseudo-elements given we need to keep it // around... None => { return new_style_specifies_animations || new_style.is_pseudo_style(); }, }; let old_ui_style = old_style.get_ui(); let keyframes_could_have_changed = context .shared .traversal_flags .contains(TraversalFlags::ForCSSRuleChanges); // If the traversal is triggered due to changes in CSS rules changes, we // need to try to update all CSS animations on the element if the // element has or will have CSS animation style regardless of whether // the animation is running or not. // // TODO: We should check which @keyframes were added/changed/deleted and // update only animations corresponding to those @keyframes. if keyframes_could_have_changed { return true; } // If the animations changed, well... if !old_ui_style.animations_equals(new_ui_style) { return true; } let old_display = old_style.clone_display(); let new_display = new_style.clone_display(); // If we were display: none, we may need to trigger animations. if old_display == Display::None && new_display != Display::None { return new_style_specifies_animations; } // If we are becoming display: none, we may need to stop animations. if old_display != Display::None && new_display == Display::None { return has_animations; } // We might need to update animations if writing-mode or direction // changed, and any of the animations contained logical properties. // // We may want to be more granular, but it's probably not worth it. if new_style.writing_mode != old_style.writing_mode { return has_animations; } false } fn might_need_transitions_update( &self, context: &StyleContext, old_style: Option<&ComputedValues>, new_style: &ComputedValues, pseudo_element: Option, ) -> bool { let old_style = match old_style { Some(v) => v, None => return false, }; if !self.has_css_transitions(context.shared, pseudo_element) && !new_style.get_ui().specifies_transitions() { return false; } if old_style.clone_display().is_none() { return false; } return true; } /// Create a SequentialTask for resolving descendants in a SMIL display /// property animation if the display property changed from none. #[cfg(feature = "gecko")] fn handle_display_change_for_smil_if_needed( &self, context: &mut StyleContext, old_values: Option<&ComputedValues>, new_values: &ComputedValues, restyle_hints: RestyleHint, ) { use crate::context::PostAnimationTasks; if !restyle_hints.intersects(RestyleHint::RESTYLE_SMIL) { return; } if new_values.is_display_property_changed_from_none(old_values) { // When display value is changed from none to other, we need to // traverse descendant elements in a subsequent normal // traversal (we can't traverse them in this animation-only restyle // since we have no way to know whether the decendants // need to be traversed at the beginning of the animation-only // restyle). let task = crate::context::SequentialTask::process_post_animation( *self, PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL, ); context.thread_local.tasks.push(task); } } #[cfg(feature = "gecko")] fn maybe_resolve_starting_style( &self, context: &mut StyleContext, old_values: Option<&Arc>, new_styles: &ResolvedElementStyles, ) -> Option> { // For both cases: // 1. If we didn't see any starting-style rules for this given element during full matching. // 2. If there is no transitions specified. // We don't have to resolve starting style. if !new_styles.may_have_starting_style() || !new_styles.primary_style().get_ui().specifies_transitions() { return None; } // We resolve starting style only if we don't have before-change-style, or we change from // display:none. if old_values.is_some() && !new_styles .primary_style() .is_display_property_changed_from_none(old_values.map(|s| &**s)) { return None; } // Note: Basically, we have to remove transition rules because the starting style for an // element is the after-change style with @starting-style rules applied in addition. // However, we expect there is no transition rules for this element when calling this // function because we do this only when we don't have before-change style or we change // from display:none. In these cases, it's unlikely to have running transitions on this // element. let mut resolver = StyleResolverForElement::new( *self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable, ); let starting_style = resolver.resolve_starting_style().style; if starting_style.style().clone_display().is_none() { return None; } Some(starting_style.0) } /// Handle CSS Transitions. Returns None if we don't need to update transitions. And it returns /// the before-change style per CSS Transitions spec. /// /// Note: The before-change style could be the computed values of all properties on the element /// as of the previous style change event, or the starting style if we don't have the valid /// before-change style there. #[cfg(feature = "gecko")] fn process_transitions( &self, context: &mut StyleContext, old_values: Option<&Arc>, new_styles: &mut ResolvedElementStyles, ) -> Option> { let starting_values = self.maybe_resolve_starting_style(context, old_values, new_styles); let before_change_or_starting = if starting_values.is_some() { starting_values.as_ref() } else { old_values }; let new_values = new_styles.primary_style_mut(); if !self.might_need_transitions_update( context, before_change_or_starting.map(|s| &**s), new_values, /* pseudo_element = */ None, ) { return None; } let after_change_style = if self.has_css_transitions(context.shared, /* pseudo_element = */ None) { self.after_change_style(context, new_values) } else { None }; // In order to avoid creating a SequentialTask for transitions which // may not be updated, we check it per property to make sure Gecko // side will really update transition. if !self.needs_transitions_update( before_change_or_starting.unwrap(), after_change_style.as_ref().unwrap_or(&new_values), ) { return None; } if let Some(values_without_transitions) = after_change_style { *new_values = values_without_transitions; } // Move the new-created starting style, or clone the old values. if starting_values.is_some() { starting_values } else { old_values.cloned() } } #[cfg(feature = "gecko")] fn process_animations( &self, context: &mut StyleContext, old_styles: &mut ElementStyles, new_styles: &mut ResolvedElementStyles, restyle_hint: RestyleHint, important_rules_changed: bool, ) { use crate::context::UpdateAnimationsTasks; let old_values = &old_styles.primary; if context.shared.traversal_flags.for_animation_only() { self.handle_display_change_for_smil_if_needed( context, old_values.as_deref(), new_styles.primary_style(), restyle_hint, ); return; } // Bug 868975: These steps should examine and update the visited styles // in addition to the unvisited styles. let mut tasks = UpdateAnimationsTasks::empty(); if old_values.as_deref().map_or_else( || { new_styles .primary_style() .get_ui() .specifies_scroll_timelines() }, |old| { !old.get_ui() .scroll_timelines_equals(new_styles.primary_style().get_ui()) }, ) { tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES); } if old_values.as_deref().map_or_else( || { new_styles .primary_style() .get_ui() .specifies_view_timelines() }, |old| { !old.get_ui() .view_timelines_equals(new_styles.primary_style().get_ui()) }, ) { tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES); } if self.needs_animations_update( context, old_values.as_deref(), new_styles.primary_style(), /* pseudo_element = */ None, ) { tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); } let before_change_style = self.process_transitions(context, old_values.as_ref(), new_styles); if before_change_style.is_some() { tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); } if self.has_animations(&context.shared) { tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES); if important_rules_changed { tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS); } if new_styles .primary_style() .is_display_property_changed_from_none(old_values.as_deref()) { tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE); } } if !tasks.is_empty() { let task = crate::context::SequentialTask::update_animations( *self, before_change_style, tasks, ); context.thread_local.tasks.push(task); } } #[cfg(feature = "servo")] fn process_animations( &self, context: &mut StyleContext, old_styles: &mut ElementStyles, new_resolved_styles: &mut ResolvedElementStyles, _restyle_hint: RestyleHint, _important_rules_changed: bool, ) { use crate::animation::AnimationSetKey; use crate::dom::TDocument; let style_changed = self.process_animations_for_style( context, &mut old_styles.primary, new_resolved_styles.primary_style_mut(), /* pseudo_element = */ None, ); // If we have modified animation or transitions, we recascade style for this node. if style_changed { let primary_style = new_resolved_styles.primary_style(); let mut rule_node = primary_style.rules().clone(); let declarations = context.shared.animations.get_all_declarations( &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()), context.shared.current_time_for_animations, self.as_node().owner_doc().shared_lock(), ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); if rule_node != *primary_style.rules() { let inputs = CascadeInputs { rules: Some(rule_node), visited_rules: primary_style.visited_rules().cloned(), flags: primary_style.flags.for_cascade_inputs(), }; new_resolved_styles.primary.style = StyleResolverForElement::new( *self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable, ) .cascade_style_and_visited_with_default_parents(inputs); } } self.process_animations_for_pseudo( context, old_styles, new_resolved_styles, PseudoElement::Before, ); self.process_animations_for_pseudo( context, old_styles, new_resolved_styles, PseudoElement::After, ); } #[cfg(feature = "servo")] fn process_animations_for_pseudo( &self, context: &mut StyleContext, old_styles: &mut ElementStyles, new_resolved_styles: &mut ResolvedElementStyles, pseudo_element: PseudoElement, ) { use crate::animation::AnimationSetKey; use crate::dom::TDocument; let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone()); let mut style = match new_resolved_styles.pseudos.get(&pseudo_element) { Some(style) => Arc::clone(style), None => { context .shared .animations .cancel_all_animations_for_key(&key); return; }, }; let mut old_style = old_styles.pseudos.get(&pseudo_element).cloned(); self.process_animations_for_style( context, &mut old_style, &mut style, Some(pseudo_element.clone()), ); let declarations = context.shared.animations.get_all_declarations( &key, context.shared.current_time_for_animations, self.as_node().owner_doc().shared_lock(), ); if declarations.is_empty() { return; } let mut rule_node = style.rules().clone(); Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); if rule_node == *style.rules() { return; } let inputs = CascadeInputs { rules: Some(rule_node), visited_rules: style.visited_rules().cloned(), flags: style.flags.for_cascade_inputs(), }; let new_style = StyleResolverForElement::new( *self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable, ) .cascade_style_and_visited_for_pseudo_with_default_parents( inputs, &pseudo_element, &new_resolved_styles.primary, ); new_resolved_styles .pseudos .set(&pseudo_element, new_style.0); } #[cfg(feature = "servo")] fn process_animations_for_style( &self, context: &mut StyleContext, old_values: &mut Option>, new_values: &mut Arc, pseudo_element: Option, ) -> bool { use crate::animation::{AnimationSetKey, AnimationState}; // We need to call this before accessing the `ElementAnimationSet` from the // map because this call will do a RwLock::read(). let needs_animations_update = self.needs_animations_update( context, old_values.as_deref(), new_values, pseudo_element, ); let might_need_transitions_update = self.might_need_transitions_update( context, old_values.as_deref(), new_values, pseudo_element, ); let mut after_change_style = None; if might_need_transitions_update { after_change_style = self.after_change_style(context, new_values); } let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); let shared_context = context.shared; let mut animation_set = shared_context .animations .sets .write() .remove(&key) .unwrap_or_default(); // Starting animations is expensive, because we have to recalculate the style // for all the keyframes. We only want to do this if we think that there's a // chance that the animations really changed. if needs_animations_update { let mut resolver = StyleResolverForElement::new( *self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable, ); animation_set.update_animations_for_new_style::( *self, &shared_context, &new_values, &mut resolver, ); } animation_set.update_transitions_for_new_style( might_need_transitions_update, &shared_context, old_values.as_ref(), after_change_style.as_ref().unwrap_or(new_values), ); // We clear away any finished transitions, but retain animations, because they // might still be used for proper calculation of `animation-fill-mode`. This // should change the computed values in the style, so we don't need to mark // this set as dirty. animation_set .transitions .retain(|transition| transition.state != AnimationState::Finished); // If the ElementAnimationSet is empty, and don't store it in order to // save memory and to avoid extra processing later. let changed_animations = animation_set.dirty; if !animation_set.is_empty() { animation_set.dirty = false; shared_context .animations .sets .write() .insert(key, animation_set); } changed_animations } /// Computes and applies non-redundant damage. fn accumulate_damage_for( &self, shared_context: &SharedStyleContext, damage: &mut RestyleDamage, old_values: &ComputedValues, new_values: &ComputedValues, pseudo: Option<&PseudoElement>, ) -> ChildRestyleRequirement { debug!("accumulate_damage_for: {:?}", self); debug_assert!(!shared_context .traversal_flags .contains(TraversalFlags::FinalAnimationTraversal)); let difference = self.compute_style_difference(old_values, new_values, pseudo); *damage |= difference.damage; debug!(" > style difference: {:?}", difference); // We need to cascade the children in order to ensure the correct // propagation of inherited computed value flags. if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() { debug!( " > flags changed: {:?} != {:?}", old_values.flags, new_values.flags ); return ChildRestyleRequirement::MustCascadeChildren; } if old_values.effective_zoom != new_values.effective_zoom { // Zoom changes need to get propagated to children. debug!( " > zoom changed: {:?} != {:?}", old_values.effective_zoom, new_values.effective_zoom ); return ChildRestyleRequirement::MustCascadeChildren; } match difference.change { StyleChange::Unchanged => return ChildRestyleRequirement::CanSkipCascade, StyleChange::Changed { reset_only } => { // If inherited properties changed, the best we can do is // cascade the children. if !reset_only { return ChildRestyleRequirement::MustCascadeChildren; } }, } let old_display = old_values.clone_display(); let new_display = new_values.clone_display(); if old_display != new_display { // If we used to be a display: none element, and no longer are, our // children need to be restyled because they're unstyled. if old_display == Display::None { return ChildRestyleRequirement::MustCascadeChildren; } // Blockification of children may depend on our display value, // so we need to actually do the recascade. We could potentially // do better, but it doesn't seem worth it. if old_display.is_item_container() != new_display.is_item_container() { return ChildRestyleRequirement::MustCascadeChildren; } // We may also need to blockify and un-blockify descendants if our // display goes from / to display: contents, since the "layout // parent style" changes. if old_display.is_contents() || new_display.is_contents() { return ChildRestyleRequirement::MustCascadeChildren; } // Line break suppression may also be affected if the display // type changes from ruby to non-ruby. #[cfg(feature = "gecko")] { if old_display.is_ruby_type() != new_display.is_ruby_type() { return ChildRestyleRequirement::MustCascadeChildren; } } } // Children with justify-items: auto may depend on our // justify-items property value. // // Similarly, we could potentially do better, but this really // seems not common enough to care about. #[cfg(feature = "gecko")] { use crate::values::specified::align::AlignFlags; let old_justify_items = old_values.get_position().clone_justify_items(); let new_justify_items = new_values.get_position().clone_justify_items(); let was_legacy_justify_items = old_justify_items.computed.0.contains(AlignFlags::LEGACY); let is_legacy_justify_items = new_justify_items.computed.0.contains(AlignFlags::LEGACY); if is_legacy_justify_items != was_legacy_justify_items { return ChildRestyleRequirement::MustCascadeChildren; } if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed { return ChildRestyleRequirement::MustCascadeChildren; } } #[cfg(feature = "servo")] { // We may need to set or propagate the CAN_BE_FRAGMENTED bit // on our children. if old_values.is_multicol() != new_values.is_multicol() { return ChildRestyleRequirement::MustCascadeChildren; } } // We could prove that, if our children don't inherit reset // properties, we can stop the cascade. ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle } } impl PrivateMatchMethods for E {} /// The public API that elements expose for selector matching. pub trait MatchMethods: TElement { /// Returns the closest parent element that doesn't have a display: contents /// style (and thus generates a box). /// /// This is needed to correctly handle blockification of flex and grid /// items. /// /// Returns itself if the element has no parent. In practice this doesn't /// happen because the root element is blockified per spec, but it could /// happen if we decide to not blockify for roots of disconnected subtrees, /// which is a kind of dubious behavior. fn layout_parent(&self) -> Self { let mut current = self.clone(); loop { current = match current.traversal_parent() { Some(el) => el, None => return current, }; let is_display_contents = current .borrow_data() .unwrap() .styles .primary() .is_display_contents(); if !is_display_contents { return current; } } } /// Updates the styles with the new ones, diffs them, and stores the restyle /// damage. fn finish_restyle( &self, context: &mut StyleContext, data: &mut ElementData, mut new_styles: ResolvedElementStyles, important_rules_changed: bool, ) -> ChildRestyleRequirement { use std::cmp; self.process_animations( context, &mut data.styles, &mut new_styles, data.hint, important_rules_changed, ); // First of all, update the styles. let old_styles = data.set_styles(new_styles); let new_primary_style = data.styles.primary.as_ref().unwrap(); let mut restyle_requirement = ChildRestyleRequirement::CanSkipCascade; let is_root = new_primary_style .flags .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE); let is_container = !new_primary_style .get_box() .clone_container_type() .is_normal(); if is_root || is_container { let device = context.shared.stylist.device(); let old_style = old_styles.primary.as_ref(); let new_font_size = new_primary_style.get_font().clone_font_size(); let old_font_size = old_style.map(|s| s.get_font().clone_font_size()); if old_font_size != Some(new_font_size) { if is_root { debug_assert!(self.owner_doc_matches_for_testing(device)); let size = new_font_size.computed_size(); device.set_root_font_size(new_primary_style.effective_zoom.unzoom(size.px())); if device.used_root_font_size() { // If the root font-size changed since last time, and something // in the document did use rem units, ensure we recascade the // entire tree. restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; } } if is_container && old_font_size.is_some() { // TODO(emilio): Maybe only do this if we were matched // against relative font sizes? // Also, maybe we should do this as well for font-family / // etc changes (for ex/ch/ic units to work correctly)? We // should probably do the optimization mentioned above if // so. restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; } } // For line-height, we want the fully resolved value, as `normal` also depends on other // font properties. let new_line_height = device .calc_line_height( &new_primary_style.get_font(), new_primary_style.writing_mode, None, ) .0; let old_line_height = old_style.map(|s| { device .calc_line_height(&s.get_font(), s.writing_mode, None) .0 }); if old_line_height != Some(new_line_height) { if is_root { debug_assert!(self.owner_doc_matches_for_testing(device)); device.set_root_line_height( new_primary_style .effective_zoom .unzoom(new_line_height.px()), ); if device.used_root_line_height() { restyle_requirement = std::cmp::max( restyle_requirement, ChildRestyleRequirement::MustCascadeDescendants, ); } } if is_container && old_line_height.is_some() { restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; } } } if context.shared.stylist.quirks_mode() == QuirksMode::Quirks { if self.is_html_document_body_element() { // NOTE(emilio): We _could_ handle dynamic changes to it if it // changes and before we reach our children the cascade stops, // but we don't track right now whether we use the document body // color, and nobody else handles that properly anyway. let device = context.shared.stylist.device(); // Needed for the "inherit from body" quirk. let text_color = new_primary_style.get_inherited_text().clone_color(); device.set_body_text_color(text_color); } } // Don't accumulate damage if we're in the final animation traversal. if context .shared .traversal_flags .contains(TraversalFlags::FinalAnimationTraversal) { return ChildRestyleRequirement::MustCascadeChildren; } // Also, don't do anything if there was no style. let old_primary_style = match old_styles.primary { Some(s) => s, None => return ChildRestyleRequirement::MustCascadeChildren, }; let old_container_type = old_primary_style.clone_container_type(); let new_container_type = new_primary_style.clone_container_type(); if old_container_type != new_container_type && !new_container_type.is_size_container_type() { // Stopped being a size container. Re-evaluate container queries and units on all our descendants. // Changes into and between different size containment is handled in `UpdateContainerQueryStyles`. restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; } else if old_container_type.is_size_container_type() && !old_primary_style.is_display_contents() && new_primary_style.is_display_contents() { // Also re-evaluate when a container gets 'display: contents', since size queries will now evaluate to unknown. // Other displays like 'inline' will keep generating a box, so they are handled in `UpdateContainerQueryStyles`. restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; } restyle_requirement = cmp::max( restyle_requirement, self.accumulate_damage_for( context.shared, &mut data.damage, &old_primary_style, new_primary_style, None, ), ); if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() { // This is the common case; no need to examine pseudos here. return restyle_requirement; } let pseudo_styles = old_styles .pseudos .as_array() .iter() .zip(data.styles.pseudos.as_array().iter()); for (i, (old, new)) in pseudo_styles.enumerate() { match (old, new) { (&Some(ref old), &Some(ref new)) => { self.accumulate_damage_for( context.shared, &mut data.damage, old, new, Some(&PseudoElement::from_eager_index(i)), ); }, (&None, &None) => {}, _ => { // It's possible that we're switching from not having // ::before/::after at all to having styles for them but not // actually having a useful pseudo-element. Check for that // case. let pseudo = PseudoElement::from_eager_index(i); let new_pseudo_should_exist = new.as_ref().map_or(false, |s| pseudo.should_exist(s)); let old_pseudo_should_exist = old.as_ref().map_or(false, |s| pseudo.should_exist(s)); if new_pseudo_should_exist != old_pseudo_should_exist { data.damage |= RestyleDamage::reconstruct(); return restyle_requirement; } }, } } restyle_requirement } /// Updates the rule nodes without re-running selector matching, using just /// the rule tree. /// /// Returns true if an !important rule was replaced. fn replace_rules( &self, replacements: RestyleHint, context: &mut StyleContext, cascade_inputs: &mut ElementCascadeInputs, ) -> bool { let mut result = false; result |= self.replace_rules_internal( replacements, context, CascadeVisitedMode::Unvisited, cascade_inputs, ); result |= self.replace_rules_internal( replacements, context, CascadeVisitedMode::Visited, cascade_inputs, ); result } /// Given the old and new style of this element, and whether it's a /// pseudo-element, compute the restyle damage used to determine which /// kind of layout or painting operations we'll need. fn compute_style_difference( &self, old_values: &ComputedValues, new_values: &ComputedValues, pseudo: Option<&PseudoElement>, ) -> StyleDifference { debug_assert!(pseudo.map_or(true, |p| p.is_eager())); RestyleDamage::compute_style_difference(old_values, new_values) } } impl MatchMethods for E {}