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