Skip to main content

style/properties/
cascade.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//! The main cascading algorithm of the style system.
6
7use crate::applicable_declarations::{CascadePriority, RevertKind};
8use crate::color::AbsoluteColor;
9use crate::computed_value_flags::ComputedValueFlags;
10use crate::context::TreeCountingCaches;
11use crate::custom_properties::{
12    get_attr_value_for_cycle_resolution, handle_invalid_at_computed_value_time,
13    remove_and_insert_initial_value, substitute_references_if_needed_and_apply,
14    ComputedCustomProperties, ComputedSubstitutionFunctions, Name, NonCustomReferenceMap,
15    ReferenceFlags, References, SingleNonCustomReference, SubstitutionFunctionKind, VariableValue,
16};
17use crate::dom::{AttributeTracker, DummyElementContext, ElementContext, TElement};
18#[cfg(feature = "gecko")]
19use crate::font_metrics::FontMetricsOrientation;
20use crate::properties::{
21    property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, LonghandId,
22    LonghandIdSet, PrioritaryPropertyId, PrioritaryPropertyIdSet, PropertyDeclaration,
23    PropertyDeclarationId, PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder,
24    CASCADE_PROPERTY,
25};
26use crate::properties::{CustomDeclaration, CustomDeclarationValue, UnparsedValue};
27use crate::properties_and_values::rule::Descriptors as PropertyDescriptors;
28use crate::properties_and_values::value::ComputedValue as ComputedRegisteredValue;
29use crate::rule_cache::{RuleCache, RuleCacheConditions};
30use crate::rule_tree::{CascadeLevel, CascadeOrigin, RuleCascadeFlags, StrongRuleNode};
31use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
32use crate::selector_parser::PseudoElement;
33use crate::shared_lock::StylesheetGuards;
34use crate::style_adjuster::StyleAdjuster;
35use crate::stylesheets::container_rule::ContainerSizeQuery;
36use crate::stylesheets::layer_rule::LayerOrder;
37use crate::stylesheets::UrlExtraData;
38use crate::stylist::Stylist;
39use crate::values::computed::ToComputedValue;
40#[cfg(feature = "gecko")]
41use crate::values::specified::length::FontBaseSize;
42use crate::values::specified::position::PositionTryFallbacksTryTactic;
43use crate::values::{computed, specified};
44use rustc_hash::FxHashMap;
45use selectors::matching::ElementSelectorFlags;
46use servo_arc::Arc;
47use smallvec::SmallVec;
48use std::borrow::Cow;
49use std::cmp;
50use std::collections::hash_map::Entry;
51
52/// Whether we're resolving a style with the purposes of reparenting for ::first-line.
53#[derive(Copy, Clone)]
54#[allow(missing_docs)]
55pub enum FirstLineReparenting<'a> {
56    No,
57    Yes {
58        /// The style we're re-parenting for ::first-line. ::first-line only affects inherited
59        /// properties so we use this to avoid some work and also ensure correctness by copying the
60        /// reset structs from this style.
61        style_to_reparent: &'a ComputedValues,
62    },
63}
64
65/// Performs the CSS cascade, computing new styles for an element from its parent style.
66///
67/// The arguments are:
68///
69///   * `device`: Used to get the initial viewport and other external state.
70///
71///   * `rule_node`: The rule node in the tree that represent the CSS rules that
72///   matched.
73///
74///   * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
75///
76/// Returns the computed values.
77///   * `flags`: Various flags.
78///
79pub fn cascade<E>(
80    stylist: &Stylist,
81    pseudo: Option<&PseudoElement>,
82    rule_node: &StrongRuleNode,
83    guards: &StylesheetGuards,
84    parent_style: Option<&ComputedValues>,
85    layout_parent_style: Option<&ComputedValues>,
86    first_line_reparenting: FirstLineReparenting,
87    try_tactic: &PositionTryFallbacksTryTactic,
88    visited_rules: Option<&StrongRuleNode>,
89    cascade_input_flags: ComputedValueFlags,
90    included_cascade_flags: RuleCascadeFlags,
91    rule_cache: Option<&RuleCache>,
92    rule_cache_conditions: &mut RuleCacheConditions,
93    element: Option<E>,
94    tree_counting_caches: &mut TreeCountingCaches,
95) -> Arc<ComputedValues>
96where
97    E: TElement,
98{
99    cascade_rules(
100        stylist,
101        pseudo,
102        rule_node,
103        guards,
104        parent_style,
105        layout_parent_style,
106        first_line_reparenting,
107        try_tactic,
108        CascadeMode::Unvisited { visited_rules },
109        cascade_input_flags,
110        included_cascade_flags,
111        rule_cache,
112        rule_cache_conditions,
113        element,
114        tree_counting_caches,
115    )
116}
117
118struct DeclarationIterator<'a> {
119    // Global to the iteration.
120    guards: &'a StylesheetGuards<'a>,
121    restriction: Option<PropertyFlags>,
122    // The rule we're iterating over.
123    current_rule_node: Option<&'a StrongRuleNode>,
124    // Per rule state.
125    declarations: DeclarationImportanceIterator<'a>,
126    priority: CascadePriority,
127}
128
129impl<'a> DeclarationIterator<'a> {
130    #[inline]
131    fn new(
132        rule_node: &'a StrongRuleNode,
133        guards: &'a StylesheetGuards,
134        pseudo: Option<&PseudoElement>,
135    ) -> Self {
136        let restriction = pseudo.and_then(|p| p.property_restriction());
137        let mut iter = Self {
138            guards,
139            current_rule_node: Some(rule_node),
140            priority: CascadePriority::new(
141                CascadeLevel::new(CascadeOrigin::UA),
142                LayerOrder::root(),
143                RuleCascadeFlags::empty(),
144            ),
145            declarations: DeclarationImportanceIterator::default(),
146            restriction,
147        };
148        iter.update_for_node(rule_node);
149        iter
150    }
151
152    fn update_for_node(&mut self, node: &'a StrongRuleNode) {
153        self.priority = node.cascade_priority();
154        let guard = self.priority.cascade_level().origin().guard(&self.guards);
155        self.declarations = match node.style_source() {
156            Some(source) => source.read(guard).declaration_importance_iter(),
157            None => DeclarationImportanceIterator::default(),
158        };
159    }
160}
161
162impl<'a> Iterator for DeclarationIterator<'a> {
163    type Item = (&'a PropertyDeclaration, CascadePriority);
164
165    #[inline]
166    fn next(&mut self) -> Option<Self::Item> {
167        loop {
168            if let Some((decl, importance)) = self.declarations.next_back() {
169                if self.priority.cascade_level().is_important() != importance.important() {
170                    continue;
171                }
172
173                if let Some(restriction) = self.restriction {
174                    // decl.id() is either a longhand or a custom
175                    // property.  Custom properties are always allowed, but
176                    // longhands are only allowed if they have our
177                    // restriction flag set.
178                    if let PropertyDeclarationId::Longhand(id) = decl.id() {
179                        if !id.flags().contains(restriction)
180                            && self.priority.cascade_level().origin() != CascadeOrigin::UA
181                        {
182                            continue;
183                        }
184                    }
185                }
186
187                return Some((decl, self.priority));
188            }
189
190            let next_node = self.current_rule_node.take()?.parent()?;
191            self.current_rule_node = Some(next_node);
192            self.update_for_node(next_node);
193        }
194    }
195}
196
197fn cascade_rules<E>(
198    stylist: &Stylist,
199    pseudo: Option<&PseudoElement>,
200    rule_node: &StrongRuleNode,
201    guards: &StylesheetGuards,
202    parent_style: Option<&ComputedValues>,
203    layout_parent_style: Option<&ComputedValues>,
204    first_line_reparenting: FirstLineReparenting,
205    try_tactic: &PositionTryFallbacksTryTactic,
206    cascade_mode: CascadeMode,
207    cascade_input_flags: ComputedValueFlags,
208    included_cascade_flags: RuleCascadeFlags,
209    rule_cache: Option<&RuleCache>,
210    rule_cache_conditions: &mut RuleCacheConditions,
211    element: Option<E>,
212    tree_counting_caches: &mut TreeCountingCaches,
213) -> Arc<ComputedValues>
214where
215    E: TElement,
216{
217    apply_declarations(
218        stylist,
219        pseudo,
220        rule_node,
221        guards,
222        DeclarationIterator::new(rule_node, guards, pseudo),
223        parent_style,
224        layout_parent_style,
225        first_line_reparenting,
226        try_tactic,
227        cascade_mode,
228        cascade_input_flags,
229        included_cascade_flags,
230        rule_cache,
231        rule_cache_conditions,
232        element,
233        tree_counting_caches,
234    )
235}
236
237/// Whether we're cascading for visited or unvisited styles.
238#[derive(Clone, Copy)]
239pub enum CascadeMode<'a, 'b> {
240    /// We're cascading for unvisited styles.
241    Unvisited {
242        /// The visited rules that should match the visited style.
243        visited_rules: Option<&'a StrongRuleNode>,
244    },
245    /// We're cascading for visited styles.
246    Visited {
247        /// The cascade for our unvisited style.
248        unvisited_context: &'a computed::Context<'b>,
249    },
250}
251
252fn iter_declarations<'c, 'decls: 'c>(
253    iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
254    declarations: &mut Declarations<'decls>,
255    mut custom: Option<(&mut Cascade<'c>, &mut computed::Context)>,
256    attribute_tracker: &mut AttributeTracker,
257) {
258    for (declaration, priority) in iter {
259        if let PropertyDeclaration::Custom(ref declaration) = *declaration {
260            if let Some((ref mut cascade, ref mut context)) = custom {
261                cascade.cascade_custom_property(context, declaration, priority);
262            }
263        } else {
264            let id = declaration.id().as_longhand().unwrap();
265            declarations.note_declaration(declaration, priority, id);
266            if Cascade::might_have_non_custom_or_attr_dependency(id, declaration) {
267                if let Some((ref mut cascade, ref mut context)) = custom {
268                    cascade.maybe_note_non_custom_dependency(
269                        context,
270                        id,
271                        declaration,
272                        attribute_tracker,
273                    );
274                }
275            }
276        }
277    }
278}
279
280/// NOTE: This function expects the declaration with more priority to appear
281/// first.
282pub fn apply_declarations<'decls, E, I>(
283    stylist: &Stylist,
284    pseudo: Option<&PseudoElement>,
285    rules: &StrongRuleNode,
286    guards: &StylesheetGuards,
287    iter: I,
288    parent_style: Option<&ComputedValues>,
289    layout_parent_style: Option<&ComputedValues>,
290    first_line_reparenting: FirstLineReparenting<'_>,
291    try_tactic: &PositionTryFallbacksTryTactic,
292    cascade_mode: CascadeMode,
293    cascade_input_flags: ComputedValueFlags,
294    included_cascade_flags: RuleCascadeFlags,
295    rule_cache: Option<&RuleCache>,
296    rule_cache_conditions: &mut RuleCacheConditions,
297    element: Option<E>,
298    tree_counting_caches: &mut TreeCountingCaches,
299) -> Arc<ComputedValues>
300where
301    E: TElement,
302    I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
303{
304    debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
305    let device = stylist.device();
306    let inherited_style = parent_style.unwrap_or(device.default_computed_values());
307    let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
308    let container_size_query =
309        ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some());
310
311    let originating_element = element.map(|e| e.ultimate_originating_element());
312    let element_context = match originating_element {
313        Some(ref e) => e as &dyn ElementContext,
314        None => &DummyElementContext {},
315    };
316
317    let mut context = computed::Context::new(
318        // We'd really like to own the rules here to avoid refcount traffic, but
319        // animation's usage of `apply_declarations` make this tricky. See bug
320        // 1375525.
321        StyleBuilder::new(
322            device,
323            Some(stylist),
324            parent_style,
325            pseudo,
326            Some(rules.clone()),
327            is_root_element,
328        ),
329        stylist.quirks_mode(),
330        rule_cache_conditions,
331        container_size_query,
332        included_cascade_flags,
333        element_context,
334        tree_counting_caches,
335    );
336
337    context.style().add_flags(cascade_input_flags);
338    // Reuse flags from computing registered custom properties initial values, such as
339    // whether they depend on viewport units.
340    context
341        .style()
342        .add_flags(stylist.get_custom_property_initial_values_flags());
343
344    let using_cached_reset_properties;
345    let ignore_colors = context.builder.device.forced_colors().is_active();
346    let mut cascade = Cascade::new(first_line_reparenting, stylist, ignore_colors);
347    let mut declarations = Default::default();
348    let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
349    let mut attribute_tracker = AttributeTracker::new(element_context);
350
351    let properties_to_apply = match cascade_mode {
352        CascadeMode::Visited { unvisited_context } => {
353            context.builder.substitution_functions =
354                unvisited_context.builder.substitution_functions.clone();
355            context.builder.writing_mode = unvisited_context.builder.writing_mode;
356            context.builder.color_scheme = unvisited_context.builder.color_scheme;
357            // We never insert visited styles into the cache so we don't need to try looking it up.
358            // It also wouldn't be super-profitable, only a handful :visited properties are
359            // non-inherited.
360            using_cached_reset_properties = false;
361            // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could
362            // try to avoid gathering the declarations. That'd be:
363            //      unvisited_context.builder.rules.as_ref() == Some(rules)
364            iter_declarations(iter, &mut declarations, None, &mut attribute_tracker);
365
366            LonghandIdSet::visited_dependent()
367        },
368        CascadeMode::Unvisited { visited_rules } => {
369            cascade.init_custom_properties(&mut context);
370            iter_declarations(
371                iter,
372                &mut declarations,
373                Some((&mut cascade, &mut context)),
374                &mut attribute_tracker,
375            );
376            // Detect cycles, remove properties participating in them, and resolve custom
377            // properties, applying prioritary properties (font-size, color-scheme, line-height)
378            // interleaved so that a custom property depending on `em`/`lh`/the used color-scheme is
379            // substituted once the prioritary property it needs has been applied.
380            cascade.apply_custom_and_prioritary_properties(
381                &mut context,
382                &declarations,
383                &mut shorthand_cache,
384                &mut attribute_tracker,
385            );
386
387            if let Some(visited_rules) = visited_rules {
388                cascade.compute_visited_style_if_needed(
389                    &mut context,
390                    element,
391                    parent_style,
392                    layout_parent_style,
393                    try_tactic,
394                    visited_rules,
395                    guards,
396                );
397            }
398
399            using_cached_reset_properties =
400                cascade.try_to_use_cached_reset_properties(&mut context, rule_cache, guards);
401
402            if using_cached_reset_properties {
403                LonghandIdSet::late_group_only_inherited()
404            } else {
405                LonghandIdSet::late_group()
406            }
407        },
408    };
409
410    cascade.apply_non_prioritary_properties(
411        &mut context,
412        &declarations.longhand_declarations,
413        &mut shorthand_cache,
414        &properties_to_apply,
415        &mut attribute_tracker,
416    );
417
418    context.builder.attribute_references = attribute_tracker.finalize();
419
420    cascade.finished_applying_properties(&mut context.builder);
421
422    context.builder.clear_modified_reset();
423
424    if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
425        StyleAdjuster::new(&mut context.builder).adjust(
426            layout_parent_style.unwrap_or(inherited_style),
427            element,
428            try_tactic,
429            &cascade.author_specified,
430        );
431    }
432
433    if context.builder.modified_reset() || using_cached_reset_properties {
434        // If we adjusted any reset structs, we can't cache this ComputedValues.
435        //
436        // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside
437        // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be
438        // set appropriately if we didn't compute those reset properties.)
439        context.rule_cache_conditions.borrow_mut().set_uncacheable();
440    }
441
442    if context
443        .builder
444        .flags()
445        .intersects(ComputedValueFlags::tree_counting_function_flags())
446    {
447        if let Some(el) = element {
448            el.apply_selector_flags(ElementSelectorFlags::MAY_HAVE_TREE_COUNTING_FUNCTION);
449        } else {
450            debug_assert!(
451                false,
452                "Tree counting function flag applied without an element?"
453            );
454        }
455    }
456
457    context.builder.build()
458}
459
460/// For ignored colors mode, we sometimes want to do something equivalent to
461/// "revert-or-initial", where we `revert` for a given origin, but then apply a
462/// given initial value if nothing in other origins did override it.
463///
464/// This is a bit of a clunky way of achieving this.
465type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>;
466
467fn is_base_appearance(context: &computed::Context) -> bool {
468    use computed::Appearance;
469    let box_style = context.builder.get_box();
470    match box_style.clone_appearance() {
471        Appearance::BaseSelect => {
472            matches!(
473                box_style.clone__moz_default_appearance(),
474                Appearance::Listbox | Appearance::Menulist
475            )
476        },
477        Appearance::Base => box_style.clone__moz_default_appearance() != Appearance::None,
478        _ => false,
479    }
480}
481
482fn tweak_when_ignoring_colors(
483    context: &computed::Context,
484    longhand_id: LonghandId,
485    origin: CascadeOrigin,
486    declaration: &mut Cow<PropertyDeclaration>,
487    declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden,
488) {
489    use crate::values::computed::ToComputedValue;
490    use crate::values::specified::Color;
491
492    if !longhand_id.ignored_when_document_colors_disabled() {
493        return;
494    }
495
496    let is_ua_or_user_rule = matches!(origin, CascadeOrigin::User | CascadeOrigin::UA);
497    if is_ua_or_user_rule {
498        return;
499    }
500
501    // Always honor colors if forced-color-adjust is set to none.
502    let forced = context
503        .builder
504        .get_inherited_text()
505        .clone_forced_color_adjust();
506    if forced == computed::ForcedColorAdjust::None {
507        return;
508    }
509
510    fn alpha_channel(color: &Color, context: &computed::Context) -> f32 {
511        // We assume here currentColor is opaque.
512        color
513            .to_computed_value(context)
514            .resolve_to_absolute(&AbsoluteColor::BLACK)
515            .alpha
516    }
517
518    // A few special-cases ahead.
519    match **declaration {
520        // Honor CSS-wide keywords like unset / revert / initial...
521        PropertyDeclaration::CSSWideKeyword(..) => return,
522        PropertyDeclaration::BackgroundColor(ref color) => {
523            // We honor system colors and transparent colors unconditionally.
524            //
525            // NOTE(emilio): We honor transparent unconditionally, like we do
526            // for color, even though it causes issues like bug 1625036. The
527            // reasoning is that the conditions that trigger that (having
528            // mismatched widget and default backgrounds) are both uncommon, and
529            // broken in other applications as well, and not honoring
530            // transparent makes stuff uglier or break unconditionally
531            // (bug 1666059, bug 1755713).
532            if color.honored_in_forced_colors_mode(context, /* allow_transparent = */ true) {
533                return;
534            }
535            // For background-color, we revert or initial-with-preserved-alpha
536            // otherwise, this is needed to preserve semi-transparent
537            // backgrounds.
538            let alpha = alpha_channel(color, context);
539            if alpha == 0.0 {
540                return;
541            }
542            let mut color = context.builder.device.default_background_color();
543            color.alpha = alpha;
544            declarations_to_apply_unless_overridden
545                .push(PropertyDeclaration::BackgroundColor(color.into()))
546        },
547        PropertyDeclaration::Color(ref color) => {
548            // We honor color: transparent and system colors.
549            if color
550                .0
551                .honored_in_forced_colors_mode(context, /* allow_transparent = */ true)
552            {
553                return;
554            }
555            // If the inherited color would be transparent, but we would
556            // override this with a non-transparent color, then override it with
557            // the default color. Otherwise just let it inherit through.
558            if context
559                .builder
560                .get_parent_inherited_text()
561                .clone_color()
562                .alpha
563                == 0.0
564            {
565                let color = context.builder.device.default_color();
566                declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color(
567                    specified::ColorPropertyValue(color.into()),
568                ))
569            }
570        },
571        // We honor url background-images if backplating.
572        #[cfg(feature = "gecko")]
573        PropertyDeclaration::BackgroundImage(ref bkg) => {
574            use crate::values::generics::image::Image;
575            if static_prefs::pref!("browser.display.permit_backplate") {
576                if bkg
577                    .0
578                    .iter()
579                    .all(|image| matches!(*image, Image::Url(..) | Image::None))
580                {
581                    return;
582                }
583            }
584        },
585        _ => {
586            // We honor system colors more generally for all colors.
587            //
588            // We used to honor transparent but that causes accessibility
589            // regressions like bug 1740924.
590            //
591            // NOTE(emilio): This doesn't handle caret-color and accent-color
592            // because those use a slightly different syntax (<color> | auto for
593            // example).
594            //
595            // That's probably fine though, as using a system color for
596            // caret-color doesn't make sense (using currentColor is fine), and
597            // we ignore accent-color in high-contrast-mode anyways.
598            if let Some(color) = declaration.color_value() {
599                if color
600                    .honored_in_forced_colors_mode(context, /* allow_transparent = */ false)
601                {
602                    return;
603                }
604            }
605        },
606    }
607
608    *declaration.to_mut() =
609        PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert);
610}
611
612/// We track the index only for prioritary properties. For other properties we can just iterate.
613type DeclarationIndex = u16;
614
615/// "Prioritary" properties are properties that other properties depend on in one way or another.
616///
617/// We keep track of their position in the declaration vector, in order to be able to cascade them
618/// separately in precise order.
619#[derive(Copy, Clone)]
620struct PrioritaryDeclarationPosition {
621    // DeclarationIndex::MAX signals no index.
622    most_important: DeclarationIndex,
623    least_important: DeclarationIndex,
624}
625
626impl Default for PrioritaryDeclarationPosition {
627    fn default() -> Self {
628        Self {
629            most_important: DeclarationIndex::MAX,
630            least_important: DeclarationIndex::MAX,
631        }
632    }
633}
634
635#[derive(Copy, Clone)]
636struct Declaration<'a> {
637    decl: &'a PropertyDeclaration,
638    priority: CascadePriority,
639    next_index: DeclarationIndex,
640}
641
642/// The set of property declarations from our rules.
643#[derive(Default)]
644pub(crate) struct Declarations<'a> {
645    /// Whether we have any prioritary property. This is just a minor optimization.
646    has_prioritary_properties: bool,
647    /// A list of all the applicable longhand declarations.
648    longhand_declarations: SmallVec<[Declaration<'a>; 64]>,
649    /// The prioritary property position data.
650    prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY],
651}
652
653impl<'a> Declarations<'a> {
654    fn note_prioritary_property(&mut self, id: PrioritaryPropertyId) {
655        let new_index = self.longhand_declarations.len();
656        if new_index >= DeclarationIndex::MAX as usize {
657            // This prioritary property is past the amount of declarations we can track. Let's give
658            // up applying it to prevent getting confused.
659            return;
660        }
661
662        self.has_prioritary_properties = true;
663        let new_index = new_index as DeclarationIndex;
664        let position = &mut self.prioritary_positions[id as usize];
665        if position.most_important == DeclarationIndex::MAX {
666            // We still haven't seen this property, record the current position as the most
667            // prioritary index.
668            position.most_important = new_index;
669        } else {
670            // Let the previous item in the list know about us.
671            self.longhand_declarations[position.least_important as usize].next_index = new_index;
672        }
673        position.least_important = new_index;
674    }
675
676    fn note_declaration(
677        &mut self,
678        decl: &'a PropertyDeclaration,
679        priority: CascadePriority,
680        id: LonghandId,
681    ) {
682        if let Some(id) = PrioritaryPropertyId::from_longhand(id) {
683            self.note_prioritary_property(id);
684        }
685        self.longhand_declarations.push(Declaration {
686            decl,
687            priority,
688            next_index: 0,
689        });
690    }
691}
692
693#[derive(Default)]
694struct RevertedSet {
695    // Just to avoid the hashmap lookup in the common case.
696    longhands_set: LonghandIdSet,
697    longhands: FxHashMap<LonghandId, (CascadePriority, RevertKind)>,
698    custom: PrecomputedHashMap<Name, (CascadePriority, RevertKind)>,
699}
700
701#[derive(Default)]
702struct SeenSubstitutionFunctions<'a> {
703    /// The boolean means whether the value may have references. If false, we don't need to bother
704    /// performing lookups for cycle detection.
705    var: PrecomputedHashMap<&'a Name, bool>,
706    attr: PrecomputedHashSet<&'a Name>,
707}
708
709#[derive(Default)]
710struct SeenSet<'a> {
711    longhands: LonghandIdSet,
712    custom: SeenSubstitutionFunctions<'a>,
713}
714
715/// Only registered (typed) properties emit a non-custom edge, from their own value.
716fn find_non_custom_references(
717    registration: &PropertyDescriptors,
718    value: &VariableValue,
719    is_root_element: bool,
720) -> ReferenceFlags {
721    use crate::properties_and_values::syntax::data_type::DependentDataTypes;
722
723    let mut result = ReferenceFlags::empty();
724    let Some(syntax) = registration.syntax.as_ref() else {
725        return result;
726    };
727    let dependent_types = syntax.dependent_types();
728    let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH);
729    if may_reference_length {
730        result |= value.references.non_custom_references(is_root_element);
731    }
732    if dependent_types.intersects(DependentDataTypes::COLOR) {
733        // The value depends on the used color-scheme (e.g. a `<color>` property referencing
734        // `light-dark()` or a system color).
735        result |= ReferenceFlags::COLOR_SCHEME;
736    }
737    result
738}
739
740/// Resolves the custom properties for a single `@keyframes` keyframe, layered on top of the base
741/// style's already-computed custom properties.
742///
743/// `@keyframes` aren't part of the regular cascade (see bug 1883255), so this lets the caller seed
744/// the substitution map with the element's computed custom properties, cascade the keyframe's
745/// custom declarations on top, and resolve them (running cycle detection and substitution) into
746/// `context.builder.substitution_functions`, which the subsequent animation-value computation reads
747/// to substitute `var()` references.
748pub struct KeyframeCustomPropertiesBuilder<'a> {
749    cascade: Cascade<'a>,
750    decls: Declarations<'a>,
751    shorthand_cache: ShorthandsWithPropertyReferencesCache,
752}
753
754impl<'a> KeyframeCustomPropertiesBuilder<'a> {
755    /// Creates a new builder, seeding the substitution map with `base` (typically the element's
756    /// computed custom properties).
757    pub fn new(
758        stylist: &'a Stylist,
759        context: &mut computed::Context,
760        base: ComputedCustomProperties,
761    ) -> Self {
762        context.builder.substitution_functions =
763            ComputedSubstitutionFunctions::new(Some(base), None);
764        Self {
765            cascade: Cascade::new_for_custom_properties_only(stylist),
766            decls: Declarations::default(),
767            shorthand_cache: ShorthandsWithPropertyReferencesCache::default(),
768        }
769    }
770
771    /// Cascades a single custom-property declaration from the keyframe.
772    pub fn cascade(
773        &mut self,
774        context: &mut computed::Context,
775        declaration: &'a CustomDeclaration,
776        priority: CascadePriority,
777    ) {
778        self.cascade
779            .cascade_custom_property(context, declaration, priority);
780    }
781
782    /// Resolves the cascaded custom properties into `context.builder.substitution_functions`.
783    pub fn build(
784        mut self,
785        context: &mut computed::Context,
786        attribute_tracker: &mut AttributeTracker,
787    ) {
788        self.cascade.apply_custom_and_prioritary_properties(
789            context,
790            &self.decls,
791            &mut self.shorthand_cache,
792            attribute_tracker,
793        );
794    }
795}
796
797pub(crate) struct Cascade<'a> {
798    first_line_reparenting: FirstLineReparenting<'a>,
799    stylist: &'a Stylist,
800    ignore_colors: bool,
801    seen: SeenSet<'a>,
802    reverted: RevertedSet,
803    author_specified: LonghandIdSet,
804    declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden,
805    may_have_custom_property_cycles: bool,
806    references_from_non_custom_properties: NonCustomReferenceMap<Vec<Arc<UnparsedValue>>>,
807    /// Set of prioritary properties that have already been applied.
808    ensured_prioritary: PrioritaryPropertyIdSet,
809}
810
811impl<'a> Cascade<'a> {
812    fn new(
813        first_line_reparenting: FirstLineReparenting<'a>,
814        stylist: &'a Stylist,
815        ignore_colors: bool,
816    ) -> Self {
817        Self {
818            first_line_reparenting,
819            stylist,
820            ignore_colors,
821            seen: Default::default(),
822            author_specified: Default::default(),
823            reverted: Default::default(),
824            declarations_to_apply_unless_overridden: Default::default(),
825            may_have_custom_property_cycles: false,
826            ensured_prioritary: PrioritaryPropertyIdSet::default(),
827            references_from_non_custom_properties: Default::default(),
828        }
829    }
830
831    /// Creates a `Cascade` for resolving custom properties outside of a full cascade (e.g. for
832    /// keyframes). The visited-style and position-try paths aren't reachable in this mode.
833    fn new_for_custom_properties_only(stylist: &'a Stylist) -> Self {
834        Self {
835            first_line_reparenting: FirstLineReparenting::No,
836            stylist,
837            ignore_colors: false,
838            seen: Default::default(),
839            author_specified: Default::default(),
840            reverted: Default::default(),
841            declarations_to_apply_unless_overridden: Default::default(),
842            may_have_custom_property_cycles: false,
843            ensured_prioritary: PrioritaryPropertyIdSet::default(),
844            references_from_non_custom_properties: Default::default(),
845        }
846    }
847
848    fn substitute_variables_if_needed<'cache, 'decl>(
849        &self,
850        context: &mut computed::Context,
851        shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
852        declaration: &'decl PropertyDeclaration,
853        attribute_tracker: &mut AttributeTracker,
854    ) -> Cow<'decl, PropertyDeclaration>
855    where
856        'cache: 'decl,
857    {
858        let declaration = match *declaration {
859            PropertyDeclaration::WithVariables(ref declaration) => declaration,
860            ref d => return Cow::Borrowed(d),
861        };
862
863        if !declaration.id.inherited() {
864            context.rule_cache_conditions.borrow_mut().set_uncacheable();
865
866            // NOTE(emilio): We only really need to add the `display` /
867            // `content` flag if the CSS variable has not been specified on our
868            // declarations, but we don't have that information at this point,
869            // and it doesn't seem like an important enough optimization to
870            // warrant it.
871            if matches!(declaration.id, LonghandId::Display | LonghandId::Content) {
872                context
873                    .builder
874                    .add_flags(ComputedValueFlags::DISPLAY_OR_CONTENT_DEPEND_ON_INHERITED_STYLE);
875            }
876        }
877
878        debug_assert!(
879            context.builder.stylist.is_some(),
880            "Need a Stylist to substitute variables!"
881        );
882        declaration.value.substitute_variables(
883            declaration.id,
884            &context.builder.substitution_functions(),
885            context.builder.stylist.unwrap(),
886            context,
887            shorthand_cache,
888            attribute_tracker,
889        )
890    }
891
892    fn apply_one_prioritary_property(
893        &mut self,
894        context: &mut computed::Context,
895        decls: &Declarations,
896        cache: &mut ShorthandsWithPropertyReferencesCache,
897        id: PrioritaryPropertyId,
898        attr_provider: &mut AttributeTracker,
899    ) {
900        let mut index = decls.prioritary_positions[id as usize].most_important;
901        if index == DeclarationIndex::MAX {
902            return;
903        }
904
905        let longhand_id = id.to_longhand();
906        debug_assert!(
907            !longhand_id.is_logical(),
908            "That could require more book-keeping"
909        );
910        loop {
911            let decl = decls.longhand_declarations[index as usize];
912            self.apply_one_longhand(
913                context,
914                longhand_id,
915                decl.decl,
916                decl.priority,
917                cache,
918                attr_provider,
919            );
920            if self.seen.longhands.contains(longhand_id) {
921                // Found it!
922                self.did_apply_prioritary_property(context, id);
923                return;
924            }
925            debug_assert!(
926                decl.next_index == 0 || decl.next_index > index,
927                "should make progress! {} -> {}",
928                index,
929                decl.next_index,
930            );
931            index = decl.next_index;
932            if index == 0 {
933                return;
934            }
935        }
936    }
937
938    /// Some prioritary properties need book-keeping, which this takes care of.
939    fn did_apply_prioritary_property(
940        &mut self,
941        context: &mut computed::Context,
942        id: PrioritaryPropertyId,
943    ) {
944        use crate::properties::PrioritaryPropertyId::*;
945        match id {
946            Appearance => {
947                if is_base_appearance(context) {
948                    context
949                        .style()
950                        .add_flags(ComputedValueFlags::IS_IN_APPEARANCE_BASE_SUBTREE);
951                    context
952                        .included_cascade_flags
953                        .insert(RuleCascadeFlags::APPEARANCE_BASE);
954                }
955            },
956            WritingMode | Direction | TextOrientation => {
957                context.builder.writing_mode =
958                    crate::logical_geometry::WritingMode::new(context.builder.get_inherited_box());
959            },
960            Zoom => {
961                context.builder.recompute_effective_zooms();
962                if !context.builder.effective_zoom_for_inheritance.is_one() {
963                    // NOTE(emilio): This is a bit of a hack, but matches the shipped WebKit and Blink
964                    // behavior for now. Ideally, in the future, we have a pass over all
965                    // implicitly-or-explicitly-inherited properties that can contain lengths and
966                    // re-compute them properly, see https://github.com/w3c/csswg-drafts/issues/9397.
967                    // TODO(emilio): we need to eagerly do this for line-height as well, probably.
968                    self.recompute_font_size_for_zoom_change(&mut context.builder);
969                }
970            },
971            XLang => {
972                #[cfg(feature = "gecko")]
973                self.recompute_initial_font_family_if_needed(&mut context.builder);
974                self.recompute_keyword_font_size_if_needed(context);
975            },
976            FontFamily => {
977                #[cfg(feature = "gecko")]
978                self.prioritize_user_fonts_if_needed(&mut context.builder);
979                self.recompute_keyword_font_size_if_needed(context);
980            },
981            FontSize => {
982                if self.seen.longhands.contains(LonghandId::MathDepth) {
983                    #[cfg(feature = "gecko")]
984                    Self::recompute_math_font_size_if_needed(context);
985                }
986                if self.seen.longhands.contains(LonghandId::XLang)
987                    || self.seen.longhands.contains(LonghandId::FontFamily)
988                {
989                    self.recompute_keyword_font_size_if_needed(context);
990                }
991                #[cfg(feature = "gecko")]
992                self.constrain_font_size_if_needed(&mut context.builder);
993            },
994            XTextScale => {
995                #[cfg(feature = "gecko")]
996                self.unzoom_fonts_if_needed(&mut context.builder);
997            },
998            MozMinFontSizeRatio => {
999                #[cfg(feature = "gecko")]
1000                self.constrain_font_size_if_needed(&mut context.builder);
1001            },
1002            ColorScheme => {
1003                context.builder.color_scheme =
1004                    context.builder.get_inherited_ui().color_scheme_bits();
1005            },
1006            MozDefaultAppearance | MathDepth | FontWeight | FontStretch | FontStyle
1007            | FontSizeAdjust | ForcedColorAdjust | LineHeight => {},
1008        }
1009    }
1010
1011    fn apply_non_prioritary_properties(
1012        &mut self,
1013        context: &mut computed::Context,
1014        longhand_declarations: &[Declaration],
1015        shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
1016        properties_to_apply: &LonghandIdSet,
1017        attribute_tracker: &mut AttributeTracker,
1018    ) {
1019        debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties()));
1020        debug_assert!(self.declarations_to_apply_unless_overridden.is_empty());
1021        for declaration in &*longhand_declarations {
1022            let mut longhand_id = declaration.decl.id().as_longhand().unwrap();
1023            if !properties_to_apply.contains(longhand_id) {
1024                continue;
1025            }
1026            debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none());
1027            let is_logical = longhand_id.is_logical();
1028            if is_logical {
1029                let wm = context.builder.writing_mode;
1030                context
1031                    .rule_cache_conditions
1032                    .borrow_mut()
1033                    .set_writing_mode_dependency(wm);
1034                longhand_id = longhand_id.to_physical(wm);
1035            }
1036            self.apply_one_longhand(
1037                context,
1038                longhand_id,
1039                declaration.decl,
1040                declaration.priority,
1041                shorthand_cache,
1042                attribute_tracker,
1043            );
1044        }
1045        if !self.declarations_to_apply_unless_overridden.is_empty() {
1046            debug_assert!(self.ignore_colors);
1047            for declaration in std::mem::take(&mut self.declarations_to_apply_unless_overridden) {
1048                let longhand_id = declaration.id().as_longhand().unwrap();
1049                debug_assert!(!longhand_id.is_logical());
1050                if !self.seen.longhands.contains(longhand_id) {
1051                    unsafe {
1052                        self.do_apply_declaration(context, longhand_id, &declaration);
1053                    }
1054                }
1055            }
1056        }
1057
1058        if !context.builder.effective_zoom_for_inheritance.is_one() {
1059            self.recompute_zoom_dependent_inherited_lengths(context);
1060        }
1061    }
1062
1063    #[cold]
1064    fn recompute_zoom_dependent_inherited_lengths(&self, context: &mut computed::Context) {
1065        debug_assert!(self.seen.longhands.contains(LonghandId::Zoom));
1066        for prop in LonghandIdSet::zoom_dependent_inherited_properties().iter() {
1067            if self.seen.longhands.contains(prop) {
1068                continue;
1069            }
1070            let declaration = PropertyDeclaration::css_wide_keyword(prop, CSSWideKeyword::Inherit);
1071            unsafe {
1072                self.do_apply_declaration(context, prop, &declaration);
1073            }
1074        }
1075    }
1076
1077    fn apply_one_longhand(
1078        &mut self,
1079        context: &mut computed::Context,
1080        longhand_id: LonghandId,
1081        declaration: &PropertyDeclaration,
1082        priority: CascadePriority,
1083        cache: &mut ShorthandsWithPropertyReferencesCache,
1084        attribute_tracker: &mut AttributeTracker,
1085    ) {
1086        debug_assert!(!longhand_id.is_logical());
1087        if self.seen.longhands.contains(longhand_id) {
1088            return;
1089        }
1090
1091        if !(priority.flags() - context.included_cascade_flags).is_empty() {
1092            return;
1093        }
1094
1095        if self.reverted.longhands_set.contains(longhand_id) {
1096            if let Some(&(reverted_priority, revert_kind)) =
1097                self.reverted.longhands.get(&longhand_id)
1098            {
1099                if !reverted_priority.allows_when_reverted(&priority, revert_kind) {
1100                    return;
1101                }
1102            }
1103        }
1104
1105        let mut declaration =
1106            self.substitute_variables_if_needed(context, cache, declaration, attribute_tracker);
1107
1108        // When document colors are disabled, do special handling of
1109        // properties that are marked as ignored in that mode.
1110        let origin = priority.cascade_level().origin();
1111        if self.ignore_colors {
1112            tweak_when_ignoring_colors(
1113                context,
1114                longhand_id,
1115                origin,
1116                &mut declaration,
1117                &mut self.declarations_to_apply_unless_overridden,
1118            );
1119        }
1120        let can_skip_apply = match declaration.get_css_wide_keyword() {
1121            Some(keyword) => {
1122                if let Some(revert_kind) = keyword.revert_kind() {
1123                    // We intentionally don't want to insert it into
1124                    // `self.seen.longhands`, `reverted` takes care of rejecting
1125                    // other declarations as needed.
1126                    self.reverted.longhands_set.insert(longhand_id);
1127                    self.reverted
1128                        .longhands
1129                        .insert(longhand_id, (priority, revert_kind));
1130                    return;
1131                }
1132
1133                let inherited = longhand_id.inherited();
1134                let zoomed = !context.builder.effective_zoom_for_inheritance.is_one()
1135                    && longhand_id.zoom_dependent();
1136                match keyword {
1137                    CSSWideKeyword::Revert
1138                    | CSSWideKeyword::RevertLayer
1139                    | CSSWideKeyword::RevertRule => unreachable!(),
1140                    CSSWideKeyword::Unset => !zoomed || !inherited,
1141                    CSSWideKeyword::Inherit => inherited && !zoomed,
1142                    CSSWideKeyword::Initial => !inherited,
1143                }
1144            },
1145            None => false,
1146        };
1147
1148        self.seen.longhands.insert(longhand_id);
1149        if origin.is_author_origin() {
1150            self.author_specified.insert(longhand_id);
1151        }
1152
1153        if !can_skip_apply {
1154            // Set context.scope to this declaration's cascade level so that
1155            // tree-scoped properties (anchor-name, position-anchor, anchor-scope)
1156            // get the correct scope when converted to computed values.
1157            let old_scope = context.scope;
1158            let cascade_level = priority.cascade_level();
1159            context.scope = cascade_level;
1160            unsafe { self.do_apply_declaration(context, longhand_id, &declaration) }
1161            context.scope = old_scope;
1162        }
1163    }
1164
1165    #[inline]
1166    unsafe fn do_apply_declaration(
1167        &self,
1168        context: &mut computed::Context,
1169        longhand_id: LonghandId,
1170        declaration: &PropertyDeclaration,
1171    ) {
1172        debug_assert!(!longhand_id.is_logical());
1173        // We could (and used to) use a pattern match here, but that bloats this
1174        // function to over 100K of compiled code!
1175        //
1176        // To improve i-cache behavior, we outline the individual functions and
1177        // use virtual dispatch instead.
1178        (CASCADE_PROPERTY[longhand_id as usize])(&declaration, context);
1179    }
1180
1181    fn compute_visited_style_if_needed<E>(
1182        &self,
1183        context: &mut computed::Context,
1184        element: Option<E>,
1185        parent_style: Option<&ComputedValues>,
1186        layout_parent_style: Option<&ComputedValues>,
1187        try_tactic: &PositionTryFallbacksTryTactic,
1188        visited_rules: &StrongRuleNode,
1189        guards: &StylesheetGuards,
1190    ) where
1191        E: TElement,
1192    {
1193        let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link();
1194
1195        macro_rules! visited_parent {
1196            ($parent:expr) => {
1197                if is_link {
1198                    $parent
1199                } else {
1200                    $parent.map(|p| p.visited_style().unwrap_or(p))
1201                }
1202            };
1203        }
1204
1205        // We could call apply_declarations directly, but that'd cause
1206        // another instantiation of this function which is not great.
1207        let style = cascade_rules(
1208            context.builder.stylist.unwrap(),
1209            context.builder.pseudo,
1210            visited_rules,
1211            guards,
1212            visited_parent!(parent_style),
1213            visited_parent!(layout_parent_style),
1214            self.first_line_reparenting,
1215            try_tactic,
1216            CascadeMode::Visited {
1217                unvisited_context: &*context,
1218            },
1219            // Cascade input flags don't matter for the visited style, they are
1220            // in the main (unvisited) style.
1221            Default::default(),
1222            context.included_cascade_flags,
1223            // The rule cache doesn't care about caching :visited
1224            // styles, we cache the unvisited style instead. We still do
1225            // need to set the caching dependencies properly if present
1226            // though, so the cache conditions need to match.
1227            None, // rule_cache
1228            &mut *context.rule_cache_conditions.borrow_mut(),
1229            element,
1230            &mut *context.tree_counting_caches.borrow_mut(),
1231        );
1232        context.builder.visited_style = Some(style);
1233    }
1234
1235    fn finished_applying_properties(&self, builder: &mut StyleBuilder) {
1236        #[cfg(feature = "gecko")]
1237        {
1238            if let Some(bg) = builder.get_background_if_mutated() {
1239                bg.fill_arrays();
1240            }
1241
1242            if let Some(svg) = builder.get_svg_if_mutated() {
1243                svg.fill_arrays();
1244            }
1245        }
1246
1247        if self
1248            .author_specified
1249            .contains_any(LonghandIdSet::border_background_properties())
1250        {
1251            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
1252        }
1253
1254        if self.author_specified.contains(LonghandId::Color) {
1255            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_COLOR);
1256        }
1257
1258        if self.author_specified.contains(LonghandId::TextShadow) {
1259            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_SHADOW);
1260        }
1261
1262        if self.author_specified.contains(LonghandId::GridAutoFlow) {
1263            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_GRID_AUTO_FLOW);
1264        }
1265        #[cfg(feature = "servo")]
1266        {
1267            if let Some(font) = builder.get_font_if_mutated() {
1268                font.compute_font_hash();
1269            }
1270        }
1271    }
1272
1273    fn try_to_use_cached_reset_properties(
1274        &self,
1275        context: &mut computed::Context<'a>,
1276        cache: Option<&'a RuleCache>,
1277        guards: &StylesheetGuards,
1278    ) -> bool {
1279        let style = match self.first_line_reparenting {
1280            FirstLineReparenting::Yes { style_to_reparent } => style_to_reparent,
1281            FirstLineReparenting::No => {
1282                let Some(cache) = cache else { return false };
1283                let Some(style) = cache.find(guards, &context) else {
1284                    return false;
1285                };
1286                style
1287            },
1288        };
1289
1290        context.builder.copy_reset_from(style);
1291
1292        // We're using the same reset style as another element, and we'll skip
1293        // applying the relevant properties. So we need to do the relevant
1294        // bookkeeping here to keep these bits correct.
1295        //
1296        // Note that the border/background properties are non-inherited, so we
1297        // don't need to do anything else other than just copying the bits over.
1298        //
1299        // When using this optimization, we also need to copy whether the old
1300        // style specified viewport units / used font-relative lengths, this one
1301        // would as well.  It matches the same rules, so it is the right thing
1302        // to do anyways, even if it's only used on inherited properties.
1303        let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND
1304            | ComputedValueFlags::HAS_AUTHOR_SPECIFIED_GRID_AUTO_FLOW
1305            | ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS
1306            | ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS
1307            | ComputedValueFlags::IS_IN_APPEARANCE_BASE_SUBTREE
1308            | ComputedValueFlags::USES_CONTAINER_UNITS
1309            | ComputedValueFlags::USES_VIEWPORT_UNITS
1310            | ComputedValueFlags::USES_FONT_RELATIVE_UNITS
1311            | ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY
1312            | ComputedValueFlags::USES_SIBLING_COUNT
1313            | ComputedValueFlags::USES_SIBLING_INDEX;
1314        context.builder.add_flags(style.flags & bits_to_copy);
1315
1316        true
1317    }
1318
1319    /// The initial font depends on the current lang group so we may need to
1320    /// recompute it if the language changed.
1321    #[inline]
1322    #[cfg(feature = "gecko")]
1323    fn recompute_initial_font_family_if_needed(&self, builder: &mut StyleBuilder) {
1324        use crate::gecko_bindings::bindings;
1325        use crate::values::computed::font::FontFamily;
1326
1327        let default_font_type = {
1328            let font = builder.get_font();
1329
1330            if !font.mFont.family.is_initial {
1331                return;
1332            }
1333
1334            let default_font_type = unsafe {
1335                bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
1336                    builder.device.document(),
1337                    font.mLanguage.mRawPtr,
1338                )
1339            };
1340
1341            let initial_generic = font.mFont.family.families.single_generic();
1342            debug_assert!(
1343                initial_generic.is_some(),
1344                "Initial font should be just one generic font"
1345            );
1346            if initial_generic == Some(default_font_type) {
1347                return;
1348            }
1349
1350            default_font_type
1351        };
1352
1353        // NOTE: Leaves is_initial untouched.
1354        builder.mutate_font().mFont.family.families =
1355            FontFamily::generic(default_font_type).families.clone();
1356    }
1357
1358    /// Prioritize user fonts if needed by pref.
1359    #[inline]
1360    #[cfg(feature = "gecko")]
1361    fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) {
1362        use crate::gecko_bindings::bindings;
1363
1364        // Check the use_document_fonts setting for content, but for chrome
1365        // documents they're treated as always enabled.
1366        if static_prefs::pref!("browser.display.use_document_fonts") != 0
1367            || builder.device.chrome_rules_enabled_for_document()
1368        {
1369            return;
1370        }
1371
1372        let default_font_type = {
1373            let font = builder.get_font();
1374
1375            if font.mFont.family.is_system_font {
1376                return;
1377            }
1378
1379            if !font.mFont.family.families.needs_user_font_prioritization() {
1380                return;
1381            }
1382
1383            unsafe {
1384                bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
1385                    builder.device.document(),
1386                    font.mLanguage.mRawPtr,
1387                )
1388            }
1389        };
1390
1391        let font = builder.mutate_font();
1392        font.mFont
1393            .family
1394            .families
1395            .prioritize_first_generic_or_prepend(default_font_type);
1396    }
1397
1398    /// Some keyword sizes depend on the font family and language.
1399    fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) {
1400        use crate::values::computed::ToComputedValue;
1401
1402        if !self.seen.longhands.contains(LonghandId::XLang)
1403            && !self.seen.longhands.contains(LonghandId::FontFamily)
1404        {
1405            return;
1406        }
1407
1408        let new_size = {
1409            let font = context.builder.get_font();
1410            let info = font.clone_font_size().keyword_info;
1411            let new_size = match info.kw {
1412                specified::FontSizeKeyword::None => return,
1413                _ => {
1414                    context.for_non_inherited_property = false;
1415                    specified::FontSize::Keyword(info).to_computed_value(context)
1416                },
1417            };
1418
1419            #[cfg(feature = "gecko")]
1420            if font.mScriptUnconstrainedSize == new_size.computed_size {
1421                return;
1422            }
1423
1424            new_size
1425        };
1426
1427        context.builder.mutate_font().set_font_size(new_size);
1428    }
1429
1430    /// Some properties, plus setting font-size itself, may make us go out of
1431    /// our minimum font-size range.
1432    #[cfg(feature = "gecko")]
1433    fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) {
1434        use crate::gecko_bindings::bindings;
1435        use crate::values::generics::NonNegative;
1436
1437        let min_font_size = {
1438            let font = builder.get_font();
1439            let min_font_size = unsafe {
1440                bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document())
1441            };
1442
1443            if font.mFont.size.0 >= min_font_size {
1444                return;
1445            }
1446
1447            NonNegative(min_font_size)
1448        };
1449
1450        builder.mutate_font().mFont.size = min_font_size;
1451    }
1452
1453    /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up
1454    /// the struct when this happens by unzooming its contained font values, which will have been
1455    /// zoomed in the parent.
1456    #[cfg(feature = "gecko")]
1457    fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) {
1458        debug_assert!(self.seen.longhands.contains(LonghandId::XTextScale));
1459
1460        let parent_text_scale = builder.get_parent_font().clone__x_text_scale();
1461        let text_scale = builder.get_font().clone__x_text_scale();
1462        if parent_text_scale == text_scale {
1463            return;
1464        }
1465        debug_assert_ne!(
1466            parent_text_scale.text_zoom_enabled(),
1467            text_scale.text_zoom_enabled(),
1468            "There's only one value that disables it"
1469        );
1470        debug_assert!(
1471            !text_scale.text_zoom_enabled(),
1472            "We only ever disable text zoom never enable it"
1473        );
1474        let device = builder.device;
1475        builder.mutate_font().unzoom_fonts(device);
1476    }
1477
1478    fn recompute_font_size_for_zoom_change(&self, builder: &mut StyleBuilder) {
1479        debug_assert!(self.seen.longhands.contains(LonghandId::Zoom));
1480        // NOTE(emilio): Intentionally not using the effective zoom here, since all the inherited
1481        // zooms are already applied.
1482        let old_size = builder.get_font().clone_font_size();
1483        let new_size = old_size.zoom(builder.effective_zoom_for_inheritance);
1484        if old_size == new_size {
1485            return;
1486        }
1487        builder.mutate_font().set_font_size(new_size);
1488    }
1489
1490    /// Special handling of font-size: math (used for MathML).
1491    /// https://w3c.github.io/mathml-core/#the-math-script-level-property
1492    /// TODO: Bug: 1548471: MathML Core also does not specify a script min size
1493    /// should we unship that feature or standardize it?
1494    #[cfg(feature = "gecko")]
1495    fn recompute_math_font_size_if_needed(context: &mut computed::Context) {
1496        use crate::values::generics::NonNegative;
1497
1498        // Do not do anything if font-size: math or math-depth is not set.
1499        if context.builder.get_font().clone_font_size().keyword_info.kw
1500            != specified::FontSizeKeyword::Math
1501        {
1502            return;
1503        }
1504
1505        const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71;
1506
1507        // Helper function that calculates the scale factor applied to font-size
1508        // when math-depth goes from parent_math_depth to computed_math_depth.
1509        // This function is essentially a modification of the MathML3's formula
1510        // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor
1511        // of parent_script_percent_scale_down is applied when math-depth goes
1512        // from 0 to 1 and parent_script_script_percent_scale_down is applied
1513        // when math-depth goes from 0 to 2. This is also a straightforward
1514        // implementation of the specification's algorithm:
1515        // https://w3c.github.io/mathml-core/#the-math-script-level-property
1516        fn scale_factor_for_math_depth_change(
1517            parent_math_depth: i32,
1518            computed_math_depth: i32,
1519            parent_script_percent_scale_down: Option<f32>,
1520            parent_script_script_percent_scale_down: Option<f32>,
1521        ) -> f32 {
1522            let mut a = parent_math_depth;
1523            let mut b = computed_math_depth;
1524            let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE;
1525            let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c);
1526            let scale_between_0_and_2 =
1527                parent_script_script_percent_scale_down.unwrap_or_else(|| c * c);
1528            let mut s = 1.0;
1529            let mut invert_scale_factor = false;
1530            if a == b {
1531                return s;
1532            }
1533            if b < a {
1534                std::mem::swap(&mut a, &mut b);
1535                invert_scale_factor = true;
1536            }
1537            let mut e = b - a;
1538            if a <= 0 && b >= 2 {
1539                s *= scale_between_0_and_2;
1540                e -= 2;
1541            } else if a == 1 {
1542                s *= scale_between_0_and_2 / scale_between_0_and_1;
1543                e -= 1;
1544            } else if b == 1 {
1545                s *= scale_between_0_and_1;
1546                e -= 1;
1547            }
1548            s *= (c as f32).powi(e);
1549            if invert_scale_factor {
1550                1.0 / s.max(f32::MIN_POSITIVE)
1551            } else {
1552                s
1553            }
1554        }
1555
1556        let (new_size, new_unconstrained_size) = {
1557            use crate::values::specified::font::QueryFontMetricsFlags;
1558
1559            let builder = &context.builder;
1560            let font = builder.get_font();
1561            let parent_font = builder.get_parent_font();
1562
1563            let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth);
1564
1565            if delta == 0 {
1566                return;
1567            }
1568
1569            let mut min = parent_font.mScriptMinSize;
1570            if font.mXTextScale.text_zoom_enabled() {
1571                min = builder.device.zoom_text(min);
1572            }
1573
1574            // Calculate scale factor following MathML Core's algorithm.
1575            let scale = {
1576                // Script scale factors are independent of orientation.
1577                let font_metrics = context.query_font_metrics(
1578                    FontBaseSize::InheritedStyle,
1579                    FontMetricsOrientation::Horizontal,
1580                    QueryFontMetricsFlags::NEEDS_MATH_SCALES,
1581                );
1582                scale_factor_for_math_depth_change(
1583                    parent_font.mMathDepth as i32,
1584                    font.mMathDepth as i32,
1585                    font_metrics.script_percent_scale_down,
1586                    font_metrics.script_script_percent_scale_down,
1587                )
1588            };
1589
1590            let parent_size = parent_font.mSize.0;
1591            let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0;
1592            let new_size = parent_size.scale_by(scale);
1593            let new_unconstrained_size = parent_unconstrained_size.scale_by(scale);
1594
1595            if scale <= 1. {
1596                // The parent size can be smaller than scriptminsize, e.g. if it
1597                // was specified explicitly. Don't scale in this case, but we
1598                // don't want to set it to scriptminsize either since that will
1599                // make it larger.
1600                if parent_size <= min {
1601                    (parent_size, new_unconstrained_size)
1602                } else {
1603                    (min.max(new_size), new_unconstrained_size)
1604                }
1605            } else {
1606                // If the new unconstrained size is larger than the min size,
1607                // this means we have escaped the grasp of scriptminsize and can
1608                // revert to using the unconstrained size.
1609                // However, if the new size is even larger (perhaps due to usage
1610                // of em units), use that instead.
1611                (
1612                    new_size.min(new_unconstrained_size.max(min)),
1613                    new_unconstrained_size,
1614                )
1615            }
1616        };
1617        let font = context.builder.mutate_font();
1618        font.mFont.size = NonNegative(new_size);
1619        font.mSize = NonNegative(new_size);
1620        font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size);
1621    }
1622
1623    /// Seeds `context.builder.substitution_functions` with the inherited custom properties and the
1624    /// registered initial values, before custom-property declarations are cascaded into it.
1625    fn init_custom_properties(&mut self, context: &mut computed::Context) {
1626        let is_root_element = context.is_root_element();
1627        let initial_values = self.stylist.get_custom_property_initial_values();
1628        let inherited = if is_root_element {
1629            debug_assert!(context.inherited_custom_properties().is_empty());
1630            initial_values.inherited.clone()
1631        } else {
1632            context.inherited_custom_properties().inherited.clone()
1633        };
1634        let properties = ComputedCustomProperties {
1635            inherited,
1636            non_inherited: initial_values.non_inherited.clone(),
1637        };
1638        context.builder.substitution_functions =
1639            ComputedSubstitutionFunctions::new(Some(properties), None);
1640    }
1641
1642    /// Resolves the custom properties and applies the prioritary properties in a single
1643    /// cycle-tracked walk: as a custom property depending on `em`/`lh`/the used color-scheme
1644    /// becomes resolvable, `substitute_all` applies the prioritary property it needs (via
1645    /// `ensure_prioritary_property`) so it computes against the right value. Any prioritary
1646    /// property not triggered that way is applied at the end.
1647    fn apply_custom_and_prioritary_properties(
1648        &mut self,
1649        context: &mut computed::Context,
1650        decls: &Declarations,
1651        shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
1652        attribute_tracker: &mut AttributeTracker,
1653    ) {
1654        if self.may_have_custom_property_cycles {
1655            let stylist = self.stylist;
1656            let seen = std::mem::take(&mut self.seen.custom);
1657            let references = std::mem::take(&mut self.references_from_non_custom_properties);
1658            substitute_all(
1659                &seen,
1660                &references,
1661                stylist,
1662                context,
1663                self,
1664                decls,
1665                shorthand_cache,
1666                attribute_tracker,
1667            );
1668        }
1669        // Apply any prioritary property that wasn't applied while resolving custom properties.
1670        if decls.has_prioritary_properties {
1671            for id in PrioritaryPropertyId::each() {
1672                self.ensure_prioritary_property(
1673                    context,
1674                    decls,
1675                    shorthand_cache,
1676                    attribute_tracker,
1677                    id,
1678                );
1679            }
1680        }
1681        self.finish_cascade_custom_properties(context);
1682    }
1683
1684    /// Applies a prioritary property and the prioritary properties it depends on.
1685    fn ensure_prioritary_property(
1686        &mut self,
1687        context: &mut computed::Context,
1688        decls: &Declarations,
1689        cache: &mut ShorthandsWithPropertyReferencesCache,
1690        attribute_tracker: &mut AttributeTracker,
1691        id: PrioritaryPropertyId,
1692    ) {
1693        if self.ensured_prioritary.contains(id) {
1694            return;
1695        }
1696        self.ensured_prioritary.insert(id);
1697        let deps = id.dependencies();
1698        if !self.ensured_prioritary.contains_all(deps) {
1699            for dep in deps.iter() {
1700                self.ensure_prioritary_property(context, decls, cache, attribute_tracker, dep);
1701            }
1702        }
1703        self.apply_one_prioritary_property(context, decls, cache, id, attribute_tracker);
1704    }
1705
1706    /// Cascade a given custom property declaration.
1707    fn cascade_custom_property(
1708        &mut self,
1709        context: &mut computed::Context,
1710        declaration: &'a CustomDeclaration,
1711        priority: CascadePriority,
1712    ) {
1713        let CustomDeclaration {
1714            ref name,
1715            ref value,
1716        } = *declaration;
1717
1718        if let Some(&(reverted_priority, revert_kind)) = self.reverted.custom.get(name) {
1719            if !reverted_priority.allows_when_reverted(&priority, revert_kind) {
1720                return;
1721            }
1722        }
1723
1724        if !(priority.flags() - context.included_cascade_flags).is_empty() {
1725            return;
1726        }
1727
1728        let entry = match self.seen.custom.var.entry(name) {
1729            Entry::Occupied(..) => return,
1730            Entry::Vacant(v) => v,
1731        };
1732
1733        let registration = self.stylist.get_custom_property_registration(&name);
1734        let initial_values = self.stylist.get_custom_property_initial_values();
1735        if !Self::value_may_affect_style(context, name, registration, initial_values, value) {
1736            entry.insert(false);
1737            return;
1738        }
1739
1740        let has_references = match value {
1741            CustomDeclarationValue::Unparsed(unparsed_value) => {
1742                // Non-custom dependency is really relevant for registered custom properties
1743                // that require computed value of such dependencies.
1744                unparsed_value
1745                    .references
1746                    .flags
1747                    .intersects(ReferenceFlags::ATTR | ReferenceFlags::VAR)
1748                    || !find_non_custom_references(
1749                        registration,
1750                        unparsed_value,
1751                        context.is_root_element(),
1752                    )
1753                    .is_empty()
1754            },
1755            // XXX This matches the previous behavior but I don't think it's fully sound. Parsed
1756            // values may have non-custom references, can't they? But also how do we get a parsed
1757            // value at all here?
1758            CustomDeclarationValue::Parsed(..) => false,
1759            // If we're a wide keyword we either get an initial value or an inherited one (no
1760            // references). Revert might have references, but we remove the `seen` entry, so it
1761            // doesn't matter.
1762            CustomDeclarationValue::CSSWideKeyword(..) => false,
1763        };
1764        self.may_have_custom_property_cycles |= has_references;
1765        entry.insert(has_references);
1766
1767        match value {
1768            CustomDeclarationValue::Unparsed(unparsed_value) => {
1769                if !has_references {
1770                    // If the variable value has no references to other properties, perform
1771                    // substitution here instead of forcing a full traversal in `substitute_all`
1772                    // afterwards.
1773                    substitute_references_if_needed_and_apply(
1774                        name,
1775                        SubstitutionFunctionKind::Var,
1776                        unparsed_value,
1777                        self.stylist,
1778                        context,
1779                        // We just checked there are no attr dependencies.
1780                        &mut AttributeTracker::new_dummy(),
1781                    );
1782                    return;
1783                }
1784                let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1785                context
1786                    .builder
1787                    .substitution_functions
1788                    .insert_var(registration, name, value);
1789            },
1790            CustomDeclarationValue::Parsed(parsed_value) => {
1791                let value = parsed_value.to_computed_value(&context);
1792                context
1793                    .builder
1794                    .substitution_functions
1795                    .insert_var(registration, name, value);
1796            },
1797            CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword.revert_kind() {
1798                Some(revert_kind) => {
1799                    self.seen.custom.var.remove(name);
1800                    self.reverted
1801                        .custom
1802                        .insert(name.clone(), (priority, revert_kind));
1803                },
1804                None => match keyword {
1805                    CSSWideKeyword::Initial => {
1806                        // For non-inherited custom properties, 'initial' was handled in value_may_affect_style.
1807                        debug_assert!(registration.inherits(), "Should've been handled earlier");
1808                        remove_and_insert_initial_value(
1809                            name,
1810                            registration,
1811                            &mut context.builder.substitution_functions,
1812                        );
1813                    },
1814                    CSSWideKeyword::Inherit => {
1815                        // For inherited custom properties, 'inherit' was handled in value_may_affect_style.
1816                        debug_assert!(!registration.inherits(), "Should've been handled earlier");
1817                        context
1818                            .style()
1819                            .add_flags(ComputedValueFlags::INHERITS_RESET_STYLE);
1820                        let inherited_value = context
1821                            .inherited_custom_properties()
1822                            .non_inherited
1823                            .get(name)
1824                            .cloned();
1825                        if let Some(inherited_value) = inherited_value {
1826                            context.builder.substitution_functions.insert_var(
1827                                registration,
1828                                name,
1829                                inherited_value,
1830                            );
1831                        }
1832                    },
1833                    // handled in value_may_affect_style or in the revert_kind branch above.
1834                    CSSWideKeyword::Revert
1835                    | CSSWideKeyword::RevertLayer
1836                    | CSSWideKeyword::RevertRule
1837                    | CSSWideKeyword::Unset => unreachable!(),
1838                },
1839            },
1840        }
1841    }
1842
1843    /// Fast check to avoid calling maybe_note_non_custom_dependency in ~all cases.
1844    #[inline]
1845    pub fn might_have_non_custom_or_attr_dependency(
1846        id: LonghandId,
1847        decl: &PropertyDeclaration,
1848    ) -> bool {
1849        if let PropertyDeclaration::WithVariables(v) = decl {
1850            return matches!(id, LonghandId::LineHeight | LonghandId::FontSize)
1851                || v.value
1852                    .variable_value
1853                    .references
1854                    .flags
1855                    .intersects(ReferenceFlags::ATTR);
1856        }
1857        false
1858    }
1859
1860    /// Note a non-custom property with variable reference that may in turn depend on that property.
1861    /// e.g. `font-size` depending on a custom property that may be a registered property using `em`.
1862    pub fn maybe_note_non_custom_dependency(
1863        &mut self,
1864        context: &mut computed::Context,
1865        id: LonghandId,
1866        decl: &'a PropertyDeclaration,
1867        attribute_tracker: &mut AttributeTracker,
1868    ) {
1869        debug_assert!(Self::might_have_non_custom_or_attr_dependency(id, decl));
1870        let PropertyDeclaration::WithVariables(v) = decl else {
1871            return;
1872        };
1873        let value = &v.value.variable_value;
1874        let refs = &value.references;
1875
1876        if !refs
1877            .flags
1878            .intersects(ReferenceFlags::VAR | ReferenceFlags::ATTR)
1879        {
1880            return;
1881        }
1882
1883        // Attributes in non-custom properties may reference `var()` or `attr()` in their
1884        // values, which we need to track to support chained references and detect cycles.
1885        if refs.flags.intersects(ReferenceFlags::ATTR) {
1886            self.update_attributes_map(context, value, attribute_tracker);
1887            if !refs.flags.intersects(ReferenceFlags::VAR) {
1888                return;
1889            }
1890        }
1891
1892        // The non-custom node(s) this property feeds. We don't try to figure out here which
1893        // referenced custom properties can actually cycle back to it: instead we record the whole
1894        // declaration value, and let `substitute_all` traverse its references (following fallbacks
1895        // only when the primary is invalid). This way, references that only appear in an unused
1896        // fallback (e.g. `var(--exists, var(--font-size-em))`) don't create a spurious cycle, while
1897        // references in a used fallback (e.g. `var(--noexist, var(--font-size-em))`) do.
1898        //
1899        // With unit algebra in `calc()`, references aren't limited to `font-size`. For example,
1900        // `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`, or
1901        // `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);`
1902        let references = match id {
1903            LonghandId::FontSize => ReferenceFlags::FONT_UNITS,
1904            LonghandId::LineHeight => ReferenceFlags::LH_UNITS | ReferenceFlags::FONT_UNITS,
1905            LonghandId::ColorScheme => ReferenceFlags::COLOR_SCHEME,
1906            _ => return,
1907        };
1908
1909        references.for_each_non_custom(context.is_root_element(), |idx| {
1910            self.references_from_non_custom_properties[idx]
1911                .get_or_insert_with(Vec::new)
1912                .push(v.value.clone());
1913        });
1914    }
1915
1916    fn value_may_affect_style(
1917        context: &computed::Context,
1918        name: &Name,
1919        registration: &PropertyDescriptors,
1920        initial_values: &ComputedCustomProperties,
1921        value: &CustomDeclarationValue,
1922    ) -> bool {
1923        match *value {
1924            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1925                // For inherited custom properties, explicit 'inherit' means we
1926                // can just use any existing value in the inherited
1927                // CustomPropertiesMap.
1928                if registration.inherits() {
1929                    return false;
1930                }
1931            },
1932            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1933                // For non-inherited custom properties, explicit 'initial' means
1934                // we can just use any initial value in the registration.
1935                if !registration.inherits() {
1936                    return false;
1937                }
1938            },
1939            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1940                // Explicit 'unset' means we can either just use any existing
1941                // value in the inherited CustomPropertiesMap or the initial
1942                // value in the registration.
1943                return false;
1944            },
1945            _ => {},
1946        }
1947
1948        let existing_value = context
1949            .builder
1950            .substitution_functions
1951            .get_var(registration, &name);
1952        let Some(existing_value) = existing_value else {
1953            if matches!(
1954                value,
1955                CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1956            ) {
1957                debug_assert!(registration.inherits(), "Should've been handled earlier");
1958                // The initial value of a custom property without a
1959                // guaranteed-invalid initial value is the same as it
1960                // not existing in the map.
1961                if registration.initial_value.is_none() {
1962                    return false;
1963                }
1964            }
1965            return true;
1966        };
1967        match value {
1968            CustomDeclarationValue::Unparsed(value) => {
1969                // Don't bother overwriting an existing value with the same
1970                // specified value.
1971                if let Some(existing_value) = existing_value.as_universal() {
1972                    return existing_value != value;
1973                }
1974            },
1975            CustomDeclarationValue::Parsed(..) => {
1976                // If the value has dependencies, context might not yield the same
1977                // result as the eventual value.
1978            },
1979            CustomDeclarationValue::CSSWideKeyword(kw) => {
1980                match kw {
1981                    CSSWideKeyword::Inherit => {
1982                        debug_assert!(!registration.inherits(), "Should've been handled earlier");
1983                        // existing_value is the registered initial value.
1984                        // Don't bother adding it to self.custom_properties.non_inherited
1985                        // if the key is also absent from self.inherited.non_inherited.
1986                        if context
1987                            .inherited_custom_properties()
1988                            .non_inherited
1989                            .get(name)
1990                            .is_none()
1991                        {
1992                            return false;
1993                        }
1994                    },
1995                    CSSWideKeyword::Initial => {
1996                        debug_assert!(registration.inherits(), "Should've been handled earlier");
1997                        // Don't bother overwriting an existing value with the initial value
1998                        // specified in the registration.
1999                        if let Some(initial_value) = initial_values.get(registration, name) {
2000                            return existing_value != initial_value;
2001                        }
2002                    },
2003                    CSSWideKeyword::Unset => {
2004                        debug_assert!(false, "Should've been handled earlier");
2005                    },
2006                    CSSWideKeyword::Revert
2007                    | CSSWideKeyword::RevertLayer
2008                    | CSSWideKeyword::RevertRule => {},
2009                }
2010            },
2011        };
2012
2013        true
2014    }
2015
2016    /// For a given unparsed variable, update the attributes map with its attr references.
2017    pub fn update_attributes_map(
2018        &mut self,
2019        context: &mut computed::Context,
2020        value: &'a VariableValue,
2021        attribute_tracker: &mut AttributeTracker,
2022    ) {
2023        let refs = &value.references;
2024        if !refs.flags.intersects(ReferenceFlags::ATTR) {
2025            return;
2026        }
2027        self.may_have_custom_property_cycles = true;
2028
2029        for next in &refs.refs {
2030            if !next.is_attr_with_type() || !self.seen.custom.attr.insert(&next.name) {
2031                // Only type() can have nested references, so we don't need to eagerly look at
2032                // others.
2033                continue;
2034            }
2035            if let Ok(v) = get_attr_value_for_cycle_resolution(
2036                &next.name,
2037                &next.attribute_data,
2038                &value.url_data,
2039                attribute_tracker,
2040            ) {
2041                context
2042                    .builder
2043                    .substitution_functions
2044                    .insert_attr(&next.name, v);
2045            }
2046        }
2047    }
2048
2049    /// Computes the map of applicable custom properties, saving the result into the computed
2050    /// context, and applies the prioritary properties interleaved with custom-property resolution.
2051    pub fn finish_cascade_custom_properties(&mut self, context: &mut computed::Context) {
2052        context
2053            .builder
2054            .substitution_functions
2055            .custom_properties
2056            .shrink_to_fit();
2057
2058        // Some pages apply a lot of redundant custom properties, see e.g.
2059        // bug 1758974 comment 5. Try to detect the case where the values
2060        // haven't really changed, and save some memory by reusing the inherited
2061        // map in that case.
2062        let initial_values = self.stylist.get_custom_property_initial_values();
2063        let reuse_inherited = context.inherited_custom_properties().inherited
2064            == context
2065                .builder
2066                .substitution_functions
2067                .custom_properties
2068                .inherited;
2069        if reuse_inherited {
2070            let inherited = context.inherited_custom_properties().inherited.clone();
2071            context
2072                .builder
2073                .substitution_functions
2074                .custom_properties
2075                .inherited = inherited;
2076        }
2077        if initial_values.non_inherited
2078            == context
2079                .builder
2080                .substitution_functions
2081                .custom_properties
2082                .non_inherited
2083        {
2084            let non_inherited = initial_values.non_inherited.clone();
2085            context
2086                .builder
2087                .substitution_functions
2088                .custom_properties
2089                .non_inherited = non_inherited;
2090        }
2091    }
2092}
2093
2094fn substitute_all(
2095    seen: &SeenSubstitutionFunctions,
2096    references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Arc<UnparsedValue>>>,
2097    stylist: &Stylist,
2098    computed_context: &mut computed::Context,
2099    cascade: &mut Cascade,
2100    decls: &Declarations,
2101    shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
2102    attr_tracker: &mut AttributeTracker,
2103) {
2104    // The cycle dependencies removal in this function is a variant
2105    // of Tarjan's algorithm. It is mostly based on the pseudo-code
2106    // listed in
2107    // https://en.wikipedia.org/w/index.php?
2108    // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495
2109
2110    #[derive(Clone, Eq, PartialEq, Debug)]
2111    enum VarType {
2112        Attr(Name),
2113        Custom(Name),
2114        NonCustom(SingleNonCustomReference),
2115    }
2116
2117    /// Struct recording necessary information for each variable.
2118    #[derive(Debug)]
2119    struct VarInfo {
2120        /// The name of the variable. It will be taken when the corresponding variable is popped
2121        /// from the stack, which serves as a mark for whether the variable is currently in the
2122        /// stack below.
2123        var: Option<VarType>,
2124        /// If the variable is in a dependency cycle, lowlink represents a smaller index which
2125        /// corresponds to a variable in the same strong connected component, which is known to be
2126        /// accessible from this variable. It is not necessarily the root, though.
2127        lowlink: usize,
2128    }
2129
2130    #[derive(Debug, Default)]
2131    struct OrderIndexMap {
2132        /// The map from the custom property name to its order index.
2133        var: PrecomputedHashMap<Name, usize>,
2134        /// The map from the attribute name to its order index.
2135        attr: PrecomputedHashMap<Name, usize>,
2136    }
2137
2138    impl OrderIndexMap {
2139        fn clear(&mut self) {
2140            self.var.clear();
2141            self.attr.clear();
2142        }
2143    }
2144
2145    /// Context struct for traversing the variable graph, so that we can
2146    /// avoid referencing all the fields multiple times.
2147    struct Context<'a, 'b: 'a, 'c, 'd> {
2148        /// Number of variables visited. This is used as the order index
2149        /// when we visit a new unresolved variable.
2150        count: usize,
2151        /// The map from a substitution function name to its order index.
2152        index_map: OrderIndexMap,
2153        /// Mapping from a non-custom dependency to its order index.
2154        non_custom_index_map: NonCustomReferenceMap<usize>,
2155        /// Information of each variable indexed by the order index.
2156        var_info: SmallVec<[VarInfo; 5]>,
2157        /// The stack of order index of visited variables. It contains
2158        /// all unfinished strong connected components.
2159        stack: SmallVec<[usize; 5]>,
2160        /// The stylist is used to get registered properties, and to resolve the environment to
2161        /// substitute `env()` variables.
2162        stylist: &'a Stylist,
2163        /// The computed context is used to get inherited custom properties, compute registered
2164        /// custom properties, and apply prioritary properties.
2165        computed_context: &'a mut computed::Context<'b>,
2166        /// The cascade owns prioritary-property application; when a `NonCustom` node (or a
2167        /// color-scheme-dependent custom property) becomes resolvable, we apply the corresponding
2168        /// prioritary property directly through it.
2169        cascade: &'a mut Cascade<'c>,
2170        /// The declarations, needed to apply prioritary properties.
2171        decls: &'a Declarations<'d>,
2172        /// Shorthand cache, needed to apply prioritary properties.
2173        cache: &'a mut ShorthandsWithPropertyReferencesCache,
2174    }
2175
2176    impl<'a, 'b: 'a, 'c, 'd> Context<'a, 'b, 'c, 'd> {
2177        fn reset(&mut self) {
2178            self.count = 0;
2179            self.index_map.clear();
2180            self.non_custom_index_map = Default::default();
2181            self.var_info.clear();
2182            self.stack.clear();
2183        }
2184
2185        fn map(&self) -> &ComputedSubstitutionFunctions {
2186            &self.computed_context.builder.substitution_functions
2187        }
2188
2189        fn map_mut(&mut self) -> &mut ComputedSubstitutionFunctions {
2190            &mut self.computed_context.builder.substitution_functions
2191        }
2192
2193        /// Marks a given `name` as being in a loop.
2194        fn handle_loop(&mut self, name: &VarType) {
2195            match name {
2196                VarType::Attr(name) => {
2197                    self.computed_context
2198                        .builder
2199                        .substitution_functions
2200                        .remove_attr(name);
2201                },
2202                VarType::Custom(name) => {
2203                    // This variable is in a loop. Resolve to invalid.
2204                    handle_invalid_at_computed_value_time(
2205                        name,
2206                        self.stylist.get_custom_property_registration(name),
2207                        self.computed_context,
2208                    );
2209                },
2210                VarType::NonCustom(non_custom) => {
2211                    self.computed_context
2212                        .builder
2213                        .invalid_non_custom_properties
2214                        .insert(non_custom.to_prioritary_id().to_longhand());
2215                },
2216            }
2217        }
2218
2219        /// Applies a prioritary property (and its dependencies) while resolving custom properties.
2220        ///
2221        /// The in-progress map lives outside `computed_context` during traversal; we move it into
2222        /// `computed_context.builder.substitution_functions` so the prioritary declaration's `var()`
2223        /// references resolve against the custom properties resolved so far, then take it back out.
2224        fn apply_prioritary_property(
2225            &mut self,
2226            id: PrioritaryPropertyId,
2227            attr_tracker: &mut AttributeTracker,
2228        ) {
2229            self.cascade.ensure_prioritary_property(
2230                self.computed_context,
2231                self.decls,
2232                self.cache,
2233                attr_tracker,
2234                id,
2235            );
2236        }
2237    }
2238
2239    /// Traverse the references in `root` (the value of a custom property or of a non-custom
2240    /// property feeding a `NonCustom` node), creating graph edges to the referenced substitution
2241    /// functions and updating `lowlink`/`self_ref` accordingly.
2242    ///
2243    /// We follow var()/attr()/env() fallbacks only when the primary substitution function is
2244    /// guaranteed-invalid (i.e. when the fallback is actually used). This matches the substitution
2245    /// order and the resolution of https://github.com/w3c/csswg-drafts/issues/11500: cycles (or
2246    /// dependencies) that only exist through an unused fallback don't count.
2247    ///
2248    /// We need to bubble up non custom references from unregistered properties.
2249    fn visit_value_references<'a, 'b, 'c, 'd>(
2250        var: &VarType,
2251        root: &References,
2252        url_data: &UrlExtraData,
2253        index: usize,
2254        references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Arc<UnparsedValue>>>,
2255        context: &mut Context<'a, 'b, 'c, 'd>,
2256        lowlink: &mut usize,
2257        self_ref: &mut bool,
2258        attribute_tracker: &mut AttributeTracker,
2259        non_custom_references: &mut ReferenceFlags,
2260    ) {
2261        // FIXME: Maybe avoid visiting the same var twice if not needed?
2262        let mut refs_stack = SmallVec::<[&References; 5]>::new();
2263        refs_stack.push(root);
2264        while let Some(refs) = refs_stack.pop() {
2265            *non_custom_references |= refs.flags;
2266            for next in &refs.refs {
2267                if next.substitution_kind == SubstitutionFunctionKind::Env {
2268                    // env() doesn't reference custom properties, so it never participates in
2269                    // cycles; its fallback is used only when the environment doesn't provide
2270                    // the variable.
2271                    let device = context.stylist.device();
2272                    let present = device
2273                        .environment()
2274                        .get(&next.name, device, url_data)
2275                        .is_some();
2276                    if !present {
2277                        if let Some(ref fallback) = next.fallback {
2278                            refs_stack.push(&fallback.references);
2279                        }
2280                    }
2281                    continue;
2282                }
2283
2284                let next_var = if next.substitution_kind == SubstitutionFunctionKind::Attr {
2285                    // An type()-less attr() within attr(... type()) can still have nested
2286                    // references.
2287                    let can_chain = next.is_attr_with_type() || matches!(var, VarType::Attr(..));
2288                    if !can_chain {
2289                        continue;
2290                    }
2291                    if context.map().get_attr(&next.name).is_none() {
2292                        if let Ok(val) = get_attr_value_for_cycle_resolution(
2293                            &next.name,
2294                            &next.attribute_data,
2295                            url_data,
2296                            attribute_tracker,
2297                        ) {
2298                            context.map_mut().insert_attr(&next.name, val);
2299                        }
2300                    }
2301                    VarType::Attr(next.name.clone())
2302                } else {
2303                    VarType::Custom(next.name.clone())
2304                };
2305
2306                visit_link(
2307                    next_var,
2308                    index,
2309                    references_from_non_custom_properties,
2310                    context,
2311                    lowlink,
2312                    self_ref,
2313                    attribute_tracker,
2314                );
2315
2316                // Now that the primary reference has been resolved, classify it to
2317                // decide whether its fallback is used.
2318                let kind = next.substitution_kind;
2319                let resolved = match kind {
2320                    SubstitutionFunctionKind::Var => {
2321                        let registration =
2322                            context.stylist.get_custom_property_registration(&next.name);
2323                        context.map().get_var(registration, &next.name)
2324                    },
2325                    SubstitutionFunctionKind::Attr => context.map().get_attr(&next.name),
2326                    SubstitutionFunctionKind::Env => unreachable!("Handled above"),
2327                };
2328                // The primary is guaranteed-invalid if it's absent from the map, or still
2329                // present but unresolved (i.e. part of a cycle currently being resolved).
2330                let mut primary_valid = false;
2331                if let Some(ref resolved) = resolved {
2332                    if let Some(v) = resolved.as_universal() {
2333                        primary_valid = !v.has_references();
2334                        *non_custom_references |= v.references.flags;
2335                    } else {
2336                        // Resolved and computed value, no other custom references left.
2337                        primary_valid = true;
2338                    }
2339                }
2340
2341                if !primary_valid {
2342                    if let Some(ref fallback) = next.fallback {
2343                        refs_stack.push(&fallback.references);
2344                    }
2345                }
2346            }
2347        }
2348    }
2349
2350    /// Traverse a single dependency `var` of the variable at order index `index`, updating its
2351    /// `lowlink`/`self_ref` from the result.
2352    fn visit_link<'a, 'b, 'c, 'd>(
2353        var: VarType,
2354        index: usize,
2355        non_custom_references: &NonCustomReferenceMap<Vec<Arc<UnparsedValue>>>,
2356        context: &mut Context<'a, 'b, 'c, 'd>,
2357        lowlink: &mut usize,
2358        self_ref: &mut bool,
2359        attr_tracker: &mut AttributeTracker,
2360    ) {
2361        let next_index = match traverse(var, non_custom_references, context, attr_tracker) {
2362            Some(index) => index,
2363            // There is nothing to do if the next variable has been
2364            // fully resolved at this point.
2365            None => return,
2366        };
2367        let next_info = &context.var_info[next_index];
2368        if next_index > index {
2369            // The next variable has a larger index than us, so it
2370            // must be inserted in the recursive call above. We want
2371            // to get its lowlink.
2372            *lowlink = cmp::min(*lowlink, next_info.lowlink);
2373        } else if next_index == index {
2374            *self_ref = true;
2375        } else if next_info.var.is_some() {
2376            // The next variable has a smaller order index and it is
2377            // in the stack, so we are at the same component.
2378            *lowlink = cmp::min(*lowlink, next_index);
2379        }
2380    }
2381
2382    /// This function combines the traversal for cycle removal and value
2383    /// substitution. It returns either a signal None if this variable
2384    /// has been fully resolved (to either having no reference or being
2385    /// marked invalid), or the order index for the given name.
2386    ///
2387    /// When it returns, the variable corresponds to the name would be
2388    /// in one of the following states:
2389    /// * It is still in context.stack, which means it is part of an
2390    ///   potentially incomplete dependency circle.
2391    /// * It has been removed from the map.  It can be either that the
2392    ///   substitution failed, or it is inside a dependency circle.
2393    ///   When this function removes a variable from the map because
2394    ///   of dependency circle, it would put all variables in the same
2395    ///   strong connected component to the set together.
2396    /// * It doesn't have any reference, because either this variable
2397    ///   doesn't have reference at all in specified value, or it has
2398    ///   been completely resolved.
2399    /// * There is no such variable at all.
2400    fn traverse<'a, 'b, 'c, 'd>(
2401        var: VarType,
2402        references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Arc<UnparsedValue>>>,
2403        context: &mut Context<'a, 'b, 'c, 'd>,
2404        attribute_tracker: &mut AttributeTracker,
2405    ) -> Option<usize> {
2406        // Some shortcut checks.
2407        // The non-custom (font/line-height/color-scheme) dependencies of this value, carried out
2408        // of the match so we can create the corresponding nodes once we've pushed this variable.
2409        let mut value_non_custom_refs = ReferenceFlags::empty();
2410        let mut registered = false;
2411        let value = match var {
2412            VarType::Custom(ref name) | VarType::Attr(ref name) => {
2413                let map = &context.computed_context.builder.substitution_functions;
2414                let (registration, value, kind) = if matches!(var, VarType::Custom(..)) {
2415                    let registration = context.stylist.get_custom_property_registration(name);
2416                    (
2417                        registration,
2418                        map.get_var(registration, name)?.as_universal()?,
2419                        SubstitutionFunctionKind::Var,
2420                    )
2421                } else {
2422                    // `attr()` is always treated as unregistered.
2423                    (
2424                        PropertyDescriptors::unregistered(),
2425                        map.get_attr(name)?.as_universal()?,
2426                        SubstitutionFunctionKind::Attr,
2427                    )
2428                };
2429                let is_root = context.computed_context.is_root_element();
2430                value_non_custom_refs = find_non_custom_references(registration, value, is_root);
2431                registered = !registration.is_universal();
2432                let has_dependency = value
2433                    .references
2434                    .flags
2435                    .intersects(ReferenceFlags::ATTR | ReferenceFlags::VAR)
2436                    || !value_non_custom_refs.is_empty();
2437                // Nothing to resolve.
2438                if !has_dependency {
2439                    debug_assert!(
2440                        !value.references.flags.intersects(ReferenceFlags::ENV),
2441                        "Should've been handled earlier"
2442                    );
2443                    if kind == SubstitutionFunctionKind::Attr || registered {
2444                        // We might still need to compute the value if this is not an universal
2445                        // registration but we thought it had a dependency during the cascade and it
2446                        // turned out not to. Note that if this was already computed we would've
2447                        // bailed out in the as_universal() check.
2448                        let value = value.clone();
2449                        substitute_references_if_needed_and_apply(
2450                            name,
2451                            kind,
2452                            &value,
2453                            context.stylist,
2454                            context.computed_context,
2455                            attribute_tracker,
2456                        );
2457                    }
2458                    return None;
2459                }
2460
2461                // Has this variable been visited?
2462                let index_map = if kind == SubstitutionFunctionKind::Var {
2463                    &mut context.index_map.var
2464                } else {
2465                    &mut context.index_map.attr
2466                };
2467                match index_map.entry(name.clone()) {
2468                    Entry::Occupied(entry) => {
2469                        return Some(*entry.get());
2470                    },
2471                    Entry::Vacant(entry) => {
2472                        entry.insert(context.count);
2473                    },
2474                }
2475                // Hold a strong reference to the value so that we don't
2476                // need to keep reference to context.map.
2477                Some(value.clone())
2478            },
2479            VarType::NonCustom(ref non_custom) => {
2480                let entry = &mut context.non_custom_index_map[*non_custom];
2481                if let Some(v) = entry {
2482                    return Some(*v);
2483                }
2484                *entry = Some(context.count);
2485                None
2486            },
2487        };
2488
2489        // Add new entry to the information table.
2490        let index = context.count;
2491        context.count += 1;
2492        debug_assert_eq!(index, context.var_info.len());
2493        context.var_info.push(VarInfo {
2494            var: Some(var.clone()),
2495            lowlink: index,
2496        });
2497        context.stack.push(index);
2498
2499        let mut self_ref = false;
2500        let mut lowlink = index;
2501        if let Some(ref v) = value.as_ref() {
2502            debug_assert!(
2503                matches!(var, VarType::Custom(_) | VarType::Attr(_)),
2504                "Non-custom property has references?"
2505            );
2506
2507            // Visit the references in this value...
2508            visit_value_references(
2509                &var,
2510                &v.references,
2511                &v.url_data,
2512                index,
2513                references_from_non_custom_properties,
2514                context,
2515                &mut lowlink,
2516                &mut self_ref,
2517                attribute_tracker,
2518                &mut value_non_custom_refs,
2519            );
2520
2521            // ... Then the non-custom properties this value depends on (font-size, line-height,
2522            // color-scheme), as computed by `find_non_custom_references` above.
2523            let is_root = context.computed_context.is_root_element();
2524            if registered && !value_non_custom_refs.is_empty() {
2525                value_non_custom_refs.for_each_non_custom(is_root, |r| {
2526                    visit_link(
2527                        VarType::NonCustom(r),
2528                        index,
2529                        references_from_non_custom_properties,
2530                        context,
2531                        &mut lowlink,
2532                        &mut self_ref,
2533                        attribute_tracker,
2534                    );
2535                });
2536            }
2537        } else if let VarType::NonCustom(non_custom) = var {
2538            // line-height's resolution depends on font-size's, so its node has a graph edge to the
2539            // font-size node. This is needed for cycle detection (not just application order, which
2540            // `prioritary_property_dependencies(LineHeight)` handles): it puts the line-height node
2541            // in the same strongly-connected component as a font-size<->custom cycle, so the node
2542            // doesn't apply line-height (and, transitively, font-size) before that cycle is
2543            // removed.
2544            //
2545            // TODO(emilio): I think the right fix for this is
2546            // s/SingleNonCustomReference/PrioritaryPropertyId in VarType::NonCustom.
2547            if non_custom == SingleNonCustomReference::LhUnits {
2548                visit_link(
2549                    VarType::NonCustom(SingleNonCustomReference::FontUnits),
2550                    index,
2551                    references_from_non_custom_properties,
2552                    context,
2553                    &mut lowlink,
2554                    &mut self_ref,
2555                    attribute_tracker,
2556                );
2557            }
2558            let entry = &references_from_non_custom_properties[non_custom];
2559            if let Some(values) = entry.as_ref() {
2560                for value in values {
2561                    // Traverse the non-custom property's declaration value(s), creating edges to
2562                    // the custom properties they reference (directly or through a used fallback)
2563                    // that might cycle back to this node. Note we traverse unregistered/universal
2564                    // references too: while their own font-relative units are textual (resolved
2565                    // against the parent, see `find_non_custom_references`), they may chain to a
2566                    // registered property that does cycle back.
2567                    let value = &value.variable_value;
2568                    visit_value_references(
2569                        &var,
2570                        &value.references,
2571                        &value.url_data,
2572                        index,
2573                        references_from_non_custom_properties,
2574                        context,
2575                        &mut lowlink,
2576                        &mut self_ref,
2577                        attribute_tracker,
2578                        &mut Default::default(),
2579                    );
2580                }
2581            }
2582        }
2583
2584        context.var_info[index].lowlink = lowlink;
2585        if lowlink != index {
2586            // This variable is in a loop, but it is not the root of this strong connected
2587            // component. We simply return for now, and the root would remove it from the map.
2588            //
2589            // This cannot be removed from the map here, because otherwise the shortcut check at the
2590            // beginning of this function would return the wrong value.
2591            return Some(index);
2592        }
2593
2594        // This is the root of a strong-connected component.
2595        let mut in_loop = self_ref;
2596        loop {
2597            let var_index = context
2598                .stack
2599                .pop()
2600                .expect("The current variable should still be in stack");
2601            let var_info = &mut context.var_info[var_index];
2602            // We should never visit the variable again, so it's safe to take the name away.
2603            let var_name = var_info
2604                .var
2605                .take()
2606                .expect("Variable should not be popped from stack twice");
2607            if var_index != index {
2608                // Anything here is in a loop which can traverse to the node we are handling, so
2609                // it's invalid at computed-value time.
2610                in_loop = true;
2611            }
2612            if in_loop {
2613                context.handle_loop(&var_name);
2614            }
2615            if var_index == index {
2616                debug_assert_eq!(var_name, var);
2617                break;
2618            }
2619        }
2620
2621        if in_loop {
2622            return None;
2623        }
2624
2625        // Not in a loop, apply the value.
2626        match var {
2627            VarType::Custom(ref name) | VarType::Attr(ref name) => {
2628                if let Some(ref v) = value {
2629                    let kind = if matches!(var, VarType::Custom(..)) {
2630                        SubstitutionFunctionKind::Var
2631                    } else {
2632                        SubstitutionFunctionKind::Attr
2633                    };
2634                    // We know we're not in a loop now, perform substitution.
2635                    // TODO(emilio): Merge with the cycle detection loop instead?
2636                    substitute_references_if_needed_and_apply(
2637                        name,
2638                        kind,
2639                        v,
2640                        context.stylist,
2641                        context.computed_context,
2642                        attribute_tracker,
2643                    );
2644                }
2645            },
2646            VarType::NonCustom(non_custom) => {
2647                context.apply_prioritary_property(non_custom.to_prioritary_id(), attribute_tracker);
2648            },
2649        }
2650        // All resolved, so return the signal value.
2651        None
2652    }
2653
2654    let mut context = Context {
2655        count: 0,
2656        index_map: OrderIndexMap::default(),
2657        non_custom_index_map: NonCustomReferenceMap::default(),
2658        stack: SmallVec::new(),
2659        var_info: SmallVec::new(),
2660        stylist,
2661        computed_context: &mut *computed_context,
2662        cascade: &mut *cascade,
2663        decls,
2664        cache: &mut *shorthand_cache,
2665    };
2666    let mut first = true;
2667    let mut run_one = |var: VarType| {
2668        if !first {
2669            context.reset();
2670        }
2671        first = false;
2672        traverse(
2673            var,
2674            references_from_non_custom_properties,
2675            &mut context,
2676            attr_tracker,
2677        );
2678    };
2679    // Note that `seen` doesn't contain names inherited from our parent, but
2680    // those can't have variable references (since we inherit the computed
2681    // variables) so we don't want to spend cycles traversing them anyway.
2682    for (var, has_refs) in &seen.var {
2683        if !has_refs {
2684            continue;
2685        }
2686        run_one(VarType::Custom((*var).clone()));
2687    }
2688    // Traverse potentially untraversed chained references from `attr(type())` in non-custom
2689    // properties.
2690    for attr in &seen.attr {
2691        run_one(VarType::Attr((*attr).clone()));
2692    }
2693}