/* 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::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; #[cfg(feature = "gecko")] 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; /// 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( 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, ) -> Arc 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, // 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 { 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( 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, ) -> Arc 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, 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 CustomPropertiesBuilder::might_have_non_custom_dependency(id, declaration) { if let Some(ref mut builder) = custom_builder { builder.maybe_note_non_custom_dependency(id, declaration); } } } } } /// 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, ) -> Arc where E: TElement + 'a, I: Iterator, { 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.forced_colors().is_active(); 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; context.builder.color_scheme = unvisited_context.builder.color_scheme; // 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, 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. #[cfg(feature = "gecko")] { 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 { // Honor CSS-wide keywords like unset / revert / initial... PropertyDeclaration::CSSWideKeyword(..) => return, 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 ( | 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>; 64]>, /// 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, 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); #[cfg(feature = "gecko")] let has_writing_mode = has_writing_mode | apply!(TextOrientation); if has_writing_mode { context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box()) } if apply!(Zoom) { context.builder.effective_zoom = context .builder .inherited_effective_zoom() .compute_effective(context.builder.specified_zoom()); // NOTE(emilio): This is a bit of a hack, but matches the shipped WebKit and Blink // behavior for now. Ideally, in the future, we have a pass over all // implicitly-or-explicitly-inherited properties that can contain lengths and // re-compute them properly, see https://github.com/w3c/csswg-drafts/issues/9397. self.recompute_font_size_for_zoom_change(&mut context.builder); } // Compute font-family. let has_font_family = apply!(FontFamily); let has_lang = apply!(XLang); #[cfg(feature = "gecko")] { 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. #[cfg(feature = "gecko")] { 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); } } #[cfg(feature = "servo")] { apply!(FontSize); if has_lang || has_font_family { self.recompute_keyword_font_size_if_needed(context); } } // Compute the rest of the first-available-font-affecting properties. apply!(FontWeight); apply!(FontStretch); apply!(FontStyle); #[cfg(feature = "gecko")] apply!(FontSizeAdjust); #[cfg(feature = "gecko")] apply!(ForcedColorAdjust); // color-scheme needs to be after forced-color-adjust, since it's one of the "skipped in // forced-colors-mode" properties. if apply!(ColorScheme) { context.builder.color_scheme = context.builder.get_inherited_ui().color_scheme_bits(); } 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_visited_style_if_needed( &self, context: &mut computed::Context, element: Option, 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); } #[cfg(feature = "gecko")] if self .author_specified .contains(LonghandId::FontSynthesisWeight) { builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT); } #[cfg(feature = "gecko")] 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. 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) }, }; #[cfg(feature = "gecko")] 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; } /// 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. #[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 never enable it" ); let device = builder.device; builder.mutate_font().unzoom_fonts(device); } fn recompute_font_size_for_zoom_change(&self, builder: &mut StyleBuilder) { debug_assert!(self.seen.contains(LonghandId::Zoom)); // NOTE(emilio): Intentionally not using the effective zoom here, since all the inherited // zooms are already applied. let old_size = builder.get_font().clone_font_size(); let new_size = old_size.zoom(builder.resolved_specified_zoom()); if old_size == new_size { return; } builder.mutate_font().set_font_size(new_size); } /// 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, parent_script_script_percent_scale_down: Option, ) -> 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 { std::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); } }