aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/matching.rs
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2024-02-27 23:39:06 +0800
committerGitHub <noreply@github.com>2024-02-27 15:39:06 +0000
commitfaf754dfa655f0b9a28f62bc47a78fbf78ebcaf4 (patch)
tree4725e1446680d036797b1fc258733ae6b2c9f354 /components/style/matching.rs
parentb07505417e629bbb081be9683630f2d7a5f50544 (diff)
downloadservo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.tar.gz
servo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.zip
Move Stylo to its own repo (#31350)
* Remove packages that were moved to external repo * Add workspace dependencies pointing to 2023-06-14 branch * Fix servo-tidy.toml errors * Update commit to include #31346 * Update commit to include servo/stylo#2 * Move css-properties.json lookup to target/doc/stylo * Remove dependency on vendored mako in favour of pypi dependency This also removes etc/ci/generate_workflow.py, which has been unused since at least 9e71bd6a7010d6e5723831696ae0ebe26b47682f. * Add temporary code to debug Windows test failures * Fix failures on Windows due to custom target dir * Update commit to include servo/stylo#3 * Fix license in tests/unit/style/build.rs * Document how to build with local Stylo in Cargo.toml
Diffstat (limited to 'components/style/matching.rs')
-rw-r--r--components/style/matching.rs1096
1 files changed, 0 insertions, 1096 deletions
diff --git a/components/style/matching.rs b/components/style/matching.rs
deleted file mode 100644
index bc9c797a5d2..00000000000
--- a/components/style/matching.rs
+++ /dev/null
@@ -1,1096 +0,0 @@
-/* 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;
-use crate::context::{CascadeInputs, 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::ResolvedElementStyles;
-use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
-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<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
- 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<Self>,
- 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::root(),
- style_attribute,
- primary_rules,
- );
- result |= Self::replace_single_rule_node(
- context.shared,
- CascadeLevel::same_tree_author_important(),
- LayerOrder::root(),
- 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<Self>,
- primary_style: &Arc<ComputedValues>,
- ) -> Option<Arc<ComputedValues>> {
- let rule_node = primary_style.rules();
- let without_transition_rules = context
- .shared
- .stylist
- .rule_tree()
- .remove_transition_rule_if_applicable(rule_node);
- if without_transition_rules == *rule_node {
- // We don't have transition rule in this case, so return None to let
- // the caller use the original ComputedValues.
- return None;
- }
-
- // FIXME(bug 868975): We probably need to transition visited style as
- // well.
- let inputs = CascadeInputs {
- rules: Some(without_transition_rules),
- visited_rules: primary_style.visited_rules().cloned(),
- flags: primary_style.flags.for_cascade_inputs(),
- };
-
- // Actually `PseudoElementResolution` doesn't really matter.
- let style = StyleResolverForElement::new(
- *self,
- context,
- RuleInclusion::All,
- PseudoElementResolution::IfApplicable,
- )
- .cascade_style_and_visited_with_default_parents(inputs);
-
- Some(style.0)
- }
-
- fn needs_animations_update(
- &self,
- context: &mut StyleContext<Self>,
- old_style: Option<&ComputedValues>,
- new_style: &ComputedValues,
- pseudo_element: Option<PseudoElement>,
- ) -> 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<Self>,
- old_style: Option<&ComputedValues>,
- new_style: &ComputedValues,
- pseudo_element: Option<PseudoElement>,
- ) -> 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<Self>,
- 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 process_animations(
- &self,
- context: &mut StyleContext<Self>,
- old_styles: &mut ElementStyles,
- new_styles: &mut ResolvedElementStyles,
- restyle_hint: RestyleHint,
- important_rules_changed: bool,
- ) {
- use crate::context::UpdateAnimationsTasks;
-
- let new_values = new_styles.primary_style_mut();
- 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_values,
- 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_values.get_ui().specifies_scroll_timelines(),
- |old| !old.get_ui().scroll_timelines_equals(new_values.get_ui()),
- ) {
- tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES);
- }
-
- if old_values.as_deref().map_or_else(
- || new_values.get_ui().specifies_view_timelines(),
- |old| !old.get_ui().view_timelines_equals(new_values.get_ui()),
- ) {
- tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES);
- }
-
- if self.needs_animations_update(
- context,
- old_values.as_deref(),
- new_values,
- /* pseudo_element = */ None,
- ) {
- tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS);
- }
-
- let before_change_style = if self.might_need_transitions_update(
- context,
- old_values.as_deref(),
- new_values,
- /* pseudo_element = */ 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.
- let needs_transitions_update = {
- // We borrow new_values here, so need to add a scope to make
- // sure we release it before assigning a new value to it.
- let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values);
-
- self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref)
- };
-
- if needs_transitions_update {
- if let Some(values_without_transitions) = after_change_style {
- *new_values = values_without_transitions;
- }
- tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS);
-
- // We need to clone old_values into SequentialTask, so we can
- // use it later.
- old_values.clone()
- } else {
- None
- }
- } else {
- None
- };
-
- if self.has_animations(&context.shared) {
- tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES);
- if important_rules_changed {
- tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS);
- }
- if new_values.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<Self>,
- 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<Self>,
- 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<Self>,
- old_values: &mut Option<Arc<ComputedValues>>,
- new_values: &mut Arc<ComputedValues>,
- pseudo_element: Option<PseudoElement>,
- ) -> 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>(
- *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;
- }
-
- 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<E: TElement> 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<Self>,
- 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 new_font_size = new_primary_style.get_font().clone_font_size();
- let old_font_size = old_styles
- .primary
- .as_ref()
- .map(|s| s.get_font().clone_font_size());
-
- if old_font_size != Some(new_font_size) {
- if is_root {
- let device = context.shared.stylist.device();
- debug_assert!(self.owner_doc_matches_for_testing(device));
- device.set_root_font_size(new_font_size.computed_size().into());
- 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;
- }
- }
- }
-
- 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<Self>,
- 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<E: TElement> MatchMethods for E {}