Skip to main content

style/properties/
cascade.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The main cascading algorithm of the style system.
6
7use crate::applicable_declarations::CascadePriority;
8use crate::color::AbsoluteColor;
9use crate::computed_value_flags::ComputedValueFlags;
10use crate::custom_properties::{
11    CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution,
12};
13use crate::dom::{AttributeProvider, AttributeTracker, DummyAttributeProvider, TElement};
14#[cfg(feature = "gecko")]
15use crate::font_metrics::FontMetricsOrientation;
16use crate::logical_geometry::WritingMode;
17use crate::properties::{
18    property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, Importance,
19    LonghandId, LonghandIdSet, PrioritaryPropertyId, PropertyDeclaration, PropertyDeclarationId,
20    PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY,
21};
22use crate::rule_cache::{RuleCache, RuleCacheConditions};
23use crate::rule_tree::{CascadeLevel, StrongRuleNode};
24use crate::selector_parser::PseudoElement;
25use crate::shared_lock::StylesheetGuards;
26use crate::style_adjuster::StyleAdjuster;
27use crate::stylesheets::container_rule::ContainerSizeQuery;
28use crate::stylesheets::{layer_rule::LayerOrder, Origin};
29use crate::stylist::Stylist;
30#[cfg(feature = "gecko")]
31use crate::values::specified::length::FontBaseSize;
32use crate::values::specified::position::PositionTryFallbacksTryTactic;
33use crate::values::{computed, specified};
34use rustc_hash::FxHashMap;
35use servo_arc::Arc;
36use smallvec::SmallVec;
37use std::borrow::Cow;
38
39/// Whether we're resolving a style with the purposes of reparenting for ::first-line.
40#[derive(Copy, Clone)]
41#[allow(missing_docs)]
42pub enum FirstLineReparenting<'a> {
43    No,
44    Yes {
45        /// The style we're re-parenting for ::first-line. ::first-line only affects inherited
46        /// properties so we use this to avoid some work and also ensure correctness by copying the
47        /// reset structs from this style.
48        style_to_reparent: &'a ComputedValues,
49    },
50}
51
52/// Performs the CSS cascade, computing new styles for an element from its parent style.
53///
54/// The arguments are:
55///
56///   * `device`: Used to get the initial viewport and other external state.
57///
58///   * `rule_node`: The rule node in the tree that represent the CSS rules that
59///   matched.
60///
61///   * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
62///
63/// Returns the computed values.
64///   * `flags`: Various flags.
65///
66pub fn cascade<E>(
67    stylist: &Stylist,
68    pseudo: Option<&PseudoElement>,
69    rule_node: &StrongRuleNode,
70    guards: &StylesheetGuards,
71    parent_style: Option<&ComputedValues>,
72    layout_parent_style: Option<&ComputedValues>,
73    first_line_reparenting: FirstLineReparenting,
74    try_tactic: &PositionTryFallbacksTryTactic,
75    visited_rules: Option<&StrongRuleNode>,
76    cascade_input_flags: ComputedValueFlags,
77    rule_cache: Option<&RuleCache>,
78    rule_cache_conditions: &mut RuleCacheConditions,
79    element: Option<E>,
80) -> Arc<ComputedValues>
81where
82    E: TElement,
83{
84    cascade_rules(
85        stylist,
86        pseudo,
87        rule_node,
88        guards,
89        parent_style,
90        layout_parent_style,
91        first_line_reparenting,
92        try_tactic,
93        CascadeMode::Unvisited { visited_rules },
94        cascade_input_flags,
95        rule_cache,
96        rule_cache_conditions,
97        element,
98    )
99}
100
101struct DeclarationIterator<'a> {
102    // Global to the iteration.
103    guards: &'a StylesheetGuards<'a>,
104    restriction: Option<PropertyFlags>,
105    // The rule we're iterating over.
106    current_rule_node: Option<&'a StrongRuleNode>,
107    // Per rule state.
108    declarations: DeclarationImportanceIterator<'a>,
109    origin: Origin,
110    importance: Importance,
111    priority: CascadePriority,
112}
113
114impl<'a> DeclarationIterator<'a> {
115    #[inline]
116    fn new(
117        rule_node: &'a StrongRuleNode,
118        guards: &'a StylesheetGuards,
119        pseudo: Option<&PseudoElement>,
120    ) -> Self {
121        let restriction = pseudo.and_then(|p| p.property_restriction());
122        let mut iter = Self {
123            guards,
124            current_rule_node: Some(rule_node),
125            origin: Origin::UserAgent,
126            importance: Importance::Normal,
127            priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()),
128            declarations: DeclarationImportanceIterator::default(),
129            restriction,
130        };
131        iter.update_for_node(rule_node);
132        iter
133    }
134
135    fn update_for_node(&mut self, node: &'a StrongRuleNode) {
136        self.priority = node.cascade_priority();
137        let level = self.priority.cascade_level();
138        self.origin = level.origin();
139        self.importance = level.importance();
140        let guard = match self.origin {
141            Origin::Author => self.guards.author,
142            Origin::User | Origin::UserAgent => self.guards.ua_or_user,
143        };
144        self.declarations = match node.style_source() {
145            Some(source) => source.read(guard).declaration_importance_iter(),
146            None => DeclarationImportanceIterator::default(),
147        };
148    }
149}
150
151impl<'a> Iterator for DeclarationIterator<'a> {
152    type Item = (&'a PropertyDeclaration, CascadePriority);
153
154    #[inline]
155    fn next(&mut self) -> Option<Self::Item> {
156        loop {
157            if let Some((decl, importance)) = self.declarations.next_back() {
158                if self.importance != importance {
159                    continue;
160                }
161
162                if let Some(restriction) = self.restriction {
163                    // decl.id() is either a longhand or a custom
164                    // property.  Custom properties are always allowed, but
165                    // longhands are only allowed if they have our
166                    // restriction flag set.
167                    if let PropertyDeclarationId::Longhand(id) = decl.id() {
168                        if !id.flags().contains(restriction) && self.origin != Origin::UserAgent {
169                            continue;
170                        }
171                    }
172                }
173
174                return Some((decl, self.priority));
175            }
176
177            let next_node = self.current_rule_node.take()?.parent()?;
178            self.current_rule_node = Some(next_node);
179            self.update_for_node(next_node);
180        }
181    }
182}
183
184fn cascade_rules<E>(
185    stylist: &Stylist,
186    pseudo: Option<&PseudoElement>,
187    rule_node: &StrongRuleNode,
188    guards: &StylesheetGuards,
189    parent_style: Option<&ComputedValues>,
190    layout_parent_style: Option<&ComputedValues>,
191    first_line_reparenting: FirstLineReparenting,
192    try_tactic: &PositionTryFallbacksTryTactic,
193    cascade_mode: CascadeMode,
194    cascade_input_flags: ComputedValueFlags,
195    rule_cache: Option<&RuleCache>,
196    rule_cache_conditions: &mut RuleCacheConditions,
197    element: Option<E>,
198) -> Arc<ComputedValues>
199where
200    E: TElement,
201{
202    apply_declarations(
203        stylist,
204        pseudo,
205        rule_node,
206        guards,
207        DeclarationIterator::new(rule_node, guards, pseudo),
208        parent_style,
209        layout_parent_style,
210        first_line_reparenting,
211        try_tactic,
212        cascade_mode,
213        cascade_input_flags,
214        rule_cache,
215        rule_cache_conditions,
216        element,
217    )
218}
219
220/// Whether we're cascading for visited or unvisited styles.
221#[derive(Clone, Copy)]
222pub enum CascadeMode<'a, 'b> {
223    /// We're cascading for unvisited styles.
224    Unvisited {
225        /// The visited rules that should match the visited style.
226        visited_rules: Option<&'a StrongRuleNode>,
227    },
228    /// We're cascading for visited styles.
229    Visited {
230        /// The cascade for our unvisited style.
231        unvisited_context: &'a computed::Context<'b>,
232    },
233}
234
235fn iter_declarations<'builder, 'decls: 'builder>(
236    iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
237    declarations: &mut Declarations<'decls>,
238    mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>,
239    attribute_tracker: &mut AttributeTracker,
240) {
241    for (declaration, priority) in iter {
242        if let PropertyDeclaration::Custom(ref declaration) = *declaration {
243            if let Some(ref mut builder) = custom_builder {
244                builder.cascade(declaration, priority, attribute_tracker);
245            }
246        } else {
247            let id = declaration.id().as_longhand().unwrap();
248            declarations.note_declaration(declaration, priority, id);
249            if CustomPropertiesBuilder::might_have_non_custom_dependency(id, declaration) {
250                if let Some(ref mut builder) = custom_builder {
251                    builder.maybe_note_non_custom_dependency(id, declaration);
252                }
253            }
254        }
255    }
256}
257
258/// NOTE: This function expects the declaration with more priority to appear
259/// first.
260pub fn apply_declarations<'a, E, I>(
261    stylist: &'a Stylist,
262    pseudo: Option<&'a PseudoElement>,
263    rules: &StrongRuleNode,
264    guards: &StylesheetGuards,
265    iter: I,
266    parent_style: Option<&'a ComputedValues>,
267    layout_parent_style: Option<&ComputedValues>,
268    first_line_reparenting: FirstLineReparenting<'a>,
269    try_tactic: &'a PositionTryFallbacksTryTactic,
270    cascade_mode: CascadeMode,
271    cascade_input_flags: ComputedValueFlags,
272    rule_cache: Option<&'a RuleCache>,
273    rule_cache_conditions: &'a mut RuleCacheConditions,
274    element: Option<E>,
275) -> Arc<ComputedValues>
276where
277    E: TElement + 'a,
278    I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>,
279{
280    debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
281    let device = stylist.device();
282    let inherited_style = parent_style.unwrap_or(device.default_computed_values());
283    let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
284
285    let container_size_query =
286        ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some());
287
288    let mut context = computed::Context::new(
289        // We'd really like to own the rules here to avoid refcount traffic, but
290        // animation's usage of `apply_declarations` make this tricky. See bug
291        // 1375525.
292        StyleBuilder::new(
293            device,
294            Some(stylist),
295            parent_style,
296            pseudo,
297            Some(rules.clone()),
298            is_root_element,
299        ),
300        stylist.quirks_mode(),
301        rule_cache_conditions,
302        container_size_query,
303    );
304
305    context.style().add_flags(cascade_input_flags);
306
307    let using_cached_reset_properties;
308    let ignore_colors = context.builder.device.forced_colors().is_active();
309    let mut cascade = Cascade::new(first_line_reparenting, try_tactic, ignore_colors);
310    let mut declarations = Default::default();
311    let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
312    let attr_provider: &dyn AttributeProvider = match element {
313        Some(ref attr_provider) => attr_provider,
314        None => &DummyAttributeProvider {},
315    };
316    let mut attribute_tracker = AttributeTracker::new(attr_provider);
317    let properties_to_apply = match cascade_mode {
318        CascadeMode::Visited { unvisited_context } => {
319            context.builder.custom_properties = unvisited_context.builder.custom_properties.clone();
320            context.builder.writing_mode = unvisited_context.builder.writing_mode;
321            context.builder.color_scheme = unvisited_context.builder.color_scheme;
322            // We never insert visited styles into the cache so we don't need to try looking it up.
323            // It also wouldn't be super-profitable, only a handful :visited properties are
324            // non-inherited.
325            using_cached_reset_properties = false;
326            // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could
327            // try to avoid gathering the declarations. That'd be:
328            //      unvisited_context.builder.rules.as_ref() == Some(rules)
329            iter_declarations(iter, &mut declarations, None, &mut attribute_tracker);
330
331            LonghandIdSet::visited_dependent()
332        },
333        CascadeMode::Unvisited { visited_rules } => {
334            let deferred_custom_properties = {
335                let mut builder = CustomPropertiesBuilder::new(stylist, &mut context);
336                iter_declarations(
337                    iter,
338                    &mut declarations,
339                    Some(&mut builder),
340                    &mut attribute_tracker,
341                );
342                // Detect cycles, remove properties participating in them, and resolve properties, except:
343                // * Registered custom properties that depend on font-relative properties (Resolved)
344                //   when prioritary properties are resolved), and
345                // * Any property that, in turn, depend on properties like above.
346                builder.build(
347                    DeferFontRelativeCustomPropertyResolution::Yes,
348                    &mut attribute_tracker,
349                )
350            };
351
352            // Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom
353            // properties.
354            cascade.apply_prioritary_properties(
355                &mut context,
356                &declarations,
357                &mut shorthand_cache,
358                &mut attribute_tracker,
359            );
360
361            // Resolve the deferred custom properties.
362            if let Some(deferred) = deferred_custom_properties {
363                CustomPropertiesBuilder::build_deferred(
364                    deferred,
365                    stylist,
366                    &mut context,
367                    &mut attribute_tracker,
368                );
369            }
370
371            if let Some(visited_rules) = visited_rules {
372                cascade.compute_visited_style_if_needed(
373                    &mut context,
374                    element,
375                    parent_style,
376                    layout_parent_style,
377                    visited_rules,
378                    guards,
379                );
380            }
381
382            using_cached_reset_properties = cascade.try_to_use_cached_reset_properties(
383                &mut context.builder,
384                rule_cache,
385                guards,
386            );
387
388            if using_cached_reset_properties {
389                LonghandIdSet::late_group_only_inherited()
390            } else {
391                LonghandIdSet::late_group()
392            }
393        },
394    };
395
396    cascade.apply_non_prioritary_properties(
397        &mut context,
398        &declarations.longhand_declarations,
399        &mut shorthand_cache,
400        &properties_to_apply,
401        &mut attribute_tracker,
402    );
403
404    if !attribute_tracker.references.is_empty() {
405        context.builder.attribute_references = Some(attribute_tracker.finalize());
406    }
407
408    cascade.finished_applying_properties(&mut context.builder);
409
410    std::mem::drop(cascade);
411
412    context.builder.clear_modified_reset();
413
414    if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
415        StyleAdjuster::new(&mut context.builder).adjust(
416            layout_parent_style.unwrap_or(inherited_style),
417            element,
418            try_tactic,
419        );
420    }
421
422    if context.builder.modified_reset() || using_cached_reset_properties {
423        // If we adjusted any reset structs, we can't cache this ComputedValues.
424        //
425        // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside
426        // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be
427        // set appropriately if we didn't compute those reset properties.)
428        context.rule_cache_conditions.borrow_mut().set_uncacheable();
429    }
430
431    context.builder.build()
432}
433
434/// For ignored colors mode, we sometimes want to do something equivalent to
435/// "revert-or-initial", where we `revert` for a given origin, but then apply a
436/// given initial value if nothing in other origins did override it.
437///
438/// This is a bit of a clunky way of achieving this.
439type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>;
440
441fn tweak_when_ignoring_colors(
442    context: &computed::Context,
443    longhand_id: LonghandId,
444    origin: Origin,
445    declaration: &mut Cow<PropertyDeclaration>,
446    declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden,
447) {
448    use crate::values::computed::ToComputedValue;
449    use crate::values::specified::Color;
450
451    if !longhand_id.ignored_when_document_colors_disabled() {
452        return;
453    }
454
455    let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent);
456    if is_ua_or_user_rule {
457        return;
458    }
459
460    // Always honor colors if forced-color-adjust is set to none.
461    #[cfg(feature = "gecko")]
462    {
463        let forced = context
464            .builder
465            .get_inherited_text()
466            .clone_forced_color_adjust();
467        if forced == computed::ForcedColorAdjust::None {
468            return;
469        }
470    }
471
472    fn alpha_channel(color: &Color, context: &computed::Context) -> f32 {
473        // We assume here currentColor is opaque.
474        color
475            .to_computed_value(context)
476            .resolve_to_absolute(&AbsoluteColor::BLACK)
477            .alpha
478    }
479
480    // A few special-cases ahead.
481    match **declaration {
482        // Honor CSS-wide keywords like unset / revert / initial...
483        PropertyDeclaration::CSSWideKeyword(..) => return,
484        PropertyDeclaration::BackgroundColor(ref color) => {
485            // We honor system colors and transparent colors unconditionally.
486            //
487            // NOTE(emilio): We honor transparent unconditionally, like we do
488            // for color, even though it causes issues like bug 1625036. The
489            // reasoning is that the conditions that trigger that (having
490            // mismatched widget and default backgrounds) are both uncommon, and
491            // broken in other applications as well, and not honoring
492            // transparent makes stuff uglier or break unconditionally
493            // (bug 1666059, bug 1755713).
494            if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) {
495                return;
496            }
497            // For background-color, we revert or initial-with-preserved-alpha
498            // otherwise, this is needed to preserve semi-transparent
499            // backgrounds.
500            let alpha = alpha_channel(color, context);
501            if alpha == 0.0 {
502                return;
503            }
504            let mut color = context.builder.device.default_background_color();
505            color.alpha = alpha;
506            declarations_to_apply_unless_overridden
507                .push(PropertyDeclaration::BackgroundColor(color.into()))
508        },
509        PropertyDeclaration::Color(ref color) => {
510            // We honor color: transparent and system colors.
511            if color
512                .0
513                .honored_in_forced_colors_mode(/* allow_transparent = */ true)
514            {
515                return;
516            }
517            // If the inherited color would be transparent, but we would
518            // override this with a non-transparent color, then override it with
519            // the default color. Otherwise just let it inherit through.
520            if context
521                .builder
522                .get_parent_inherited_text()
523                .clone_color()
524                .alpha
525                == 0.0
526            {
527                let color = context.builder.device.default_color();
528                declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color(
529                    specified::ColorPropertyValue(color.into()),
530                ))
531            }
532        },
533        // We honor url background-images if backplating.
534        #[cfg(feature = "gecko")]
535        PropertyDeclaration::BackgroundImage(ref bkg) => {
536            use crate::values::generics::image::Image;
537            if static_prefs::pref!("browser.display.permit_backplate") {
538                if bkg
539                    .0
540                    .iter()
541                    .all(|image| matches!(*image, Image::Url(..) | Image::None))
542                {
543                    return;
544                }
545            }
546        },
547        _ => {
548            // We honor system colors more generally for all colors.
549            //
550            // We used to honor transparent but that causes accessibility
551            // regressions like bug 1740924.
552            //
553            // NOTE(emilio): This doesn't handle caret-color and accent-color
554            // because those use a slightly different syntax (<color> | auto for
555            // example).
556            //
557            // That's probably fine though, as using a system color for
558            // caret-color doesn't make sense (using currentColor is fine), and
559            // we ignore accent-color in high-contrast-mode anyways.
560            if let Some(color) = declaration.color_value() {
561                if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) {
562                    return;
563                }
564            }
565        },
566    }
567
568    *declaration.to_mut() =
569        PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert);
570}
571
572/// We track the index only for prioritary properties. For other properties we can just iterate.
573type DeclarationIndex = u16;
574
575/// "Prioritary" properties are properties that other properties depend on in one way or another.
576///
577/// We keep track of their position in the declaration vector, in order to be able to cascade them
578/// separately in precise order.
579#[derive(Copy, Clone)]
580struct PrioritaryDeclarationPosition {
581    // DeclarationIndex::MAX signals no index.
582    most_important: DeclarationIndex,
583    least_important: DeclarationIndex,
584}
585
586impl Default for PrioritaryDeclarationPosition {
587    fn default() -> Self {
588        Self {
589            most_important: DeclarationIndex::MAX,
590            least_important: DeclarationIndex::MAX,
591        }
592    }
593}
594
595#[derive(Copy, Clone)]
596struct Declaration<'a> {
597    decl: &'a PropertyDeclaration,
598    priority: CascadePriority,
599    next_index: DeclarationIndex,
600}
601
602/// The set of property declarations from our rules.
603#[derive(Default)]
604struct Declarations<'a> {
605    /// Whether we have any prioritary property. This is just a minor optimization.
606    has_prioritary_properties: bool,
607    /// A list of all the applicable longhand declarations.
608    longhand_declarations: SmallVec<[Declaration<'a>; 64]>,
609    /// The prioritary property position data.
610    prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY],
611}
612
613impl<'a> Declarations<'a> {
614    fn note_prioritary_property(&mut self, id: PrioritaryPropertyId) {
615        let new_index = self.longhand_declarations.len();
616        if new_index >= DeclarationIndex::MAX as usize {
617            // This prioritary property is past the amount of declarations we can track. Let's give
618            // up applying it to prevent getting confused.
619            return;
620        }
621
622        self.has_prioritary_properties = true;
623        let new_index = new_index as DeclarationIndex;
624        let position = &mut self.prioritary_positions[id as usize];
625        if position.most_important == DeclarationIndex::MAX {
626            // We still haven't seen this property, record the current position as the most
627            // prioritary index.
628            position.most_important = new_index;
629        } else {
630            // Let the previous item in the list know about us.
631            self.longhand_declarations[position.least_important as usize].next_index = new_index;
632        }
633        position.least_important = new_index;
634    }
635
636    fn note_declaration(
637        &mut self,
638        decl: &'a PropertyDeclaration,
639        priority: CascadePriority,
640        id: LonghandId,
641    ) {
642        if let Some(id) = PrioritaryPropertyId::from_longhand(id) {
643            self.note_prioritary_property(id);
644        }
645        self.longhand_declarations.push(Declaration {
646            decl,
647            priority,
648            next_index: 0,
649        });
650    }
651}
652
653struct Cascade<'b> {
654    first_line_reparenting: FirstLineReparenting<'b>,
655    try_tactic: &'b PositionTryFallbacksTryTactic,
656    ignore_colors: bool,
657    seen: LonghandIdSet,
658    author_specified: LonghandIdSet,
659    reverted_set: LonghandIdSet,
660    reverted: FxHashMap<LonghandId, (CascadePriority, bool)>,
661    declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden,
662}
663
664impl<'b> Cascade<'b> {
665    fn new(
666        first_line_reparenting: FirstLineReparenting<'b>,
667        try_tactic: &'b PositionTryFallbacksTryTactic,
668        ignore_colors: bool,
669    ) -> Self {
670        Self {
671            first_line_reparenting,
672            try_tactic,
673            ignore_colors,
674            seen: LonghandIdSet::default(),
675            author_specified: LonghandIdSet::default(),
676            reverted_set: Default::default(),
677            reverted: Default::default(),
678            declarations_to_apply_unless_overridden: Default::default(),
679        }
680    }
681
682    fn substitute_variables_if_needed<'cache, 'decl>(
683        &self,
684        context: &mut computed::Context,
685        shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
686        declaration: &'decl PropertyDeclaration,
687        attribute_tracker: &mut AttributeTracker,
688    ) -> Cow<'decl, PropertyDeclaration>
689    where
690        'cache: 'decl,
691    {
692        let declaration = match *declaration {
693            PropertyDeclaration::WithVariables(ref declaration) => declaration,
694            ref d => return Cow::Borrowed(d),
695        };
696
697        if !declaration.id.inherited() {
698            context.rule_cache_conditions.borrow_mut().set_uncacheable();
699
700            // NOTE(emilio): We only really need to add the `display` /
701            // `content` flag if the CSS variable has not been specified on our
702            // declarations, but we don't have that information at this point,
703            // and it doesn't seem like an important enough optimization to
704            // warrant it.
705            match declaration.id {
706                LonghandId::Display => {
707                    context
708                        .builder
709                        .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE);
710                },
711                LonghandId::Content => {
712                    context
713                        .builder
714                        .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE);
715                },
716                _ => {},
717            }
718        }
719
720        debug_assert!(
721            context.builder.stylist.is_some(),
722            "Need a Stylist to substitute variables!"
723        );
724        declaration.value.substitute_variables(
725            declaration.id,
726            context.builder.custom_properties(),
727            context.builder.stylist.unwrap(),
728            context,
729            shorthand_cache,
730            attribute_tracker,
731        )
732    }
733
734    fn apply_one_prioritary_property(
735        &mut self,
736        context: &mut computed::Context,
737        decls: &Declarations,
738        cache: &mut ShorthandsWithPropertyReferencesCache,
739        id: PrioritaryPropertyId,
740        attr_provider: &mut AttributeTracker,
741    ) -> bool {
742        let mut index = decls.prioritary_positions[id as usize].most_important;
743        if index == DeclarationIndex::MAX {
744            return false;
745        }
746
747        let longhand_id = id.to_longhand();
748        debug_assert!(
749            !longhand_id.is_logical(),
750            "That could require more book-keeping"
751        );
752        loop {
753            let decl = decls.longhand_declarations[index as usize];
754            self.apply_one_longhand(
755                context,
756                longhand_id,
757                decl.decl,
758                decl.priority,
759                cache,
760                attr_provider,
761            );
762            if self.seen.contains(longhand_id) {
763                return true; // Common case, we're done.
764            }
765            debug_assert!(
766                self.reverted_set.contains(longhand_id),
767                "How else can we fail to apply a prioritary property?"
768            );
769            debug_assert!(
770                decl.next_index == 0 || decl.next_index > index,
771                "should make progress! {} -> {}",
772                index,
773                decl.next_index,
774            );
775            index = decl.next_index;
776            if index == 0 {
777                break;
778            }
779        }
780        false
781    }
782
783    fn apply_prioritary_properties(
784        &mut self,
785        context: &mut computed::Context,
786        decls: &Declarations,
787        cache: &mut ShorthandsWithPropertyReferencesCache,
788        attribute_tracker: &mut AttributeTracker,
789    ) {
790        // Keeps apply_one_prioritary_property calls readable, considering the repititious
791        // arguments.
792        macro_rules! apply {
793            ($prop:ident) => {
794                self.apply_one_prioritary_property(
795                    context,
796                    decls,
797                    cache,
798                    PrioritaryPropertyId::$prop,
799                    attribute_tracker,
800                )
801            };
802        }
803
804        if !decls.has_prioritary_properties {
805            return;
806        }
807
808        let has_writing_mode = apply!(WritingMode) | apply!(Direction);
809        #[cfg(feature = "gecko")]
810        let has_writing_mode = has_writing_mode | apply!(TextOrientation);
811
812        if has_writing_mode {
813            context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box())
814        }
815
816        if apply!(Zoom) {
817            context.builder.recompute_effective_zooms();
818            if !context.builder.effective_zoom_for_inheritance.is_one() {
819                // NOTE(emilio): This is a bit of a hack, but matches the shipped WebKit and Blink
820                // behavior for now. Ideally, in the future, we have a pass over all
821                // implicitly-or-explicitly-inherited properties that can contain lengths and
822                // re-compute them properly, see https://github.com/w3c/csswg-drafts/issues/9397.
823                // TODO(emilio): we need to eagerly do this for line-height as well, probably.
824                self.recompute_font_size_for_zoom_change(&mut context.builder);
825            }
826        }
827
828        // Compute font-family.
829        let has_font_family = apply!(FontFamily);
830        let has_lang = apply!(XLang);
831        #[cfg(feature = "gecko")]
832        {
833            if has_lang {
834                self.recompute_initial_font_family_if_needed(&mut context.builder);
835            }
836            if has_font_family {
837                self.prioritize_user_fonts_if_needed(&mut context.builder);
838            }
839
840            // Compute font-size.
841            if apply!(XTextScale) {
842                self.unzoom_fonts_if_needed(&mut context.builder);
843            }
844            let has_font_size = apply!(FontSize);
845            let has_math_depth = apply!(MathDepth);
846            let has_min_font_size_ratio = apply!(MozMinFontSizeRatio);
847
848            if has_math_depth && has_font_size {
849                self.recompute_math_font_size_if_needed(context);
850            }
851            if has_lang || has_font_family {
852                self.recompute_keyword_font_size_if_needed(context);
853            }
854            if has_font_size || has_min_font_size_ratio || has_lang || has_font_family {
855                self.constrain_font_size_if_needed(&mut context.builder);
856            }
857        }
858
859        #[cfg(feature = "servo")]
860        {
861            apply!(FontSize);
862            if has_lang || has_font_family {
863                self.recompute_keyword_font_size_if_needed(context);
864            }
865        }
866
867        // Compute the rest of the first-available-font-affecting properties.
868        apply!(FontWeight);
869        apply!(FontStretch);
870        apply!(FontStyle);
871        #[cfg(feature = "gecko")]
872        apply!(FontSizeAdjust);
873
874        #[cfg(feature = "gecko")]
875        apply!(ForcedColorAdjust);
876        // color-scheme needs to be after forced-color-adjust, since it's one of the "skipped in
877        // forced-colors-mode" properties.
878        if apply!(ColorScheme) {
879            context.builder.color_scheme = context.builder.get_inherited_ui().color_scheme_bits();
880        }
881        apply!(LineHeight);
882    }
883
884    fn apply_non_prioritary_properties(
885        &mut self,
886        context: &mut computed::Context,
887        longhand_declarations: &[Declaration],
888        shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
889        properties_to_apply: &LonghandIdSet,
890        attribute_tracker: &mut AttributeTracker,
891    ) {
892        debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties()));
893        debug_assert!(self.declarations_to_apply_unless_overridden.is_empty());
894        for declaration in &*longhand_declarations {
895            let mut longhand_id = declaration.decl.id().as_longhand().unwrap();
896            if !properties_to_apply.contains(longhand_id) {
897                continue;
898            }
899            debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none());
900            let is_logical = longhand_id.is_logical();
901            if is_logical {
902                let wm = context.builder.writing_mode;
903                context
904                    .rule_cache_conditions
905                    .borrow_mut()
906                    .set_writing_mode_dependency(wm);
907                longhand_id = longhand_id.to_physical(wm);
908            }
909            self.apply_one_longhand(
910                context,
911                longhand_id,
912                declaration.decl,
913                declaration.priority,
914                shorthand_cache,
915                attribute_tracker,
916            );
917        }
918        if !self.declarations_to_apply_unless_overridden.is_empty() {
919            debug_assert!(self.ignore_colors);
920            for declaration in std::mem::take(&mut self.declarations_to_apply_unless_overridden) {
921                let longhand_id = declaration.id().as_longhand().unwrap();
922                debug_assert!(!longhand_id.is_logical());
923                if !self.seen.contains(longhand_id) {
924                    unsafe {
925                        self.do_apply_declaration(context, longhand_id, &declaration);
926                    }
927                }
928            }
929        }
930
931        if !context.builder.effective_zoom_for_inheritance.is_one() {
932            self.recompute_zoom_dependent_inherited_lengths(context);
933        }
934    }
935
936    #[cold]
937    fn recompute_zoom_dependent_inherited_lengths(&self, context: &mut computed::Context) {
938        debug_assert!(self.seen.contains(LonghandId::Zoom));
939        for prop in LonghandIdSet::zoom_dependent_inherited_properties().iter() {
940            if self.seen.contains(prop) {
941                continue;
942            }
943            let declaration = PropertyDeclaration::css_wide_keyword(prop, CSSWideKeyword::Inherit);
944            unsafe {
945                self.do_apply_declaration(context, prop, &declaration);
946            }
947        }
948    }
949
950    fn apply_one_longhand(
951        &mut self,
952        context: &mut computed::Context,
953        longhand_id: LonghandId,
954        declaration: &PropertyDeclaration,
955        priority: CascadePriority,
956        cache: &mut ShorthandsWithPropertyReferencesCache,
957        attribute_tracker: &mut AttributeTracker,
958    ) {
959        debug_assert!(!longhand_id.is_logical());
960        let origin = priority.cascade_level().origin();
961        if self.seen.contains(longhand_id) {
962            return;
963        }
964
965        if self.reverted_set.contains(longhand_id) {
966            if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&longhand_id) {
967                if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
968                    return;
969                }
970            }
971        }
972
973        let mut declaration =
974            self.substitute_variables_if_needed(context, cache, declaration, attribute_tracker);
975
976        // When document colors are disabled, do special handling of
977        // properties that are marked as ignored in that mode.
978        if self.ignore_colors {
979            tweak_when_ignoring_colors(
980                context,
981                longhand_id,
982                origin,
983                &mut declaration,
984                &mut self.declarations_to_apply_unless_overridden,
985            );
986        }
987        let can_skip_apply = match declaration.get_css_wide_keyword() {
988            Some(keyword) => {
989                if matches!(
990                    keyword,
991                    CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert
992                ) {
993                    let origin_revert = keyword == CSSWideKeyword::Revert;
994                    // We intentionally don't want to insert it into `self.seen`, `reverted` takes
995                    // care of rejecting other declarations as needed.
996                    self.reverted_set.insert(longhand_id);
997                    self.reverted.insert(longhand_id, (priority, origin_revert));
998                    return;
999                }
1000
1001                let inherited = longhand_id.inherited();
1002                let zoomed = !context.builder.effective_zoom_for_inheritance.is_one()
1003                    && longhand_id.zoom_dependent();
1004                match keyword {
1005                    CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => unreachable!(),
1006                    CSSWideKeyword::Unset => !zoomed || !inherited,
1007                    CSSWideKeyword::Inherit => inherited && !zoomed,
1008                    CSSWideKeyword::Initial => !inherited,
1009                }
1010            },
1011            None => false,
1012        };
1013
1014        self.seen.insert(longhand_id);
1015        if origin == Origin::Author {
1016            self.author_specified.insert(longhand_id);
1017        }
1018
1019        if !can_skip_apply {
1020            // Set context.scope to this declaration's cascade level so that
1021            // tree-scoped properties (anchor-name, position-anchor, anchor-scope)
1022            // get the correct scope when converted to computed values.
1023            let old_scope = context.scope;
1024            let cascade_level = priority.cascade_level();
1025            context.scope = cascade_level;
1026            unsafe { self.do_apply_declaration(context, longhand_id, &declaration) }
1027            context.scope = old_scope;
1028        }
1029    }
1030
1031    #[inline]
1032    unsafe fn do_apply_declaration(
1033        &self,
1034        context: &mut computed::Context,
1035        longhand_id: LonghandId,
1036        declaration: &PropertyDeclaration,
1037    ) {
1038        debug_assert!(!longhand_id.is_logical());
1039        // We could (and used to) use a pattern match here, but that bloats this
1040        // function to over 100K of compiled code!
1041        //
1042        // To improve i-cache behavior, we outline the individual functions and
1043        // use virtual dispatch instead.
1044        (CASCADE_PROPERTY[longhand_id as usize])(&declaration, context);
1045    }
1046
1047    fn compute_visited_style_if_needed<E>(
1048        &self,
1049        context: &mut computed::Context,
1050        element: Option<E>,
1051        parent_style: Option<&ComputedValues>,
1052        layout_parent_style: Option<&ComputedValues>,
1053        visited_rules: &StrongRuleNode,
1054        guards: &StylesheetGuards,
1055    ) where
1056        E: TElement,
1057    {
1058        let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link();
1059
1060        macro_rules! visited_parent {
1061            ($parent:expr) => {
1062                if is_link {
1063                    $parent
1064                } else {
1065                    $parent.map(|p| p.visited_style().unwrap_or(p))
1066                }
1067            };
1068        }
1069
1070        // We could call apply_declarations directly, but that'd cause
1071        // another instantiation of this function which is not great.
1072        let style = cascade_rules(
1073            context.builder.stylist.unwrap(),
1074            context.builder.pseudo,
1075            visited_rules,
1076            guards,
1077            visited_parent!(parent_style),
1078            visited_parent!(layout_parent_style),
1079            self.first_line_reparenting,
1080            self.try_tactic,
1081            CascadeMode::Visited {
1082                unvisited_context: &*context,
1083            },
1084            // Cascade input flags don't matter for the visited style, they are
1085            // in the main (unvisited) style.
1086            Default::default(),
1087            // The rule cache doesn't care about caching :visited
1088            // styles, we cache the unvisited style instead. We still do
1089            // need to set the caching dependencies properly if present
1090            // though, so the cache conditions need to match.
1091            None, // rule_cache
1092            &mut *context.rule_cache_conditions.borrow_mut(),
1093            element,
1094        );
1095        context.builder.visited_style = Some(style);
1096    }
1097
1098    fn finished_applying_properties(&self, builder: &mut StyleBuilder) {
1099        #[cfg(feature = "gecko")]
1100        {
1101            if let Some(bg) = builder.get_background_if_mutated() {
1102                bg.fill_arrays();
1103            }
1104
1105            if let Some(svg) = builder.get_svg_if_mutated() {
1106                svg.fill_arrays();
1107            }
1108        }
1109
1110        if self
1111            .author_specified
1112            .contains_any(LonghandIdSet::border_background_properties())
1113        {
1114            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
1115        }
1116
1117        if self.author_specified.contains(LonghandId::FontFamily) {
1118            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY);
1119        }
1120
1121        if self.author_specified.contains(LonghandId::Color) {
1122            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_COLOR);
1123        }
1124
1125        if self.author_specified.contains(LonghandId::TextShadow) {
1126            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_SHADOW);
1127        }
1128
1129        if self.author_specified.contains(LonghandId::LetterSpacing) {
1130            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING);
1131        }
1132
1133        if self.author_specified.contains(LonghandId::WordSpacing) {
1134            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING);
1135        }
1136
1137        if self
1138            .author_specified
1139            .contains(LonghandId::FontSynthesisWeight)
1140        {
1141            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT);
1142        }
1143
1144        #[cfg(feature = "gecko")]
1145        if self
1146            .author_specified
1147            .contains(LonghandId::FontSynthesisStyle)
1148        {
1149            builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE);
1150        }
1151
1152        #[cfg(feature = "servo")]
1153        {
1154            if let Some(font) = builder.get_font_if_mutated() {
1155                font.compute_font_hash();
1156            }
1157        }
1158    }
1159
1160    fn try_to_use_cached_reset_properties(
1161        &self,
1162        builder: &mut StyleBuilder<'b>,
1163        cache: Option<&'b RuleCache>,
1164        guards: &StylesheetGuards,
1165    ) -> bool {
1166        let style = match self.first_line_reparenting {
1167            FirstLineReparenting::Yes { style_to_reparent } => style_to_reparent,
1168            FirstLineReparenting::No => {
1169                let Some(cache) = cache else { return false };
1170                let Some(style) = cache.find(guards, builder) else {
1171                    return false;
1172                };
1173                style
1174            },
1175        };
1176
1177        builder.copy_reset_from(style);
1178
1179        // We're using the same reset style as another element, and we'll skip
1180        // applying the relevant properties. So we need to do the relevant
1181        // bookkeeping here to keep these bits correct.
1182        //
1183        // Note that the border/background properties are non-inherited, so we
1184        // don't need to do anything else other than just copying the bits over.
1185        //
1186        // When using this optimization, we also need to copy whether the old
1187        // style specified viewport units / used font-relative lengths, this one
1188        // would as well.  It matches the same rules, so it is the right thing
1189        // to do anyways, even if it's only used on inherited properties.
1190        let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND
1191            | ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS
1192            | ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS
1193            | ComputedValueFlags::USES_CONTAINER_UNITS
1194            | ComputedValueFlags::USES_VIEWPORT_UNITS;
1195        builder.add_flags(style.flags & bits_to_copy);
1196
1197        true
1198    }
1199
1200    /// The initial font depends on the current lang group so we may need to
1201    /// recompute it if the language changed.
1202    #[inline]
1203    #[cfg(feature = "gecko")]
1204    fn recompute_initial_font_family_if_needed(&self, builder: &mut StyleBuilder) {
1205        use crate::gecko_bindings::bindings;
1206        use crate::values::computed::font::FontFamily;
1207
1208        let default_font_type = {
1209            let font = builder.get_font();
1210
1211            if !font.mFont.family.is_initial {
1212                return;
1213            }
1214
1215            let default_font_type = unsafe {
1216                bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
1217                    builder.device.document(),
1218                    font.mLanguage.mRawPtr,
1219                )
1220            };
1221
1222            let initial_generic = font.mFont.family.families.single_generic();
1223            debug_assert!(
1224                initial_generic.is_some(),
1225                "Initial font should be just one generic font"
1226            );
1227            if initial_generic == Some(default_font_type) {
1228                return;
1229            }
1230
1231            default_font_type
1232        };
1233
1234        // NOTE: Leaves is_initial untouched.
1235        builder.mutate_font().mFont.family.families =
1236            FontFamily::generic(default_font_type).families.clone();
1237    }
1238
1239    /// Prioritize user fonts if needed by pref.
1240    #[inline]
1241    #[cfg(feature = "gecko")]
1242    fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) {
1243        use crate::gecko_bindings::bindings;
1244
1245        // Check the use_document_fonts setting for content, but for chrome
1246        // documents they're treated as always enabled.
1247        if static_prefs::pref!("browser.display.use_document_fonts") != 0
1248            || builder.device.chrome_rules_enabled_for_document()
1249        {
1250            return;
1251        }
1252
1253        let default_font_type = {
1254            let font = builder.get_font();
1255
1256            if font.mFont.family.is_system_font {
1257                return;
1258            }
1259
1260            if !font.mFont.family.families.needs_user_font_prioritization() {
1261                return;
1262            }
1263
1264            unsafe {
1265                bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
1266                    builder.device.document(),
1267                    font.mLanguage.mRawPtr,
1268                )
1269            }
1270        };
1271
1272        let font = builder.mutate_font();
1273        font.mFont
1274            .family
1275            .families
1276            .prioritize_first_generic_or_prepend(default_font_type);
1277    }
1278
1279    /// Some keyword sizes depend on the font family and language.
1280    fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) {
1281        use crate::values::computed::ToComputedValue;
1282
1283        if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) {
1284            return;
1285        }
1286
1287        let new_size = {
1288            let font = context.builder.get_font();
1289            let info = font.clone_font_size().keyword_info;
1290            let new_size = match info.kw {
1291                specified::FontSizeKeyword::None => return,
1292                _ => {
1293                    context.for_non_inherited_property = false;
1294                    specified::FontSize::Keyword(info).to_computed_value(context)
1295                },
1296            };
1297
1298            #[cfg(feature = "gecko")]
1299            if font.mScriptUnconstrainedSize == new_size.computed_size {
1300                return;
1301            }
1302
1303            new_size
1304        };
1305
1306        context.builder.mutate_font().set_font_size(new_size);
1307    }
1308
1309    /// Some properties, plus setting font-size itself, may make us go out of
1310    /// our minimum font-size range.
1311    #[cfg(feature = "gecko")]
1312    fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) {
1313        use crate::gecko_bindings::bindings;
1314        use crate::values::generics::NonNegative;
1315
1316        let min_font_size = {
1317            let font = builder.get_font();
1318            let min_font_size = unsafe {
1319                bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document())
1320            };
1321
1322            if font.mFont.size.0 >= min_font_size {
1323                return;
1324            }
1325
1326            NonNegative(min_font_size)
1327        };
1328
1329        builder.mutate_font().mFont.size = min_font_size;
1330    }
1331
1332    /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up
1333    /// the struct when this happens by unzooming its contained font values, which will have been
1334    /// zoomed in the parent.
1335    #[cfg(feature = "gecko")]
1336    fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) {
1337        debug_assert!(self.seen.contains(LonghandId::XTextScale));
1338
1339        let parent_text_scale = builder.get_parent_font().clone__x_text_scale();
1340        let text_scale = builder.get_font().clone__x_text_scale();
1341        if parent_text_scale == text_scale {
1342            return;
1343        }
1344        debug_assert_ne!(
1345            parent_text_scale.text_zoom_enabled(),
1346            text_scale.text_zoom_enabled(),
1347            "There's only one value that disables it"
1348        );
1349        debug_assert!(
1350            !text_scale.text_zoom_enabled(),
1351            "We only ever disable text zoom never enable it"
1352        );
1353        let device = builder.device;
1354        builder.mutate_font().unzoom_fonts(device);
1355    }
1356
1357    fn recompute_font_size_for_zoom_change(&self, builder: &mut StyleBuilder) {
1358        debug_assert!(self.seen.contains(LonghandId::Zoom));
1359        // NOTE(emilio): Intentionally not using the effective zoom here, since all the inherited
1360        // zooms are already applied.
1361        let old_size = builder.get_font().clone_font_size();
1362        let new_size = old_size.zoom(builder.effective_zoom_for_inheritance);
1363        if old_size == new_size {
1364            return;
1365        }
1366        builder.mutate_font().set_font_size(new_size);
1367    }
1368
1369    /// Special handling of font-size: math (used for MathML).
1370    /// https://w3c.github.io/mathml-core/#the-math-script-level-property
1371    /// TODO: Bug: 1548471: MathML Core also does not specify a script min size
1372    /// should we unship that feature or standardize it?
1373    #[cfg(feature = "gecko")]
1374    fn recompute_math_font_size_if_needed(&self, context: &mut computed::Context) {
1375        use crate::values::generics::NonNegative;
1376
1377        // Do not do anything if font-size: math or math-depth is not set.
1378        if context.builder.get_font().clone_font_size().keyword_info.kw
1379            != specified::FontSizeKeyword::Math
1380        {
1381            return;
1382        }
1383
1384        const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71;
1385
1386        // Helper function that calculates the scale factor applied to font-size
1387        // when math-depth goes from parent_math_depth to computed_math_depth.
1388        // This function is essentially a modification of the MathML3's formula
1389        // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor
1390        // of parent_script_percent_scale_down is applied when math-depth goes
1391        // from 0 to 1 and parent_script_script_percent_scale_down is applied
1392        // when math-depth goes from 0 to 2. This is also a straightforward
1393        // implementation of the specification's algorithm:
1394        // https://w3c.github.io/mathml-core/#the-math-script-level-property
1395        fn scale_factor_for_math_depth_change(
1396            parent_math_depth: i32,
1397            computed_math_depth: i32,
1398            parent_script_percent_scale_down: Option<f32>,
1399            parent_script_script_percent_scale_down: Option<f32>,
1400        ) -> f32 {
1401            let mut a = parent_math_depth;
1402            let mut b = computed_math_depth;
1403            let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE;
1404            let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c);
1405            let scale_between_0_and_2 =
1406                parent_script_script_percent_scale_down.unwrap_or_else(|| c * c);
1407            let mut s = 1.0;
1408            let mut invert_scale_factor = false;
1409            if a == b {
1410                return s;
1411            }
1412            if b < a {
1413                std::mem::swap(&mut a, &mut b);
1414                invert_scale_factor = true;
1415            }
1416            let mut e = b - a;
1417            if a <= 0 && b >= 2 {
1418                s *= scale_between_0_and_2;
1419                e -= 2;
1420            } else if a == 1 {
1421                s *= scale_between_0_and_2 / scale_between_0_and_1;
1422                e -= 1;
1423            } else if b == 1 {
1424                s *= scale_between_0_and_1;
1425                e -= 1;
1426            }
1427            s *= (c as f32).powi(e);
1428            if invert_scale_factor {
1429                1.0 / s.max(f32::MIN_POSITIVE)
1430            } else {
1431                s
1432            }
1433        }
1434
1435        let (new_size, new_unconstrained_size) = {
1436            use crate::values::specified::font::QueryFontMetricsFlags;
1437
1438            let builder = &context.builder;
1439            let font = builder.get_font();
1440            let parent_font = builder.get_parent_font();
1441
1442            let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth);
1443
1444            if delta == 0 {
1445                return;
1446            }
1447
1448            let mut min = parent_font.mScriptMinSize;
1449            if font.mXTextScale.text_zoom_enabled() {
1450                min = builder.device.zoom_text(min);
1451            }
1452
1453            // Calculate scale factor following MathML Core's algorithm.
1454            let scale = {
1455                // Script scale factors are independent of orientation.
1456                let font_metrics = context.query_font_metrics(
1457                    FontBaseSize::InheritedStyle,
1458                    FontMetricsOrientation::Horizontal,
1459                    QueryFontMetricsFlags::NEEDS_MATH_SCALES,
1460                );
1461                scale_factor_for_math_depth_change(
1462                    parent_font.mMathDepth as i32,
1463                    font.mMathDepth as i32,
1464                    font_metrics.script_percent_scale_down,
1465                    font_metrics.script_script_percent_scale_down,
1466                )
1467            };
1468
1469            let parent_size = parent_font.mSize.0;
1470            let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0;
1471            let new_size = parent_size.scale_by(scale);
1472            let new_unconstrained_size = parent_unconstrained_size.scale_by(scale);
1473
1474            if scale <= 1. {
1475                // The parent size can be smaller than scriptminsize, e.g. if it
1476                // was specified explicitly. Don't scale in this case, but we
1477                // don't want to set it to scriptminsize either since that will
1478                // make it larger.
1479                if parent_size <= min {
1480                    (parent_size, new_unconstrained_size)
1481                } else {
1482                    (min.max(new_size), new_unconstrained_size)
1483                }
1484            } else {
1485                // If the new unconstrained size is larger than the min size,
1486                // this means we have escaped the grasp of scriptminsize and can
1487                // revert to using the unconstrained size.
1488                // However, if the new size is even larger (perhaps due to usage
1489                // of em units), use that instead.
1490                (
1491                    new_size.min(new_unconstrained_size.max(min)),
1492                    new_unconstrained_size,
1493                )
1494            }
1495        };
1496        let font = context.builder.mutate_font();
1497        font.mFont.size = NonNegative(new_size);
1498        font.mSize = NonNegative(new_size);
1499        font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size);
1500    }
1501}