style/
matching.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! High-level interface to CSS selector matching.
6
7#![allow(unsafe_code)]
8#![deny(missing_docs)]
9
10use crate::computed_value_flags::ComputedValueFlags;
11#[cfg(feature = "servo")]
12use crate::context::CascadeInputs;
13use crate::context::{ElementCascadeInputs, QuirksMode};
14use crate::context::{SharedStyleContext, StyleContext};
15use crate::data::{ElementData, ElementStyles};
16use crate::dom::TElement;
17#[cfg(feature = "servo")]
18use crate::dom::TNode;
19use crate::invalidation::element::restyle_hints::RestyleHint;
20use crate::properties::longhands::display::computed_value::T as Display;
21use crate::properties::ComputedValues;
22use crate::properties::PropertyDeclarationBlock;
23use crate::rule_tree::{CascadeLevel, StrongRuleNode};
24use crate::selector_parser::{PseudoElement, RestyleDamage};
25use crate::shared_lock::Locked;
26use crate::style_resolver::StyleResolverForElement;
27use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles};
28use crate::stylesheets::layer_rule::LayerOrder;
29use crate::stylist::RuleInclusion;
30use crate::traversal_flags::TraversalFlags;
31use servo_arc::{Arc, ArcBorrow};
32
33/// Represents the result of comparing an element's old and new style.
34#[derive(Debug)]
35pub struct StyleDifference {
36    /// The resulting damage.
37    pub damage: RestyleDamage,
38
39    /// Whether any styles changed.
40    pub change: StyleChange,
41}
42
43/// Represents whether or not the style of an element has changed.
44#[derive(Clone, Copy, Debug)]
45pub enum StyleChange {
46    /// The style hasn't changed.
47    Unchanged,
48    /// The style has changed.
49    Changed {
50        /// Whether only reset structs changed.
51        reset_only: bool,
52    },
53}
54
55/// Whether or not newly computed values for an element need to be cascaded to
56/// children (or children might need to be re-matched, e.g., for container
57/// queries).
58#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
59pub enum ChildRestyleRequirement {
60    /// Old and new computed values were the same, or we otherwise know that
61    /// we won't bother recomputing style for children, so we can skip cascading
62    /// the new values into child elements.
63    CanSkipCascade = 0,
64    /// The same as `MustCascadeChildren`, but we only need to actually
65    /// recascade if the child inherits any explicit reset style.
66    MustCascadeChildrenIfInheritResetStyle = 1,
67    /// Old and new computed values were different, so we must cascade the
68    /// new values to children.
69    MustCascadeChildren = 2,
70    /// The same as `MustCascadeChildren`, but for the entire subtree.  This is
71    /// used to handle root font-size updates needing to recascade the whole
72    /// document.
73    MustCascadeDescendants = 3,
74    /// We need to re-match the whole subttree. This is used to handle container
75    /// query relative unit changes for example. Container query size changes
76    /// also trigger re-match, but after layout.
77    MustMatchDescendants = 4,
78}
79
80/// Determines which styles are being cascaded currently.
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82enum CascadeVisitedMode {
83    /// Cascade the regular, unvisited styles.
84    Unvisited,
85    /// Cascade the styles used when an element's relevant link is visited.  A
86    /// "relevant link" is the element being matched if it is a link or the
87    /// nearest ancestor link.
88    Visited,
89}
90
91trait PrivateMatchMethods: TElement {
92    fn replace_single_rule_node(
93        context: &SharedStyleContext,
94        level: CascadeLevel,
95        layer_order: LayerOrder,
96        pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
97        path: &mut StrongRuleNode,
98    ) -> bool {
99        let stylist = &context.stylist;
100        let guards = &context.guards;
101
102        let mut important_rules_changed = false;
103        let new_node = stylist.rule_tree().update_rule_at_level(
104            level,
105            layer_order,
106            pdb,
107            path,
108            guards,
109            &mut important_rules_changed,
110        );
111        if let Some(n) = new_node {
112            *path = n;
113        }
114        important_rules_changed
115    }
116
117    /// Updates the rule nodes without re-running selector matching, using just
118    /// the rule tree, for a specific visited mode.
119    ///
120    /// Returns true if an !important rule was replaced.
121    fn replace_rules_internal(
122        &self,
123        replacements: RestyleHint,
124        context: &mut StyleContext<Self>,
125        cascade_visited: CascadeVisitedMode,
126        cascade_inputs: &mut ElementCascadeInputs,
127    ) -> bool {
128        debug_assert!(
129            replacements.intersects(RestyleHint::replacements())
130                && (replacements & !RestyleHint::replacements()).is_empty()
131        );
132
133        let primary_rules = match cascade_visited {
134            CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(),
135            CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(),
136        };
137
138        let primary_rules = match primary_rules {
139            Some(r) => r,
140            None => return false,
141        };
142
143        if !context.shared.traversal_flags.for_animation_only() {
144            let mut result = false;
145            if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) {
146                let style_attribute = self.style_attribute();
147                result |= Self::replace_single_rule_node(
148                    context.shared,
149                    CascadeLevel::same_tree_author_normal(),
150                    LayerOrder::style_attribute(),
151                    style_attribute,
152                    primary_rules,
153                );
154                result |= Self::replace_single_rule_node(
155                    context.shared,
156                    CascadeLevel::same_tree_author_important(),
157                    LayerOrder::style_attribute(),
158                    style_attribute,
159                    primary_rules,
160                );
161                // FIXME(emilio): Still a hack!
162                self.unset_dirty_style_attribute();
163            }
164            return result;
165        }
166
167        // Animation restyle hints are processed prior to other restyle
168        // hints in the animation-only traversal.
169        //
170        // Non-animation restyle hints will be processed in a subsequent
171        // normal traversal.
172        if replacements.intersects(RestyleHint::for_animations()) {
173            debug_assert!(context.shared.traversal_flags.for_animation_only());
174
175            if replacements.contains(RestyleHint::RESTYLE_SMIL) {
176                Self::replace_single_rule_node(
177                    context.shared,
178                    CascadeLevel::SMILOverride,
179                    LayerOrder::root(),
180                    self.smil_override(),
181                    primary_rules,
182                );
183            }
184
185            if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) {
186                Self::replace_single_rule_node(
187                    context.shared,
188                    CascadeLevel::Transitions,
189                    LayerOrder::root(),
190                    self.transition_rule(&context.shared)
191                        .as_ref()
192                        .map(|a| a.borrow_arc()),
193                    primary_rules,
194                );
195            }
196
197            if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) {
198                Self::replace_single_rule_node(
199                    context.shared,
200                    CascadeLevel::Animations,
201                    LayerOrder::root(),
202                    self.animation_rule(&context.shared)
203                        .as_ref()
204                        .map(|a| a.borrow_arc()),
205                    primary_rules,
206                );
207            }
208        }
209
210        false
211    }
212
213    /// If there is no transition rule in the ComputedValues, it returns None.
214    fn after_change_style(
215        &self,
216        context: &mut StyleContext<Self>,
217        primary_style: &Arc<ComputedValues>,
218    ) -> Option<Arc<ComputedValues>> {
219        // Actually `PseudoElementResolution` doesn't really matter.
220        StyleResolverForElement::new(
221            *self,
222            context,
223            RuleInclusion::All,
224            PseudoElementResolution::IfApplicable,
225        )
226        .after_change_style(primary_style)
227    }
228
229    fn needs_animations_update(
230        &self,
231        context: &mut StyleContext<Self>,
232        old_style: Option<&ComputedValues>,
233        new_style: &ComputedValues,
234        pseudo_element: Option<PseudoElement>,
235    ) -> bool {
236        let new_ui_style = new_style.get_ui();
237        let new_style_specifies_animations = new_ui_style.specifies_animations();
238
239        let has_animations = self.has_css_animations(&context.shared, pseudo_element);
240        if !new_style_specifies_animations && !has_animations {
241            return false;
242        }
243
244        let old_style = match old_style {
245            Some(old) => old,
246            // If we have no old style but have animations, we may be a
247            // pseudo-element which was re-created without style changes.
248            //
249            // This can happen when we reframe the pseudo-element without
250            // restyling it (due to content insertion on a flex container or
251            // such, for example). See bug 1564366.
252            //
253            // FIXME(emilio): The really right fix for this is keeping the
254            // pseudo-element itself around on reframes, but that's a bit
255            // harder. If we do that we can probably remove quite a lot of the
256            // EffectSet complexity though, since right now it's stored on the
257            // parent element for pseudo-elements given we need to keep it
258            // around...
259            None => {
260                return new_style_specifies_animations || new_style.is_pseudo_style();
261            },
262        };
263
264        let old_ui_style = old_style.get_ui();
265
266        let keyframes_could_have_changed = context
267            .shared
268            .traversal_flags
269            .contains(TraversalFlags::ForCSSRuleChanges);
270
271        // If the traversal is triggered due to changes in CSS rules changes, we
272        // need to try to update all CSS animations on the element if the
273        // element has or will have CSS animation style regardless of whether
274        // the animation is running or not.
275        //
276        // TODO: We should check which @keyframes were added/changed/deleted and
277        // update only animations corresponding to those @keyframes.
278        if keyframes_could_have_changed {
279            return true;
280        }
281
282        // If the animations changed, well...
283        if !old_ui_style.animations_equals(new_ui_style) {
284            return true;
285        }
286
287        let old_display = old_style.clone_display();
288        let new_display = new_style.clone_display();
289
290        // If we were display: none, we may need to trigger animations.
291        if old_display == Display::None && new_display != Display::None {
292            return new_style_specifies_animations;
293        }
294
295        // If we are becoming display: none, we may need to stop animations.
296        if old_display != Display::None && new_display == Display::None {
297            return has_animations;
298        }
299
300        // We might need to update animations if writing-mode or direction
301        // changed, and any of the animations contained logical properties.
302        //
303        // We may want to be more granular, but it's probably not worth it.
304        if new_style.writing_mode != old_style.writing_mode {
305            return has_animations;
306        }
307
308        false
309    }
310
311    fn might_need_transitions_update(
312        &self,
313        context: &StyleContext<Self>,
314        old_style: Option<&ComputedValues>,
315        new_style: &ComputedValues,
316        pseudo_element: Option<PseudoElement>,
317    ) -> bool {
318        let old_style = match old_style {
319            Some(v) => v,
320            None => return false,
321        };
322
323        if !self.has_css_transitions(context.shared, pseudo_element)
324            && !new_style.get_ui().specifies_transitions()
325        {
326            return false;
327        }
328
329        if old_style.clone_display().is_none() {
330            return false;
331        }
332
333        return true;
334    }
335
336    #[cfg(feature = "gecko")]
337    fn maybe_resolve_starting_style(
338        &self,
339        context: &mut StyleContext<Self>,
340        old_values: Option<&Arc<ComputedValues>>,
341        new_styles: &ResolvedElementStyles,
342    ) -> Option<Arc<ComputedValues>> {
343        // For both cases:
344        // 1. If we didn't see any starting-style rules for this given element during full matching.
345        // 2. If there is no transitions specified.
346        // We don't have to resolve starting style.
347        if !new_styles.may_have_starting_style()
348            || !new_styles.primary_style().get_ui().specifies_transitions()
349        {
350            return None;
351        }
352
353        // We resolve starting style only if we don't have before-change-style, or we change from
354        // display:none.
355        if old_values.is_some()
356            && !new_styles
357                .primary_style()
358                .is_display_property_changed_from_none(old_values.map(|s| &**s))
359        {
360            return None;
361        }
362
363        // Note: Basically, we have to remove transition rules because the starting style for an
364        // element is the after-change style with @starting-style rules applied in addition.
365        // However, we expect there is no transition rules for this element when calling this
366        // function because we do this only when we don't have before-change style or we change
367        // from display:none. In these cases, it's unlikely to have running transitions on this
368        // element.
369        let mut resolver = StyleResolverForElement::new(
370            *self,
371            context,
372            RuleInclusion::All,
373            PseudoElementResolution::IfApplicable,
374        );
375
376        let starting_style = resolver.resolve_starting_style().style;
377        if starting_style.style().clone_display().is_none() {
378            return None;
379        }
380
381        Some(starting_style.0)
382    }
383
384    /// Handle CSS Transitions. Returns None if we don't need to update transitions. And it returns
385    /// the before-change style per CSS Transitions spec.
386    ///
387    /// Note: The before-change style could be the computed values of all properties on the element
388    /// as of the previous style change event, or the starting style if we don't have the valid
389    /// before-change style there.
390    #[cfg(feature = "gecko")]
391    fn process_transitions(
392        &self,
393        context: &mut StyleContext<Self>,
394        old_values: Option<&Arc<ComputedValues>>,
395        new_styles: &mut ResolvedElementStyles,
396    ) -> Option<Arc<ComputedValues>> {
397        let starting_values = self.maybe_resolve_starting_style(context, old_values, new_styles);
398        let before_change_or_starting = if starting_values.is_some() {
399            starting_values.as_ref()
400        } else {
401            old_values
402        };
403        let new_values = new_styles.primary_style_mut();
404
405        if !self.might_need_transitions_update(
406            context,
407            before_change_or_starting.map(|s| &**s),
408            new_values,
409            /* pseudo_element = */ None,
410        ) {
411            return None;
412        }
413
414        let after_change_style =
415            if self.has_css_transitions(context.shared, /* pseudo_element = */ None) {
416                self.after_change_style(context, new_values)
417            } else {
418                None
419            };
420
421        // In order to avoid creating a SequentialTask for transitions which
422        // may not be updated, we check it per property to make sure Gecko
423        // side will really update transition.
424        if !self.needs_transitions_update(
425            before_change_or_starting.unwrap(),
426            after_change_style.as_ref().unwrap_or(&new_values),
427        ) {
428            return None;
429        }
430
431        if let Some(values_without_transitions) = after_change_style {
432            *new_values = values_without_transitions;
433        }
434
435        // Move the new-created starting style, or clone the old values.
436        if starting_values.is_some() {
437            starting_values
438        } else {
439            old_values.cloned()
440        }
441    }
442
443    #[cfg(feature = "gecko")]
444    fn process_animations(
445        &self,
446        context: &mut StyleContext<Self>,
447        old_styles: &mut ElementStyles,
448        new_styles: &mut ResolvedElementStyles,
449        important_rules_changed: bool,
450    ) {
451        use crate::context::UpdateAnimationsTasks;
452
453        let old_values = &old_styles.primary;
454        if context.shared.traversal_flags.for_animation_only() && old_values.is_some() {
455            return;
456        }
457
458        // Bug 868975: These steps should examine and update the visited styles
459        // in addition to the unvisited styles.
460
461        let mut tasks = UpdateAnimationsTasks::empty();
462
463        if old_values.as_deref().map_or_else(
464            || {
465                new_styles
466                    .primary_style()
467                    .get_ui()
468                    .specifies_scroll_timelines()
469            },
470            |old| {
471                !old.get_ui()
472                    .scroll_timelines_equals(new_styles.primary_style().get_ui())
473            },
474        ) {
475            tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES);
476        }
477
478        if old_values.as_deref().map_or_else(
479            || {
480                new_styles
481                    .primary_style()
482                    .get_ui()
483                    .specifies_view_timelines()
484            },
485            |old| {
486                !old.get_ui()
487                    .view_timelines_equals(new_styles.primary_style().get_ui())
488            },
489        ) {
490            tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES);
491        }
492
493        if self.needs_animations_update(
494            context,
495            old_values.as_deref(),
496            new_styles.primary_style(),
497            /* pseudo_element = */ None,
498        ) {
499            tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS);
500        }
501
502        let before_change_style =
503            self.process_transitions(context, old_values.as_ref(), new_styles);
504        if before_change_style.is_some() {
505            tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS);
506        }
507
508        if self.has_animations(&context.shared) {
509            tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES);
510            if important_rules_changed {
511                tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS);
512            }
513            if new_styles
514                .primary_style()
515                .is_display_property_changed_from_none(old_values.as_deref())
516            {
517                tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE);
518            }
519        }
520
521        if !tasks.is_empty() {
522            let task = crate::context::SequentialTask::update_animations(
523                *self,
524                before_change_style,
525                tasks,
526            );
527            context.thread_local.tasks.push(task);
528        }
529    }
530
531    #[cfg(feature = "servo")]
532    fn process_animations(
533        &self,
534        context: &mut StyleContext<Self>,
535        old_styles: &mut ElementStyles,
536        new_resolved_styles: &mut ResolvedElementStyles,
537        _important_rules_changed: bool,
538    ) {
539        use crate::animation::AnimationSetKey;
540        use crate::dom::TDocument;
541
542        let style_changed = self.process_animations_for_style(
543            context,
544            &mut old_styles.primary,
545            new_resolved_styles.primary_style_mut(),
546            /* pseudo_element = */ None,
547        );
548
549        // If we have modified animation or transitions, we recascade style for this node.
550        if style_changed {
551            let primary_style = new_resolved_styles.primary_style();
552            let mut rule_node = primary_style.rules().clone();
553            let declarations = context.shared.animations.get_all_declarations(
554                &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()),
555                context.shared.current_time_for_animations,
556                self.as_node().owner_doc().shared_lock(),
557            );
558            Self::replace_single_rule_node(
559                &context.shared,
560                CascadeLevel::Transitions,
561                LayerOrder::root(),
562                declarations.transitions.as_ref().map(|a| a.borrow_arc()),
563                &mut rule_node,
564            );
565            Self::replace_single_rule_node(
566                &context.shared,
567                CascadeLevel::Animations,
568                LayerOrder::root(),
569                declarations.animations.as_ref().map(|a| a.borrow_arc()),
570                &mut rule_node,
571            );
572
573            if rule_node != *primary_style.rules() {
574                let inputs = CascadeInputs {
575                    rules: Some(rule_node),
576                    visited_rules: primary_style.visited_rules().cloned(),
577                    flags: primary_style.flags.for_cascade_inputs(),
578                };
579
580                new_resolved_styles.primary.style = StyleResolverForElement::new(
581                    *self,
582                    context,
583                    RuleInclusion::All,
584                    PseudoElementResolution::IfApplicable,
585                )
586                .cascade_style_and_visited_with_default_parents(inputs);
587            }
588        }
589
590        self.process_animations_for_pseudo(
591            context,
592            old_styles,
593            new_resolved_styles,
594            PseudoElement::Before,
595        );
596        self.process_animations_for_pseudo(
597            context,
598            old_styles,
599            new_resolved_styles,
600            PseudoElement::After,
601        );
602    }
603
604    #[cfg(feature = "servo")]
605    fn process_animations_for_pseudo(
606        &self,
607        context: &mut StyleContext<Self>,
608        old_styles: &ElementStyles,
609        new_resolved_styles: &mut ResolvedElementStyles,
610        pseudo_element: PseudoElement,
611    ) {
612        use crate::animation::AnimationSetKey;
613        use crate::dom::TDocument;
614
615        let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone());
616        let style = match new_resolved_styles.pseudos.get(&pseudo_element) {
617            Some(style) => Arc::clone(style),
618            None => {
619                context
620                    .shared
621                    .animations
622                    .cancel_all_animations_for_key(&key);
623                return;
624            },
625        };
626
627        let old_style = old_styles.pseudos.get(&pseudo_element).cloned();
628        self.process_animations_for_style(
629            context,
630            &old_style,
631            &style,
632            Some(pseudo_element.clone()),
633        );
634
635        let declarations = context.shared.animations.get_all_declarations(
636            &key,
637            context.shared.current_time_for_animations,
638            self.as_node().owner_doc().shared_lock(),
639        );
640        if declarations.is_empty() {
641            return;
642        }
643
644        let mut rule_node = style.rules().clone();
645        Self::replace_single_rule_node(
646            &context.shared,
647            CascadeLevel::Transitions,
648            LayerOrder::root(),
649            declarations.transitions.as_ref().map(|a| a.borrow_arc()),
650            &mut rule_node,
651        );
652        Self::replace_single_rule_node(
653            &context.shared,
654            CascadeLevel::Animations,
655            LayerOrder::root(),
656            declarations.animations.as_ref().map(|a| a.borrow_arc()),
657            &mut rule_node,
658        );
659        if rule_node == *style.rules() {
660            return;
661        }
662
663        let inputs = CascadeInputs {
664            rules: Some(rule_node),
665            visited_rules: style.visited_rules().cloned(),
666            flags: style.flags.for_cascade_inputs(),
667        };
668
669        let new_style = StyleResolverForElement::new(
670            *self,
671            context,
672            RuleInclusion::All,
673            PseudoElementResolution::IfApplicable,
674        )
675        .cascade_style_and_visited_for_pseudo_with_default_parents(
676            inputs,
677            &pseudo_element,
678            &new_resolved_styles.primary,
679        );
680
681        new_resolved_styles
682            .pseudos
683            .set(&pseudo_element, new_style.0);
684    }
685
686    #[cfg(feature = "servo")]
687    fn process_animations_for_style(
688        &self,
689        context: &mut StyleContext<Self>,
690        old_values: &Option<Arc<ComputedValues>>,
691        new_values: &Arc<ComputedValues>,
692        pseudo_element: Option<PseudoElement>,
693    ) -> bool {
694        use crate::animation::{AnimationSetKey, AnimationState};
695
696        // We need to call this before accessing the `ElementAnimationSet` from the
697        // map because this call will do a RwLock::read().
698        let needs_animations_update = self.needs_animations_update(
699            context,
700            old_values.as_deref(),
701            new_values,
702            pseudo_element,
703        );
704
705        let might_need_transitions_update = self.might_need_transitions_update(
706            context,
707            old_values.as_deref(),
708            new_values,
709            pseudo_element,
710        );
711
712        let mut after_change_style = None;
713        if might_need_transitions_update {
714            after_change_style = self.after_change_style(context, new_values);
715        }
716
717        let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
718        let shared_context = context.shared;
719        let mut animation_set = shared_context
720            .animations
721            .sets
722            .write()
723            .remove(&key)
724            .unwrap_or_default();
725
726        // Starting animations is expensive, because we have to recalculate the style
727        // for all the keyframes. We only want to do this if we think that there's a
728        // chance that the animations really changed.
729        if needs_animations_update {
730            let mut resolver = StyleResolverForElement::new(
731                *self,
732                context,
733                RuleInclusion::All,
734                PseudoElementResolution::IfApplicable,
735            );
736
737            animation_set.update_animations_for_new_style::<Self>(
738                *self,
739                &shared_context,
740                &new_values,
741                &mut resolver,
742            );
743        }
744
745        animation_set.update_transitions_for_new_style(
746            might_need_transitions_update,
747            &shared_context,
748            old_values.as_ref(),
749            after_change_style.as_ref().unwrap_or(new_values),
750        );
751
752        // This should change the computed values in the style, so we don't need
753        // to mark this set as dirty.
754        animation_set
755            .transitions
756            .retain(|transition| transition.state != AnimationState::Finished);
757
758        animation_set
759            .animations
760            .retain(|animation| animation.state != AnimationState::Finished);
761
762        // If the ElementAnimationSet is empty, and don't store it in order to
763        // save memory and to avoid extra processing later.
764        let changed_animations = animation_set.dirty;
765        if !animation_set.is_empty() {
766            animation_set.dirty = false;
767            shared_context
768                .animations
769                .sets
770                .write()
771                .insert(key, animation_set);
772        }
773
774        changed_animations
775    }
776
777    /// Computes and applies non-redundant damage.
778    fn accumulate_damage_for(
779        &self,
780        shared_context: &SharedStyleContext,
781        damage: &mut RestyleDamage,
782        old_values: &ComputedValues,
783        new_values: &ComputedValues,
784        pseudo: Option<&PseudoElement>,
785    ) -> ChildRestyleRequirement {
786        debug!("accumulate_damage_for: {:?}", self);
787        debug_assert!(!shared_context
788            .traversal_flags
789            .contains(TraversalFlags::FinalAnimationTraversal));
790
791        let difference = self.compute_style_difference(old_values, new_values, pseudo);
792
793        *damage |= difference.damage;
794
795        debug!(" > style difference: {:?}", difference);
796
797        // We need to cascade the children in order to ensure the correct
798        // propagation of inherited computed value flags.
799        if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() {
800            debug!(
801                " > flags changed: {:?} != {:?}",
802                old_values.flags, new_values.flags
803            );
804            return ChildRestyleRequirement::MustCascadeChildren;
805        }
806
807        if old_values.effective_zoom != new_values.effective_zoom {
808            // Zoom changes need to get propagated to children.
809            debug!(
810                " > zoom changed: {:?} != {:?}",
811                old_values.effective_zoom, new_values.effective_zoom
812            );
813            return ChildRestyleRequirement::MustCascadeChildren;
814        }
815
816        match difference.change {
817            StyleChange::Unchanged => return ChildRestyleRequirement::CanSkipCascade,
818            StyleChange::Changed { reset_only } => {
819                // If inherited properties changed, the best we can do is
820                // cascade the children.
821                if !reset_only {
822                    return ChildRestyleRequirement::MustCascadeChildren;
823                }
824            },
825        }
826
827        let old_display = old_values.clone_display();
828        let new_display = new_values.clone_display();
829
830        if old_display != new_display {
831            // If we used to be a display: none element, and no longer are, our
832            // children need to be restyled because they're unstyled.
833            if old_display == Display::None {
834                return ChildRestyleRequirement::MustCascadeChildren;
835            }
836            // Blockification of children may depend on our display value,
837            // so we need to actually do the recascade. We could potentially
838            // do better, but it doesn't seem worth it.
839            if old_display.is_item_container() != new_display.is_item_container() {
840                return ChildRestyleRequirement::MustCascadeChildren;
841            }
842            // We may also need to blockify and un-blockify descendants if our
843            // display goes from / to display: contents, since the "layout
844            // parent style" changes.
845            if old_display.is_contents() || new_display.is_contents() {
846                return ChildRestyleRequirement::MustCascadeChildren;
847            }
848            // Line break suppression may also be affected if the display
849            // type changes from ruby to non-ruby.
850            #[cfg(feature = "gecko")]
851            {
852                if old_display.is_ruby_type() != new_display.is_ruby_type() {
853                    return ChildRestyleRequirement::MustCascadeChildren;
854                }
855            }
856        }
857
858        // Children with justify-items: auto may depend on our
859        // justify-items property value.
860        //
861        // Similarly, we could potentially do better, but this really
862        // seems not common enough to care about.
863        #[cfg(feature = "gecko")]
864        {
865            use crate::values::specified::align::AlignFlags;
866
867            let old_justify_items = old_values.get_position().clone_justify_items();
868            let new_justify_items = new_values.get_position().clone_justify_items();
869
870            let was_legacy_justify_items =
871                old_justify_items.computed.0.contains(AlignFlags::LEGACY);
872
873            let is_legacy_justify_items = new_justify_items.computed.0.contains(AlignFlags::LEGACY);
874
875            if is_legacy_justify_items != was_legacy_justify_items {
876                return ChildRestyleRequirement::MustCascadeChildren;
877            }
878
879            if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed
880            {
881                return ChildRestyleRequirement::MustCascadeChildren;
882            }
883        }
884
885        #[cfg(feature = "servo")]
886        {
887            // We may need to set or propagate the CAN_BE_FRAGMENTED bit
888            // on our children.
889            if old_values.is_multicol() != new_values.is_multicol() {
890                return ChildRestyleRequirement::MustCascadeChildren;
891            }
892        }
893
894        // We could prove that, if our children don't inherit reset
895        // properties, we can stop the cascade.
896        ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle
897    }
898}
899
900impl<E: TElement> PrivateMatchMethods for E {}
901
902/// The public API that elements expose for selector matching.
903pub trait MatchMethods: TElement {
904    /// Returns the closest parent element that doesn't have a display: contents
905    /// style (and thus generates a box).
906    ///
907    /// This is needed to correctly handle blockification of flex and grid
908    /// items.
909    ///
910    /// Returns itself if the element has no parent. In practice this doesn't
911    /// happen because the root element is blockified per spec, but it could
912    /// happen if we decide to not blockify for roots of disconnected subtrees,
913    /// which is a kind of dubious behavior.
914    fn layout_parent(&self) -> Self {
915        let mut current = self.clone();
916        loop {
917            current = match current.traversal_parent() {
918                Some(el) => el,
919                None => return current,
920            };
921
922            let is_display_contents = current
923                .borrow_data()
924                .unwrap()
925                .styles
926                .primary()
927                .is_display_contents();
928
929            if !is_display_contents {
930                return current;
931            }
932        }
933    }
934
935    /// Updates the styles with the new ones, diffs them, and stores the restyle
936    /// damage.
937    fn finish_restyle(
938        &self,
939        context: &mut StyleContext<Self>,
940        data: &mut ElementData,
941        mut new_styles: ResolvedElementStyles,
942        important_rules_changed: bool,
943    ) -> ChildRestyleRequirement {
944        use std::cmp;
945
946        self.process_animations(
947            context,
948            &mut data.styles,
949            &mut new_styles,
950            important_rules_changed,
951        );
952
953        // First of all, update the styles.
954        let old_styles = data.set_styles(new_styles);
955
956        let new_primary_style = data.styles.primary.as_ref().unwrap();
957
958        let mut restyle_requirement = ChildRestyleRequirement::CanSkipCascade;
959        let is_root = new_primary_style
960            .flags
961            .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE);
962        let is_container = !new_primary_style
963            .get_box()
964            .clone_container_type()
965            .is_normal();
966        if is_root || is_container {
967            let device = context.shared.stylist.device();
968            let old_style = old_styles.primary.as_ref();
969            let new_font_size = new_primary_style.get_font().clone_font_size();
970            let old_font_size = old_style.map(|s| s.get_font().clone_font_size());
971
972            if old_font_size != Some(new_font_size) {
973                if is_root {
974                    debug_assert!(self.owner_doc_matches_for_testing(device));
975                    let size = new_font_size.computed_size();
976                    device.set_root_font_size(new_primary_style.effective_zoom.unzoom(size.px()));
977                    if device.used_root_font_size() {
978                        // If the root font-size changed since last time, and something
979                        // in the document did use rem units, ensure we recascade the
980                        // entire tree.
981                        restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants;
982                    }
983                }
984
985                if is_container && old_font_size.is_some() {
986                    // TODO(emilio): Maybe only do this if we were matched
987                    // against relative font sizes?
988                    // Also, maybe we should do this as well for font-family /
989                    // etc changes (for ex/ch/ic units to work correctly)? We
990                    // should probably do the optimization mentioned above if
991                    // so.
992                    restyle_requirement = ChildRestyleRequirement::MustMatchDescendants;
993                }
994            }
995
996            // For line-height, we want the fully resolved value, as `normal` also depends on other
997            // font properties.
998            let new_line_height = device
999                .calc_line_height(
1000                    &new_primary_style.get_font(),
1001                    new_primary_style.writing_mode,
1002                    None,
1003                )
1004                .0;
1005            let old_line_height = old_style.map(|s| {
1006                device
1007                    .calc_line_height(&s.get_font(), s.writing_mode, None)
1008                    .0
1009            });
1010
1011            if old_line_height != Some(new_line_height) {
1012                if is_root {
1013                    debug_assert!(self.owner_doc_matches_for_testing(device));
1014                    device.set_root_line_height(
1015                        new_primary_style
1016                            .effective_zoom
1017                            .unzoom(new_line_height.px()),
1018                    );
1019                    if device.used_root_line_height() {
1020                        restyle_requirement = std::cmp::max(
1021                            restyle_requirement,
1022                            ChildRestyleRequirement::MustCascadeDescendants,
1023                        );
1024                    }
1025                }
1026
1027                if is_container && old_line_height.is_some() {
1028                    restyle_requirement = ChildRestyleRequirement::MustMatchDescendants;
1029                }
1030            }
1031        }
1032
1033        if context.shared.stylist.quirks_mode() == QuirksMode::Quirks {
1034            if self.is_html_document_body_element() {
1035                // NOTE(emilio): We _could_ handle dynamic changes to it if it
1036                // changes and before we reach our children the cascade stops,
1037                // but we don't track right now whether we use the document body
1038                // color, and nobody else handles that properly anyway.
1039                let device = context.shared.stylist.device();
1040
1041                // Needed for the "inherit from body" quirk.
1042                let text_color = new_primary_style.get_inherited_text().clone_color();
1043                device.set_body_text_color(text_color);
1044            }
1045        }
1046
1047        // Don't accumulate damage if we're in the final animation traversal.
1048        if context
1049            .shared
1050            .traversal_flags
1051            .contains(TraversalFlags::FinalAnimationTraversal)
1052        {
1053            return ChildRestyleRequirement::MustCascadeChildren;
1054        }
1055
1056        // Also, don't do anything if there was no style.
1057        let old_primary_style = match old_styles.primary {
1058            Some(s) => s,
1059            None => return ChildRestyleRequirement::MustCascadeChildren,
1060        };
1061
1062        let old_container_type = old_primary_style.clone_container_type();
1063        let new_container_type = new_primary_style.clone_container_type();
1064        if old_container_type != new_container_type && !new_container_type.is_size_container_type()
1065        {
1066            // Stopped being a size container. Re-evaluate container queries and units on all our descendants.
1067            // Changes into and between different size containment is handled in `UpdateContainerQueryStyles`.
1068            restyle_requirement = ChildRestyleRequirement::MustMatchDescendants;
1069        } else if old_container_type.is_size_container_type()
1070            && !old_primary_style.is_display_contents()
1071            && new_primary_style.is_display_contents()
1072        {
1073            // Also re-evaluate when a container gets 'display: contents', since size queries will now evaluate to unknown.
1074            // Other displays like 'inline' will keep generating a box, so they are handled in `UpdateContainerQueryStyles`.
1075            restyle_requirement = ChildRestyleRequirement::MustMatchDescendants;
1076        }
1077
1078        restyle_requirement = cmp::max(
1079            restyle_requirement,
1080            self.accumulate_damage_for(
1081                context.shared,
1082                &mut data.damage,
1083                &old_primary_style,
1084                new_primary_style,
1085                None,
1086            ),
1087        );
1088
1089        if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() {
1090            // This is the common case; no need to examine pseudos here.
1091            return restyle_requirement;
1092        }
1093
1094        let pseudo_styles = old_styles
1095            .pseudos
1096            .as_array()
1097            .iter()
1098            .zip(data.styles.pseudos.as_array().iter());
1099
1100        for (i, (old, new)) in pseudo_styles.enumerate() {
1101            match (old, new) {
1102                (&Some(ref old), &Some(ref new)) => {
1103                    self.accumulate_damage_for(
1104                        context.shared,
1105                        &mut data.damage,
1106                        old,
1107                        new,
1108                        Some(&PseudoElement::from_eager_index(i)),
1109                    );
1110                },
1111                (&None, &None) => {},
1112                _ => {
1113                    // It's possible that we're switching from not having
1114                    // ::before/::after at all to having styles for them but not
1115                    // actually having a useful pseudo-element.  Check for that
1116                    // case.
1117                    let pseudo = PseudoElement::from_eager_index(i);
1118                    let new_pseudo_should_exist =
1119                        new.as_ref().map_or(false, |s| pseudo.should_exist(s));
1120                    let old_pseudo_should_exist =
1121                        old.as_ref().map_or(false, |s| pseudo.should_exist(s));
1122                    if new_pseudo_should_exist != old_pseudo_should_exist {
1123                        data.damage |= RestyleDamage::reconstruct();
1124                        return restyle_requirement;
1125                    }
1126                },
1127            }
1128        }
1129
1130        restyle_requirement
1131    }
1132
1133    /// Updates the rule nodes without re-running selector matching, using just
1134    /// the rule tree.
1135    ///
1136    /// Returns true if an !important rule was replaced.
1137    fn replace_rules(
1138        &self,
1139        replacements: RestyleHint,
1140        context: &mut StyleContext<Self>,
1141        cascade_inputs: &mut ElementCascadeInputs,
1142    ) -> bool {
1143        let mut result = false;
1144        result |= self.replace_rules_internal(
1145            replacements,
1146            context,
1147            CascadeVisitedMode::Unvisited,
1148            cascade_inputs,
1149        );
1150        result |= self.replace_rules_internal(
1151            replacements,
1152            context,
1153            CascadeVisitedMode::Visited,
1154            cascade_inputs,
1155        );
1156        result
1157    }
1158
1159    /// Given the old and new style of this element, and whether it's a
1160    /// pseudo-element, compute the restyle damage used to determine which
1161    /// kind of layout or painting operations we'll need.
1162    fn compute_style_difference(
1163        &self,
1164        old_values: &ComputedValues,
1165        new_values: &ComputedValues,
1166        pseudo: Option<&PseudoElement>,
1167    ) -> StyleDifference {
1168        debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
1169        RestyleDamage::compute_style_difference::<Self>(old_values, new_values)
1170    }
1171}
1172
1173impl<E: TElement> MatchMethods for E {}