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