Skip to main content

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