/* 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<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::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<Self>,
        primary_style: &Arc<ComputedValues>,
    ) -> Option<Arc<ComputedValues>> {
        // 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<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;
    }

    #[cfg(feature = "gecko")]
    fn maybe_resolve_starting_style(
        &self,
        context: &mut StyleContext<Self>,
        old_values: Option<&Arc<ComputedValues>>,
        new_styles: &ResolvedElementStyles,
    ) -> Option<Arc<ComputedValues>> {
        // 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<Self>,
        old_values: Option<&Arc<ComputedValues>>,
        new_styles: &mut ResolvedElementStyles,
    ) -> Option<Arc<ComputedValues>> {
        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<Self>,
        old_styles: &mut ElementStyles,
        new_styles: &mut ResolvedElementStyles,
        important_rules_changed: bool,
    ) {
        use crate::context::UpdateAnimationsTasks;

        let old_values = &old_styles.primary;
        if context.shared.traversal_flags.for_animation_only() && old_values.is_some() {
            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<Self>,
        old_styles: &mut ElementStyles,
        new_resolved_styles: &mut ResolvedElementStyles,
        _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: &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 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 old_style = old_styles.pseudos.get(&pseudo_element).cloned();
        self.process_animations_for_style(
            context,
            &old_style,
            &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: &Option<Arc<ComputedValues>>,
        new_values: &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),
        );

        // 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);

        animation_set
            .animations
            .retain(|animation| animation.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.contains(AlignFlags::LEGACY);

            let is_legacy_justify_items = new_justify_items.computed.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,
            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());

            // 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
            });

            // Update root font-relative units. If any of these unit values changed
            // since last time, ensure that we recascade the entire tree.
            if is_root {
                debug_assert!(self.owner_doc_matches_for_testing(device));
                device.set_root_style(new_primary_style);

                // Update root font size for rem units
                if old_font_size != Some(new_font_size) {
                    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() {
                        restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants;
                    }
                }

                // Update root line height for rlh units
                if old_line_height != Some(new_line_height) {
                    device.set_root_line_height(
                        new_primary_style
                            .effective_zoom
                            .unzoom(new_line_height.px()),
                    );
                    if device.used_root_line_height() {
                        restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants;
                    }
                }

                // Update root font metrics for rcap, rch, rex, ric units. Since querying
                // font metrics can be an expensive call, they are only updated if these
                // units are used in the document.
                if device.used_root_font_metrics() && device.update_root_font_metrics() {
                    restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants;
                }
            }

            if is_container
                && (old_font_size.is_some_and(|old| old != new_font_size)
                    || old_line_height.is_some_and(|old| old != new_line_height))
            {
                // 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()));
        #[cfg(feature = "gecko")]
        {
            RestyleDamage::compute_style_difference(old_values, new_values)
        }
        #[cfg(feature = "servo")]
        {
            RestyleDamage::compute_style_difference::<Self>(old_values, new_values)
        }
    }
}

impl<E: TElement> MatchMethods for E {}
