style/properties/
cascade.rs

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