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