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