diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /servo/components/style/properties/cascade.rs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/properties/cascade.rs')
-rw-r--r-- | servo/components/style/properties/cascade.rs | 1381 |
1 files changed, 1381 insertions, 0 deletions
diff --git a/servo/components/style/properties/cascade.rs b/servo/components/style/properties/cascade.rs new file mode 100644 index 0000000000..59a8a65876 --- /dev/null +++ b/servo/components/style/properties/cascade.rs @@ -0,0 +1,1381 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! The main cascading algorithm of the style system. + +use crate::applicable_declarations::CascadePriority; +use crate::color::AbsoluteColor; +use crate::computed_value_flags::ComputedValueFlags; +use crate::custom_properties::{ + CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution, +}; +use crate::dom::TElement; +use crate::font_metrics::FontMetricsOrientation; +use crate::logical_geometry::WritingMode; +use crate::properties::{ + property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, Importance, + LonghandId, LonghandIdSet, PrioritaryPropertyId, PropertyDeclaration, PropertyDeclarationId, + PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY, +}; +use crate::rule_cache::{RuleCache, RuleCacheConditions}; +use crate::rule_tree::{CascadeLevel, StrongRuleNode}; +use crate::selector_parser::PseudoElement; +use crate::shared_lock::StylesheetGuards; +use crate::style_adjuster::StyleAdjuster; +use crate::stylesheets::container_rule::ContainerSizeQuery; +use crate::stylesheets::{layer_rule::LayerOrder, Origin}; +use crate::stylist::Stylist; +use crate::values::specified::length::FontBaseSize; +use crate::values::{computed, specified}; +use fxhash::FxHashMap; +use servo_arc::Arc; +use smallvec::SmallVec; +use std::borrow::Cow; +use std::mem; + +/// Whether we're resolving a style with the purposes of reparenting for ::first-line. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum FirstLineReparenting<'a> { + No, + Yes { + /// The style we're re-parenting for ::first-line. ::first-line only affects inherited + /// properties so we use this to avoid some work and also ensure correctness by copying the + /// reset structs from this style. + style_to_reparent: &'a ComputedValues, + }, +} + +/// Performs the CSS cascade, computing new styles for an element from its parent style. +/// +/// The arguments are: +/// +/// * `device`: Used to get the initial viewport and other external state. +/// +/// * `rule_node`: The rule node in the tree that represent the CSS rules that +/// matched. +/// +/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. +/// +/// Returns the computed values. +/// * `flags`: Various flags. +/// +pub fn cascade<E>( + stylist: &Stylist, + pseudo: Option<&PseudoElement>, + rule_node: &StrongRuleNode, + guards: &StylesheetGuards, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + first_line_reparenting: FirstLineReparenting, + visited_rules: Option<&StrongRuleNode>, + cascade_input_flags: ComputedValueFlags, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, +{ + cascade_rules( + stylist, + pseudo, + rule_node, + guards, + parent_style, + layout_parent_style, + first_line_reparenting, + CascadeMode::Unvisited { visited_rules }, + cascade_input_flags, + rule_cache, + rule_cache_conditions, + element, + ) +} + +struct DeclarationIterator<'a> { + // Global to the iteration. + guards: &'a StylesheetGuards<'a>, + restriction: Option<PropertyFlags>, + // The rule we're iterating over. + current_rule_node: Option<&'a StrongRuleNode>, + // Per rule state. + declarations: DeclarationImportanceIterator<'a>, + origin: Origin, + importance: Importance, + priority: CascadePriority, +} + +impl<'a> DeclarationIterator<'a> { + #[inline] + fn new( + rule_node: &'a StrongRuleNode, + guards: &'a StylesheetGuards, + pseudo: Option<&PseudoElement>, + ) -> Self { + let restriction = pseudo.and_then(|p| p.property_restriction()); + let mut iter = Self { + guards, + current_rule_node: Some(rule_node), + origin: Origin::UserAgent, + importance: Importance::Normal, + priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), + declarations: DeclarationImportanceIterator::default(), + restriction, + }; + iter.update_for_node(rule_node); + iter + } + + fn update_for_node(&mut self, node: &'a StrongRuleNode) { + self.priority = node.cascade_priority(); + let level = self.priority.cascade_level(); + self.origin = level.origin(); + self.importance = level.importance(); + let guard = match self.origin { + Origin::Author => self.guards.author, + Origin::User | Origin::UserAgent => self.guards.ua_or_user, + }; + self.declarations = match node.style_source() { + Some(source) => source.read(guard).declaration_importance_iter(), + None => DeclarationImportanceIterator::default(), + }; + } +} + +impl<'a> Iterator for DeclarationIterator<'a> { + type Item = (&'a PropertyDeclaration, CascadePriority); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + loop { + if let Some((decl, importance)) = self.declarations.next_back() { + if self.importance != importance { + continue; + } + + if let Some(restriction) = self.restriction { + // decl.id() is either a longhand or a custom + // property. Custom properties are always allowed, but + // longhands are only allowed if they have our + // restriction flag set. + if let PropertyDeclarationId::Longhand(id) = decl.id() { + if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { + continue; + } + } + } + + return Some((decl, self.priority)); + } + + let next_node = self.current_rule_node.take()?.parent()?; + self.current_rule_node = Some(next_node); + self.update_for_node(next_node); + } + } +} + +fn cascade_rules<E>( + stylist: &Stylist, + pseudo: Option<&PseudoElement>, + rule_node: &StrongRuleNode, + guards: &StylesheetGuards, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + first_line_reparenting: FirstLineReparenting, + cascade_mode: CascadeMode, + cascade_input_flags: ComputedValueFlags, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, +{ + apply_declarations( + stylist, + pseudo, + rule_node, + guards, + DeclarationIterator::new(rule_node, guards, pseudo), + parent_style, + layout_parent_style, + first_line_reparenting, + cascade_mode, + cascade_input_flags, + rule_cache, + rule_cache_conditions, + element, + ) +} + +/// Whether we're cascading for visited or unvisited styles. +#[derive(Clone, Copy)] +pub enum CascadeMode<'a, 'b> { + /// We're cascading for unvisited styles. + Unvisited { + /// The visited rules that should match the visited style. + visited_rules: Option<&'a StrongRuleNode>, + }, + /// We're cascading for visited styles. + Visited { + /// The cascade for our unvisited style. + unvisited_context: &'a computed::Context<'b>, + }, +} + +fn iter_declarations<'builder, 'decls: 'builder>( + iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>, + declarations: &mut Declarations<'decls>, + mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>, +) { + for (declaration, priority) in iter { + if let PropertyDeclaration::Custom(ref declaration) = *declaration { + if let Some(ref mut builder) = custom_builder { + builder.cascade(declaration, priority); + } + } else { + let id = declaration.id().as_longhand().unwrap(); + declarations.note_declaration(declaration, priority, id); + if let Some(ref mut builder) = custom_builder { + if let PropertyDeclaration::WithVariables(ref v) = declaration { + builder.note_potentially_cyclic_non_custom_dependency(id, v); + } + } + } + } +} + +/// NOTE: This function expects the declaration with more priority to appear +/// first. +pub fn apply_declarations<'a, E, I>( + stylist: &'a Stylist, + pseudo: Option<&'a PseudoElement>, + rules: &StrongRuleNode, + guards: &StylesheetGuards, + iter: I, + parent_style: Option<&'a ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + first_line_reparenting: FirstLineReparenting<'a>, + cascade_mode: CascadeMode, + cascade_input_flags: ComputedValueFlags, + rule_cache: Option<&'a RuleCache>, + rule_cache_conditions: &'a mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement + 'a, + I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>, +{ + debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); + let device = stylist.device(); + let inherited_style = parent_style.unwrap_or(device.default_computed_values()); + let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root()); + + let container_size_query = + ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some()); + + let mut context = computed::Context::new( + // We'd really like to own the rules here to avoid refcount traffic, but + // animation's usage of `apply_declarations` make this tricky. See bug + // 1375525. + StyleBuilder::new( + device, + Some(stylist), + parent_style, + pseudo, + Some(rules.clone()), + is_root_element, + ), + stylist.quirks_mode(), + rule_cache_conditions, + container_size_query, + ); + + context.style().add_flags(cascade_input_flags); + + let using_cached_reset_properties; + let ignore_colors = !context.builder.device.use_document_colors(); + let mut cascade = Cascade::new(first_line_reparenting, ignore_colors); + let mut declarations = Default::default(); + let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); + let properties_to_apply = match cascade_mode { + CascadeMode::Visited { unvisited_context } => { + context.builder.custom_properties = unvisited_context.builder.custom_properties.clone(); + context.builder.writing_mode = unvisited_context.builder.writing_mode; + // We never insert visited styles into the cache so we don't need to try looking it up. + // It also wouldn't be super-profitable, only a handful :visited properties are + // non-inherited. + using_cached_reset_properties = false; + // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could + // try to avoid gathering the declarations. That'd be: + // unvisited_context.builder.rules.as_ref() == Some(rules) + iter_declarations(iter, &mut declarations, None); + + LonghandIdSet::visited_dependent() + }, + CascadeMode::Unvisited { visited_rules } => { + let deferred_custom_properties = { + let mut builder = CustomPropertiesBuilder::new(stylist, &mut context); + iter_declarations(iter, &mut declarations, Some(&mut builder)); + // Detect cycles, remove properties participating in them, and resolve properties, except: + // * Registered custom properties that depend on font-relative properties (Resolved) + // when prioritary properties are resolved), and + // * Any property that, in turn, depend on properties like above. + builder.build(DeferFontRelativeCustomPropertyResolution::Yes) + }; + + // Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom + // properties. + cascade.apply_prioritary_properties(&mut context, &declarations, &mut shorthand_cache); + + // Resolve the deferred custom properties. + if let Some(deferred) = deferred_custom_properties { + CustomPropertiesBuilder::build_deferred(deferred, stylist, &mut context); + } + + if let Some(visited_rules) = visited_rules { + cascade.compute_visited_style_if_needed( + &mut context, + element, + parent_style, + layout_parent_style, + visited_rules, + guards, + ); + } + + using_cached_reset_properties = cascade.try_to_use_cached_reset_properties( + &mut context.builder, + rule_cache, + guards, + ); + + if using_cached_reset_properties { + LonghandIdSet::late_group_only_inherited() + } else { + LonghandIdSet::late_group() + } + }, + }; + + cascade.apply_non_prioritary_properties( + &mut context, + &declarations.longhand_declarations, + &mut shorthand_cache, + &properties_to_apply, + ); + + cascade.finished_applying_properties(&mut context.builder); + + std::mem::drop(cascade); + + context.builder.clear_modified_reset(); + + if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { + StyleAdjuster::new(&mut context.builder) + .adjust(layout_parent_style.unwrap_or(inherited_style), element); + } + + if context.builder.modified_reset() || using_cached_reset_properties { + // If we adjusted any reset structs, we can't cache this ComputedValues. + // + // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside + // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be + // set appropriately if we didn't compute those reset properties.) + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + } + + context.builder.build() +} + +/// For ignored colors mode, we sometimes want to do something equivalent to +/// "revert-or-initial", where we `revert` for a given origin, but then apply a +/// given initial value if nothing in other origins did override it. +/// +/// This is a bit of a clunky way of achieving this. +type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>; + +fn tweak_when_ignoring_colors( + context: &computed::Context, + longhand_id: LonghandId, + origin: Origin, + declaration: &mut Cow<PropertyDeclaration>, + declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden, +) { + use crate::values::computed::ToComputedValue; + use crate::values::specified::Color; + + if !longhand_id.ignored_when_document_colors_disabled() { + return; + } + + let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent); + if is_ua_or_user_rule { + return; + } + + // Always honor colors if forced-color-adjust is set to none. + let forced = context + .builder + .get_inherited_text() + .clone_forced_color_adjust(); + if forced == computed::ForcedColorAdjust::None { + return; + } + + // Don't override background-color on ::-moz-color-swatch. It is set as an + // author style (via the style attribute), but it's pretty important for it + // to show up for obvious reasons :) + if context + .builder + .pseudo + .map_or(false, |p| p.is_color_swatch()) && + longhand_id == LonghandId::BackgroundColor + { + return; + } + + fn alpha_channel(color: &Color, context: &computed::Context) -> f32 { + // We assume here currentColor is opaque. + color + .to_computed_value(context) + .resolve_to_absolute(&AbsoluteColor::BLACK) + .alpha + } + + // A few special-cases ahead. + match **declaration { + PropertyDeclaration::BackgroundColor(ref color) => { + // We honor system colors and transparent colors unconditionally. + // + // NOTE(emilio): We honor transparent unconditionally, like we do + // for color, even though it causes issues like bug 1625036. The + // reasoning is that the conditions that trigger that (having + // mismatched widget and default backgrounds) are both uncommon, and + // broken in other applications as well, and not honoring + // transparent makes stuff uglier or break unconditionally + // (bug 1666059, bug 1755713). + if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) { + return; + } + // For background-color, we revert or initial-with-preserved-alpha + // otherwise, this is needed to preserve semi-transparent + // backgrounds. + let alpha = alpha_channel(color, context); + if alpha == 0.0 { + return; + } + let mut color = context.builder.device.default_background_color(); + color.alpha = alpha; + declarations_to_apply_unless_overridden + .push(PropertyDeclaration::BackgroundColor(color.into())) + }, + PropertyDeclaration::Color(ref color) => { + // We honor color: transparent and system colors. + if color + .0 + .honored_in_forced_colors_mode(/* allow_transparent = */ true) + { + return; + } + // If the inherited color would be transparent, but we would + // override this with a non-transparent color, then override it with + // the default color. Otherwise just let it inherit through. + if context + .builder + .get_parent_inherited_text() + .clone_color() + .alpha == + 0.0 + { + let color = context.builder.device.default_color(); + declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color( + specified::ColorPropertyValue(color.into()), + )) + } + }, + // We honor url background-images if backplating. + #[cfg(feature = "gecko")] + PropertyDeclaration::BackgroundImage(ref bkg) => { + use crate::values::generics::image::Image; + if static_prefs::pref!("browser.display.permit_backplate") { + if bkg + .0 + .iter() + .all(|image| matches!(*image, Image::Url(..) | Image::None)) + { + return; + } + } + }, + _ => { + // We honor system colors more generally for all colors. + // + // We used to honor transparent but that causes accessibility + // regressions like bug 1740924. + // + // NOTE(emilio): This doesn't handle caret-color and accent-color + // because those use a slightly different syntax (<color> | auto for + // example). + // + // That's probably fine though, as using a system color for + // caret-color doesn't make sense (using currentColor is fine), and + // we ignore accent-color in high-contrast-mode anyways. + if let Some(color) = declaration.color_value() { + if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) { + return; + } + } + }, + } + + *declaration.to_mut() = + PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert); +} + +/// We track the index only for prioritary properties. For other properties we can just iterate. +type DeclarationIndex = u16; + +/// "Prioritary" properties are properties that other properties depend on in one way or another. +/// +/// We keep track of their position in the declaration vector, in order to be able to cascade them +/// separately in precise order. +#[derive(Copy, Clone)] +struct PrioritaryDeclarationPosition { + // DeclarationIndex::MAX signals no index. + most_important: DeclarationIndex, + least_important: DeclarationIndex, +} + +impl Default for PrioritaryDeclarationPosition { + fn default() -> Self { + Self { + most_important: DeclarationIndex::MAX, + least_important: DeclarationIndex::MAX, + } + } +} + +#[derive(Copy, Clone)] +struct Declaration<'a> { + decl: &'a PropertyDeclaration, + priority: CascadePriority, + next_index: DeclarationIndex, +} + +/// The set of property declarations from our rules. +#[derive(Default)] +struct Declarations<'a> { + /// Whether we have any prioritary property. This is just a minor optimization. + has_prioritary_properties: bool, + /// A list of all the applicable longhand declarations. + longhand_declarations: SmallVec<[Declaration<'a>; 32]>, + /// The prioritary property position data. + prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY], +} + +impl<'a> Declarations<'a> { + fn note_prioritary_property(&mut self, id: PrioritaryPropertyId) { + let new_index = self.longhand_declarations.len(); + if new_index >= DeclarationIndex::MAX as usize { + // This prioritary property is past the amount of declarations we can track. Let's give + // up applying it to prevent getting confused. + return; + } + + self.has_prioritary_properties = true; + let new_index = new_index as DeclarationIndex; + let position = &mut self.prioritary_positions[id as usize]; + if position.most_important == DeclarationIndex::MAX { + // We still haven't seen this property, record the current position as the most + // prioritary index. + position.most_important = new_index; + } else { + // Let the previous item in the list know about us. + self.longhand_declarations[position.least_important as usize].next_index = new_index; + } + position.least_important = new_index; + } + + fn note_declaration( + &mut self, + decl: &'a PropertyDeclaration, + priority: CascadePriority, + id: LonghandId, + ) { + if let Some(id) = PrioritaryPropertyId::from_longhand(id) { + self.note_prioritary_property(id); + } + self.longhand_declarations.push(Declaration { + decl, + priority, + next_index: 0, + }); + } +} + +struct Cascade<'b> { + first_line_reparenting: FirstLineReparenting<'b>, + ignore_colors: bool, + seen: LonghandIdSet, + author_specified: LonghandIdSet, + reverted_set: LonghandIdSet, + reverted: FxHashMap<LonghandId, (CascadePriority, bool)>, + declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden, +} + +impl<'b> Cascade<'b> { + fn new(first_line_reparenting: FirstLineReparenting<'b>, ignore_colors: bool) -> Self { + Self { + first_line_reparenting, + ignore_colors, + seen: LonghandIdSet::default(), + author_specified: LonghandIdSet::default(), + reverted_set: Default::default(), + reverted: Default::default(), + declarations_to_apply_unless_overridden: Default::default(), + } + } + + fn substitute_variables_if_needed<'cache, 'decl>( + &self, + context: &mut computed::Context, + shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, + declaration: &'decl PropertyDeclaration, + ) -> Cow<'decl, PropertyDeclaration> + where + 'cache: 'decl, + { + let declaration = match *declaration { + PropertyDeclaration::WithVariables(ref declaration) => declaration, + ref d => return Cow::Borrowed(d), + }; + + if !declaration.id.inherited() { + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + + // NOTE(emilio): We only really need to add the `display` / + // `content` flag if the CSS variable has not been specified on our + // declarations, but we don't have that information at this point, + // and it doesn't seem like an important enough optimization to + // warrant it. + match declaration.id { + LonghandId::Display => { + context + .builder + .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); + }, + LonghandId::Content => { + context + .builder + .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); + }, + _ => {}, + } + } + + debug_assert!( + context.builder.stylist.is_some(), + "Need a Stylist to substitute variables!" + ); + declaration.value.substitute_variables( + declaration.id, + context.builder.custom_properties(), + context.builder.stylist.unwrap(), + context, + shorthand_cache, + ) + } + + fn apply_one_prioritary_property( + &mut self, + context: &mut computed::Context, + decls: &Declarations, + cache: &mut ShorthandsWithPropertyReferencesCache, + id: PrioritaryPropertyId, + ) -> bool { + let mut index = decls.prioritary_positions[id as usize].most_important; + if index == DeclarationIndex::MAX { + return false; + } + + let longhand_id = id.to_longhand(); + debug_assert!( + !longhand_id.is_logical(), + "That could require more book-keeping" + ); + loop { + let decl = decls.longhand_declarations[index as usize]; + self.apply_one_longhand(context, longhand_id, decl.decl, decl.priority, cache); + if self.seen.contains(longhand_id) { + return true; // Common case, we're done. + } + debug_assert!( + self.reverted_set.contains(longhand_id), + "How else can we fail to apply a prioritary property?" + ); + debug_assert!( + decl.next_index == 0 || decl.next_index > index, + "should make progress! {} -> {}", + index, + decl.next_index, + ); + index = decl.next_index; + if index == 0 { + break; + } + } + false + } + + fn apply_prioritary_properties( + &mut self, + context: &mut computed::Context, + decls: &Declarations, + cache: &mut ShorthandsWithPropertyReferencesCache, + ) { + // Keeps apply_one_prioritary_property calls readable, considering the repititious + // arguments. + macro_rules! apply { + ($prop:ident) => { + self.apply_one_prioritary_property( + context, + decls, + cache, + PrioritaryPropertyId::$prop, + ) + }; + } + + if !decls.has_prioritary_properties { + return; + } + + let has_writing_mode = apply!(WritingMode) | apply!(Direction) | apply!(TextOrientation); + if has_writing_mode { + self.compute_writing_mode(context); + } + + if apply!(Zoom) { + self.compute_zoom(context); + } + + // Compute font-family. + let has_font_family = apply!(FontFamily); + let has_lang = apply!(XLang); + if has_lang { + self.recompute_initial_font_family_if_needed(&mut context.builder); + } + if has_font_family { + self.prioritize_user_fonts_if_needed(&mut context.builder); + } + + // Compute font-size. + if apply!(XTextScale) { + self.unzoom_fonts_if_needed(&mut context.builder); + } + let has_font_size = apply!(FontSize); + let has_math_depth = apply!(MathDepth); + let has_min_font_size_ratio = apply!(MozMinFontSizeRatio); + + if has_math_depth && has_font_size { + self.recompute_math_font_size_if_needed(context); + } + if has_lang || has_font_family { + self.recompute_keyword_font_size_if_needed(context); + } + if has_font_size || has_min_font_size_ratio || has_lang || has_font_family { + self.constrain_font_size_if_needed(&mut context.builder); + } + + // Compute the rest of the first-available-font-affecting properties. + apply!(FontWeight); + apply!(FontStretch); + apply!(FontStyle); + apply!(FontSizeAdjust); + + apply!(ColorScheme); + apply!(ForcedColorAdjust); + + // Compute the line height. + apply!(LineHeight); + } + + fn apply_non_prioritary_properties( + &mut self, + context: &mut computed::Context, + longhand_declarations: &[Declaration], + shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, + properties_to_apply: &LonghandIdSet, + ) { + debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties())); + debug_assert!(self.declarations_to_apply_unless_overridden.is_empty()); + for declaration in &*longhand_declarations { + let mut longhand_id = declaration.decl.id().as_longhand().unwrap(); + if !properties_to_apply.contains(longhand_id) { + continue; + } + debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none()); + let is_logical = longhand_id.is_logical(); + if is_logical { + let wm = context.builder.writing_mode; + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(wm); + longhand_id = longhand_id.to_physical(wm); + } + self.apply_one_longhand( + context, + longhand_id, + declaration.decl, + declaration.priority, + shorthand_cache, + ); + } + if !self.declarations_to_apply_unless_overridden.is_empty() { + debug_assert!(self.ignore_colors); + for declaration in std::mem::take(&mut self.declarations_to_apply_unless_overridden) { + let longhand_id = declaration.id().as_longhand().unwrap(); + debug_assert!(!longhand_id.is_logical()); + if !self.seen.contains(longhand_id) { + unsafe { + self.do_apply_declaration(context, longhand_id, &declaration); + } + } + } + } + } + + fn apply_one_longhand( + &mut self, + context: &mut computed::Context, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + priority: CascadePriority, + cache: &mut ShorthandsWithPropertyReferencesCache, + ) { + debug_assert!(!longhand_id.is_logical()); + let origin = priority.cascade_level().origin(); + if self.seen.contains(longhand_id) { + return; + } + + if self.reverted_set.contains(longhand_id) { + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&longhand_id) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + return; + } + } + } + + let mut declaration = self.substitute_variables_if_needed(context, cache, declaration); + + // When document colors are disabled, do special handling of + // properties that are marked as ignored in that mode. + if self.ignore_colors { + tweak_when_ignoring_colors( + context, + longhand_id, + origin, + &mut declaration, + &mut self.declarations_to_apply_unless_overridden, + ); + } + + let is_unset = match declaration.get_css_wide_keyword() { + Some(keyword) => match keyword { + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; + // We intentionally don't want to insert it into `self.seen`, `reverted` takes + // care of rejecting other declarations as needed. + self.reverted_set.insert(longhand_id); + self.reverted.insert(longhand_id, (priority, origin_revert)); + return; + }, + CSSWideKeyword::Unset => true, + CSSWideKeyword::Inherit => longhand_id.inherited(), + CSSWideKeyword::Initial => !longhand_id.inherited(), + }, + None => false, + }; + + self.seen.insert(longhand_id); + if origin == Origin::Author { + self.author_specified.insert(longhand_id); + } + + if is_unset { + return; + } + + unsafe { self.do_apply_declaration(context, longhand_id, &declaration) } + } + + #[inline] + unsafe fn do_apply_declaration( + &self, + context: &mut computed::Context, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + ) { + debug_assert!(!longhand_id.is_logical()); + // We could (and used to) use a pattern match here, but that bloats this + // function to over 100K of compiled code! + // + // To improve i-cache behavior, we outline the individual functions and + // use virtual dispatch instead. + (CASCADE_PROPERTY[longhand_id as usize])(&declaration, context); + } + + fn compute_zoom(&self, context: &mut computed::Context) { + context.builder.effective_zoom = context + .builder + .inherited_effective_zoom() + .compute_effective(context.builder.specified_zoom()); + } + + fn compute_writing_mode(&self, context: &mut computed::Context) { + context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box()) + } + + fn compute_visited_style_if_needed<E>( + &self, + context: &mut computed::Context, + element: Option<E>, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + visited_rules: &StrongRuleNode, + guards: &StylesheetGuards, + ) where + E: TElement, + { + let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link(); + + macro_rules! visited_parent { + ($parent:expr) => { + if is_link { + $parent + } else { + $parent.map(|p| p.visited_style().unwrap_or(p)) + } + }; + } + + // We could call apply_declarations directly, but that'd cause + // another instantiation of this function which is not great. + let style = cascade_rules( + context.builder.stylist.unwrap(), + context.builder.pseudo, + visited_rules, + guards, + visited_parent!(parent_style), + visited_parent!(layout_parent_style), + self.first_line_reparenting, + CascadeMode::Visited { + unvisited_context: &*context, + }, + // Cascade input flags don't matter for the visited style, they are + // in the main (unvisited) style. + Default::default(), + // The rule cache doesn't care about caching :visited + // styles, we cache the unvisited style instead. We still do + // need to set the caching dependencies properly if present + // though, so the cache conditions need to match. + None, // rule_cache + &mut *context.rule_cache_conditions.borrow_mut(), + element, + ); + context.builder.visited_style = Some(style); + } + + fn finished_applying_properties(&self, builder: &mut StyleBuilder) { + #[cfg(feature = "gecko")] + { + if let Some(bg) = builder.get_background_if_mutated() { + bg.fill_arrays(); + } + + if let Some(svg) = builder.get_svg_if_mutated() { + svg.fill_arrays(); + } + } + + if self + .author_specified + .contains_any(LonghandIdSet::border_background_properties()) + { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); + } + + if self.author_specified.contains(LonghandId::FontFamily) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY); + } + + if self.author_specified.contains(LonghandId::Color) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_COLOR); + } + + if self.author_specified.contains(LonghandId::LetterSpacing) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING); + } + + if self.author_specified.contains(LonghandId::WordSpacing) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING); + } + + if self + .author_specified + .contains(LonghandId::FontSynthesisWeight) + { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT); + } + + if self + .author_specified + .contains(LonghandId::FontSynthesisStyle) + { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE); + } + + #[cfg(feature = "servo")] + { + if let Some(font) = builder.get_font_if_mutated() { + font.compute_font_hash(); + } + } + } + + fn try_to_use_cached_reset_properties( + &self, + builder: &mut StyleBuilder<'b>, + cache: Option<&'b RuleCache>, + guards: &StylesheetGuards, + ) -> bool { + let style = match self.first_line_reparenting { + FirstLineReparenting::Yes { style_to_reparent } => style_to_reparent, + FirstLineReparenting::No => { + let Some(cache) = cache else { return false }; + let Some(style) = cache.find(guards, builder) else { + return false; + }; + style + }, + }; + + builder.copy_reset_from(style); + + // We're using the same reset style as another element, and we'll skip + // applying the relevant properties. So we need to do the relevant + // bookkeeping here to keep these bits correct. + // + // Note that the border/background properties are non-inherited, so we + // don't need to do anything else other than just copying the bits over. + // + // When using this optimization, we also need to copy whether the old + // style specified viewport units / used font-relative lengths, this one + // would as well. It matches the same rules, so it is the right thing + // to do anyways, even if it's only used on inherited properties. + let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | + ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS | + ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS | + ComputedValueFlags::USES_CONTAINER_UNITS | + ComputedValueFlags::USES_VIEWPORT_UNITS; + builder.add_flags(style.flags & bits_to_copy); + + true + } + + /// The initial font depends on the current lang group so we may need to + /// recompute it if the language changed. + #[inline] + #[cfg(feature = "gecko")] + fn recompute_initial_font_family_if_needed(&self, builder: &mut StyleBuilder) { + use crate::gecko_bindings::bindings; + use crate::values::computed::font::FontFamily; + + let default_font_type = { + let font = builder.get_font(); + + if !font.mFont.family.is_initial { + return; + } + + let default_font_type = unsafe { + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( + builder.device.document(), + font.mLanguage.mRawPtr, + ) + }; + + let initial_generic = font.mFont.family.families.single_generic(); + debug_assert!( + initial_generic.is_some(), + "Initial font should be just one generic font" + ); + if initial_generic == Some(default_font_type) { + return; + } + + default_font_type + }; + + // NOTE: Leaves is_initial untouched. + builder.mutate_font().mFont.family.families = + FontFamily::generic(default_font_type).families.clone(); + } + + /// Prioritize user fonts if needed by pref. + #[inline] + #[cfg(feature = "gecko")] + fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) { + use crate::gecko_bindings::bindings; + + // Check the use_document_fonts setting for content, but for chrome + // documents they're treated as always enabled. + if static_prefs::pref!("browser.display.use_document_fonts") != 0 || + builder.device.chrome_rules_enabled_for_document() + { + return; + } + + let default_font_type = { + let font = builder.get_font(); + + if font.mFont.family.is_system_font { + return; + } + + if !font.mFont.family.families.needs_user_font_prioritization() { + return; + } + + unsafe { + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( + builder.device.document(), + font.mLanguage.mRawPtr, + ) + } + }; + + let font = builder.mutate_font(); + font.mFont + .family + .families + .prioritize_first_generic_or_prepend(default_font_type); + } + + /// Some keyword sizes depend on the font family and language. + #[cfg(feature = "gecko")] + fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) { + use crate::values::computed::ToComputedValue; + + if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { + return; + } + + let new_size = { + let font = context.builder.get_font(); + let info = font.clone_font_size().keyword_info; + let new_size = match info.kw { + specified::FontSizeKeyword::None => return, + _ => { + context.for_non_inherited_property = false; + specified::FontSize::Keyword(info).to_computed_value(context) + }, + }; + + if font.mScriptUnconstrainedSize == new_size.computed_size { + return; + } + + new_size + }; + + context.builder.mutate_font().set_font_size(new_size); + } + + /// Some properties, plus setting font-size itself, may make us go out of + /// our minimum font-size range. + #[cfg(feature = "gecko")] + fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) { + use crate::gecko_bindings::bindings; + use crate::values::generics::NonNegative; + + let min_font_size = { + let font = builder.get_font(); + let min_font_size = unsafe { + bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document()) + }; + + if font.mFont.size.0 >= min_font_size { + return; + } + + NonNegative(min_font_size) + }; + + builder.mutate_font().mFont.size = min_font_size; + } + + /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up + /// the struct when this happens by unzooming its contained font values, which will have been + /// zoomed in the parent. + /// + /// FIXME(emilio): Why doing this _before_ handling font-size? That sounds wrong. + #[cfg(feature = "gecko")] + fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) { + debug_assert!(self.seen.contains(LonghandId::XTextScale)); + + let parent_text_scale = builder.get_parent_font().clone__x_text_scale(); + let text_scale = builder.get_font().clone__x_text_scale(); + if parent_text_scale == text_scale { + return; + } + debug_assert_ne!( + parent_text_scale.text_zoom_enabled(), + text_scale.text_zoom_enabled(), + "There's only one value that disables it" + ); + debug_assert!( + !text_scale.text_zoom_enabled(), + "We only ever disable text zoom (in svg:text), never enable it" + ); + let device = builder.device; + builder.mutate_font().unzoom_fonts(device); + } + + /// Special handling of font-size: math (used for MathML). + /// https://w3c.github.io/mathml-core/#the-math-script-level-property + /// TODO: Bug: 1548471: MathML Core also does not specify a script min size + /// should we unship that feature or standardize it? + #[cfg(feature = "gecko")] + fn recompute_math_font_size_if_needed(&self, context: &mut computed::Context) { + use crate::values::generics::NonNegative; + + // Do not do anything if font-size: math or math-depth is not set. + if context.builder.get_font().clone_font_size().keyword_info.kw != + specified::FontSizeKeyword::Math + { + return; + } + + const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71; + + // Helper function that calculates the scale factor applied to font-size + // when math-depth goes from parent_math_depth to computed_math_depth. + // This function is essentially a modification of the MathML3's formula + // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor + // of parent_script_percent_scale_down is applied when math-depth goes + // from 0 to 1 and parent_script_script_percent_scale_down is applied + // when math-depth goes from 0 to 2. This is also a straightforward + // implementation of the specification's algorithm: + // https://w3c.github.io/mathml-core/#the-math-script-level-property + fn scale_factor_for_math_depth_change( + parent_math_depth: i32, + computed_math_depth: i32, + parent_script_percent_scale_down: Option<f32>, + parent_script_script_percent_scale_down: Option<f32>, + ) -> f32 { + let mut a = parent_math_depth; + let mut b = computed_math_depth; + let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE; + let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c); + let scale_between_0_and_2 = + parent_script_script_percent_scale_down.unwrap_or_else(|| c * c); + let mut s = 1.0; + let mut invert_scale_factor = false; + if a == b { + return s; + } + if b < a { + mem::swap(&mut a, &mut b); + invert_scale_factor = true; + } + let mut e = b - a; + if a <= 0 && b >= 2 { + s *= scale_between_0_and_2; + e -= 2; + } else if a == 1 { + s *= scale_between_0_and_2 / scale_between_0_and_1; + e -= 1; + } else if b == 1 { + s *= scale_between_0_and_1; + e -= 1; + } + s *= (c as f32).powi(e); + if invert_scale_factor { + 1.0 / s.max(f32::MIN_POSITIVE) + } else { + s + } + } + + let (new_size, new_unconstrained_size) = { + let builder = &context.builder; + let font = builder.get_font(); + let parent_font = builder.get_parent_font(); + + let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth); + + if delta == 0 { + return; + } + + let mut min = parent_font.mScriptMinSize; + if font.mXTextScale.text_zoom_enabled() { + min = builder.device.zoom_text(min); + } + + // Calculate scale factor following MathML Core's algorithm. + let scale = { + // Script scale factors are independent of orientation. + let font_metrics = context.query_font_metrics( + FontBaseSize::InheritedStyle, + FontMetricsOrientation::Horizontal, + /* retrieve_math_scales = */ true, + ); + scale_factor_for_math_depth_change( + parent_font.mMathDepth as i32, + font.mMathDepth as i32, + font_metrics.script_percent_scale_down, + font_metrics.script_script_percent_scale_down, + ) + }; + + let parent_size = parent_font.mSize.0; + let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0; + let new_size = parent_size.scale_by(scale); + let new_unconstrained_size = parent_unconstrained_size.scale_by(scale); + + if scale <= 1. { + // The parent size can be smaller than scriptminsize, e.g. if it + // was specified explicitly. Don't scale in this case, but we + // don't want to set it to scriptminsize either since that will + // make it larger. + if parent_size <= min { + (parent_size, new_unconstrained_size) + } else { + (min.max(new_size), new_unconstrained_size) + } + } else { + // If the new unconstrained size is larger than the min size, + // this means we have escaped the grasp of scriptminsize and can + // revert to using the unconstrained size. + // However, if the new size is even larger (perhaps due to usage + // of em units), use that instead. + ( + new_size.min(new_unconstrained_size.max(min)), + new_unconstrained_size, + ) + } + }; + let font = context.builder.mutate_font(); + font.mFont.size = NonNegative(new_size); + font.mSize = NonNegative(new_size); + font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size); + } +} |