tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cascade.rs (55927B)


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