diff options
Diffstat (limited to 'servo/components/style/properties/cascade.rs')
-rw-r--r-- | servo/components/style/properties/cascade.rs | 1076 |
1 files changed, 1076 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..8fbccd1d97 --- /dev/null +++ b/servo/components/style/properties/cascade.rs @@ -0,0 +1,1076 @@ +/* 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::context::QuirksMode; +use crate::custom_properties::CustomPropertiesBuilder; +use crate::dom::TElement; +use crate::font_metrics::FontMetricsProvider; +use crate::logical_geometry::WritingMode; +use crate::media_queries::Device; +use crate::properties::{ComputedValues, StyleBuilder, Importance}; +use crate::properties::{LonghandId, LonghandIdSet, CSSWideKeyword, PropertyFlags}; +use crate::properties::{PropertyDeclaration, PropertyDeclarationId, DeclarationImportanceIterator}; +use crate::properties::{CASCADE_PROPERTY, ComputedValueFlags}; +use crate::rule_cache::{RuleCache, RuleCacheConditions}; +use crate::rule_tree::StrongRuleNode; +use crate::selector_parser::PseudoElement; +use crate::stylesheets::{Origin, PerOrigin}; +use servo_arc::Arc; +use crate::shared_lock::StylesheetGuards; +use smallvec::SmallVec; +use std::borrow::Cow; +use std::cell::RefCell; +use crate::style_adjuster::StyleAdjuster; +use crate::values::{computed, specified}; + +/// We split the cascade in two phases: 'early' properties, and 'late' +/// properties. +/// +/// Early properties are the ones that don't have dependencies _and_ other +/// properties depend on, for example, writing-mode related properties, color +/// (for currentColor), or font-size (for em, etc). +/// +/// Late properties are all the others. +trait CascadePhase { + fn is_early() -> bool; +} + +struct EarlyProperties; +impl CascadePhase for EarlyProperties { + fn is_early() -> bool { + true + } +} + +struct LateProperties; +impl CascadePhase for LateProperties { + fn is_early() -> bool { + false + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum ApplyResetProperties { + No, + Yes, +} + +/// 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>( + device: &Device, + pseudo: Option<&PseudoElement>, + rule_node: &StrongRuleNode, + guards: &StylesheetGuards, + parent_style: Option<&ComputedValues>, + parent_style_ignoring_first_line: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + visited_rules: Option<&StrongRuleNode>, + font_metrics_provider: &dyn FontMetricsProvider, + quirks_mode: QuirksMode, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, +{ + cascade_rules( + device, + pseudo, + rule_node, + guards, + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + font_metrics_provider, + CascadeMode::Unvisited { visited_rules }, + quirks_mode, + 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, +} + +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::Author, + importance: Importance::Normal, + declarations: DeclarationImportanceIterator::default(), + restriction, + }; + iter.update_for_node(rule_node); + iter + } + + fn update_for_node(&mut self, node: &'a StrongRuleNode) { + let origin = node.cascade_level().origin(); + self.origin = origin; + self.importance = node.importance(); + let guard = match 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, Origin); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + loop { + if let Some((decl, importance)) = self.declarations.next_back() { + if self.importance != importance { + continue; + } + + let origin = self.origin; + 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) && origin != Origin::UserAgent { + continue; + } + } + } + + return Some((decl, origin)); + } + + 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>( + device: &Device, + pseudo: Option<&PseudoElement>, + rule_node: &StrongRuleNode, + guards: &StylesheetGuards, + parent_style: Option<&ComputedValues>, + parent_style_ignoring_first_line: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + font_metrics_provider: &dyn FontMetricsProvider, + cascade_mode: CascadeMode, + quirks_mode: QuirksMode, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, +{ + debug_assert_eq!( + parent_style.is_some(), + parent_style_ignoring_first_line.is_some() + ); + apply_declarations( + device, + pseudo, + rule_node, + guards, + DeclarationIterator::new(rule_node, guards, pseudo), + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + font_metrics_provider, + cascade_mode, + quirks_mode, + rule_cache, + rule_cache_conditions, + element, + ) +} + +/// Whether we're cascading for visited or unvisited styles. +#[derive(Clone, Copy)] +pub enum CascadeMode<'a> { + /// 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 writing mode of our unvisited style, needed to correctly resolve + /// logical properties.. + writing_mode: WritingMode, + }, +} + +/// NOTE: This function expects the declaration with more priority to appear +/// first. +pub fn apply_declarations<'a, E, I>( + device: &Device, + pseudo: Option<&PseudoElement>, + rules: &StrongRuleNode, + guards: &StylesheetGuards, + iter: I, + parent_style: Option<&ComputedValues>, + parent_style_ignoring_first_line: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + font_metrics_provider: &dyn FontMetricsProvider, + cascade_mode: CascadeMode, + quirks_mode: QuirksMode, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, + I: Iterator<Item = (&'a PropertyDeclaration, Origin)>, +{ + debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); + debug_assert_eq!( + parent_style.is_some(), + parent_style_ignoring_first_line.is_some() + ); + #[cfg(feature = "gecko")] + debug_assert!( + parent_style.is_none() || + ::std::ptr::eq( + parent_style.unwrap(), + parent_style_ignoring_first_line.unwrap() + ) || + parent_style.unwrap().is_first_line_style() + ); + + let inherited_style = parent_style.unwrap_or(device.default_computed_values()); + + let mut declarations = SmallVec::<[(&_, Origin); 32]>::new(); + let custom_properties = { + let mut builder = CustomPropertiesBuilder::new( + inherited_style.custom_properties(), + device, + ); + + for (declaration, origin) in iter { + declarations.push((declaration, origin)); + if let PropertyDeclaration::Custom(ref declaration) = *declaration { + builder.cascade(declaration, origin); + } + } + + builder.build() + }; + + + let is_root_element = + pseudo.is_none() && element.map_or(false, |e| e.is_root()); + + let mut context = computed::Context { + // 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. + builder: StyleBuilder::new( + device, + parent_style, + parent_style_ignoring_first_line, + pseudo, + Some(rules.clone()), + custom_properties, + is_root_element, + ), + cached_system_font: None, + in_media_query: false, + for_smil_animation: false, + for_non_inherited_property: None, + font_metrics_provider, + quirks_mode, + rule_cache_conditions: RefCell::new(rule_cache_conditions), + }; + + let using_cached_reset_properties = { + let mut cascade = Cascade::new(&mut context, cascade_mode); + + cascade + .apply_properties::<EarlyProperties, _>(ApplyResetProperties::Yes, declarations.iter().cloned()); + + cascade.compute_visited_style_if_needed( + element, + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + guards, + ); + + let using_cached_reset_properties = + cascade.try_to_use_cached_reset_properties(rule_cache, guards); + + let apply_reset = if using_cached_reset_properties { + ApplyResetProperties::No + } else { + ApplyResetProperties::Yes + }; + + cascade.apply_properties::<LateProperties, _>(apply_reset, declarations.iter().cloned()); + + using_cached_reset_properties + }; + + 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( + builder: &StyleBuilder, + longhand_id: LonghandId, + origin: Origin, + declaration: &mut Cow<PropertyDeclaration>, + declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden, +) { + 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; + } + + // 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 builder.pseudo.map_or(false, |p| p.is_color_swatch()) && longhand_id == LonghandId::BackgroundColor { + return; + } + + fn alpha_channel(color: &Color) -> u8 { + match *color { + // Seems safe enough to assume that the default color and system + // colors are opaque in HCM, though maybe we shouldn't asume the + // later? + #[cfg(feature = "gecko")] + Color::InheritFromBodyQuirk | Color::System(..) => 255, + // We don't have the actual color here, but since except for color: + // transparent we force opaque text colors, it seems sane to do + // this. You can technically fool this bit of code with: + // + // color: transparent; background-color: currentcolor; + // + // but this is best-effort, and that seems unlikely to happen in + // practice. + Color::CurrentColor => 255, + // Complex colors are results of interpolation only and probably + // shouldn't show up around here in HCM, but we've always treated + // them as opaque effectively so keep doing it. + Color::Complex { .. } => 255, + Color::Numeric { ref parsed, .. } => parsed.alpha, + } + } + + // A few special-cases ahead. + match **declaration { + PropertyDeclaration::BackgroundColor(ref color) => { + // For background-color, we revert or initial-with-preserved-alpha + // otherwise, this is needed to preserve semi-transparent + // backgrounds. + // + // FIXME(emilio, bug 1666059): We revert for alpha == 0, but maybe + // should consider not doing that even if it causes some issues like + // bug 1625036, or finding a performant way to preserve the original + // widget background color's rgb channels but not alpha... + let alpha = alpha_channel(color); + if alpha != 0 { + let mut color = builder.device.default_background_color(); + color.alpha = alpha; + declarations_to_apply_unless_overriden.push( + PropertyDeclaration::BackgroundColor(color.into()) + ) + } + } + PropertyDeclaration::Color(ref color) => { + // We honor color: transparent, and "revert-or-initial" otherwise. + if alpha_channel(&color.0) == 0 { + 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 builder.get_parent_inherited_text().clone_color().alpha == 0 { + let color = builder.device.default_color(); + declarations_to_apply_unless_overriden.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(..))) { + return; + } + } + }, + _ => {}, + } + + *declaration.to_mut() = PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert); + +} + +struct Cascade<'a, 'b: 'a> { + context: &'a mut computed::Context<'b>, + cascade_mode: CascadeMode<'a>, + seen: LonghandIdSet, + author_specified: LonghandIdSet, + reverted: PerOrigin<LonghandIdSet>, +} + +impl<'a, 'b: 'a> Cascade<'a, 'b> { + fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self { + Self { + context, + cascade_mode, + seen: LonghandIdSet::default(), + author_specified: LonghandIdSet::default(), + reverted: Default::default(), + } + } + + fn substitute_variables_if_needed<'decl>( + &mut self, + declaration: &'decl PropertyDeclaration, + ) -> Cow<'decl, PropertyDeclaration> { + let declaration = match *declaration { + PropertyDeclaration::WithVariables(ref declaration) => declaration, + ref d => return Cow::Borrowed(d), + }; + + if !declaration.id.inherited() { + self.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 => { + self.context + .builder + .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); + }, + LonghandId::Content => { + self.context + .builder + .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); + }, + _ => {}, + } + } + + Cow::Owned(declaration.value.substitute_variables( + declaration.id, + self.context.builder.custom_properties.as_ref(), + self.context.quirks_mode, + self.context.device(), + )) + } + + #[inline(always)] + fn apply_declaration( + &mut self, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + ) { + // 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. + let discriminant = longhand_id as usize; + (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context); + } + + fn apply_properties<'decls, Phase, I>( + &mut self, + apply_reset: ApplyResetProperties, + declarations: I, + ) where + Phase: CascadePhase, + I: Iterator<Item = (&'decls PropertyDeclaration, Origin)>, + { + let apply_reset = apply_reset == ApplyResetProperties::Yes; + + debug_assert!( + !Phase::is_early() || apply_reset, + "Should always apply reset properties in the early phase, since we \ + need to know font-size / writing-mode to decide whether to use the \ + cached reset properties" + ); + + let ignore_colors = !self.context.builder.device.use_document_colors(); + let mut declarations_to_apply_unless_overriden = + DeclarationsToApplyUnlessOverriden::new(); + + for (declaration, origin) in declarations { + let declaration_id = declaration.id(); + let longhand_id = match declaration_id { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => continue, + }; + + let inherited = longhand_id.inherited(); + if !apply_reset && !inherited { + continue; + } + + if Phase::is_early() != longhand_id.is_early_property() { + continue; + } + + debug_assert!(!Phase::is_early() || !longhand_id.is_logical()); + let physical_longhand_id = if Phase::is_early() { + longhand_id + } else { + longhand_id.to_physical(self.context.builder.writing_mode) + }; + + if self.seen.contains(physical_longhand_id) { + continue; + } + + if self.reverted.borrow_for_origin(&origin).contains(physical_longhand_id) { + continue; + } + + // Only a few properties are allowed to depend on the visited state + // of links. When cascading visited styles, we can save time by + // only processing these properties. + if matches!(self.cascade_mode, CascadeMode::Visited { .. }) && + !physical_longhand_id.is_visited_dependent() + { + continue; + } + + let mut declaration = self.substitute_variables_if_needed(declaration); + + // When document colors are disabled, do special handling of + // properties that are marked as ignored in that mode. + if ignore_colors { + tweak_when_ignoring_colors( + &self.context.builder, + longhand_id, + origin, + &mut declaration, + &mut declarations_to_apply_unless_overriden, + ); + debug_assert_eq!( + declaration.id(), + PropertyDeclarationId::Longhand(longhand_id), + "Shouldn't change the declaration id!", + ); + } + + let css_wide_keyword = declaration.get_css_wide_keyword(); + if let Some(CSSWideKeyword::Revert) = css_wide_keyword { + // We intentionally don't want to insert it into `self.seen`, + // `reverted` takes care of rejecting other declarations as + // needed. + for origin in origin.following_including() { + self.reverted + .borrow_mut_for_origin(&origin) + .insert(physical_longhand_id); + } + continue; + } + + self.seen.insert(physical_longhand_id); + if origin == Origin::Author { + self.author_specified.insert(physical_longhand_id); + } + + let unset = css_wide_keyword.map_or(false, |css_wide_keyword| { + match css_wide_keyword { + CSSWideKeyword::Unset => true, + CSSWideKeyword::Inherit => inherited, + CSSWideKeyword::Initial => !inherited, + CSSWideKeyword::Revert => unreachable!(), + } + }); + + if unset { + continue; + } + + // FIXME(emilio): We should avoid generating code for logical + // longhands and just use the physical ones, then rename + // physical_longhand_id to just longhand_id. + self.apply_declaration(longhand_id, &*declaration); + } + + if ignore_colors { + for declaration in declarations_to_apply_unless_overriden.iter() { + let longhand_id = match declaration.id() { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => unreachable!(), + }; + debug_assert!(!longhand_id.is_logical()); + if self.seen.contains(longhand_id) { + continue; + } + self.apply_declaration(longhand_id, declaration); + } + } + + if Phase::is_early() { + self.fixup_font_stuff(); + self.compute_writing_mode(); + } else { + self.finished_applying_properties(); + } + } + + fn compute_writing_mode(&mut self) { + let writing_mode = match self.cascade_mode { + CascadeMode::Unvisited { .. } => { + WritingMode::new(self.context.builder.get_inherited_box()) + }, + CascadeMode::Visited { writing_mode } => writing_mode, + }; + self.context.builder.writing_mode = writing_mode; + } + + fn compute_visited_style_if_needed<E>( + &mut self, + element: Option<E>, + parent_style: Option<&ComputedValues>, + parent_style_ignoring_first_line: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + guards: &StylesheetGuards, + ) where + E: TElement, + { + let visited_rules = match self.cascade_mode { + CascadeMode::Unvisited { visited_rules } => visited_rules, + CascadeMode::Visited { .. } => return, + }; + + let visited_rules = match visited_rules { + Some(rules) => rules, + None => return, + }; + + let is_link = self.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)) + } + }; + } + + let writing_mode = self.context.builder.writing_mode; + + // We could call apply_declarations directly, but that'd cause + // another instantiation of this function which is not great. + let style = cascade_rules( + self.context.builder.device, + self.context.builder.pseudo, + visited_rules, + guards, + visited_parent!(parent_style), + visited_parent!(parent_style_ignoring_first_line), + visited_parent!(layout_parent_style), + self.context.font_metrics_provider, + CascadeMode::Visited { writing_mode }, + self.context.quirks_mode, + // 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. + /* rule_cache = */ None, + &mut *self.context.rule_cache_conditions.borrow_mut(), + element, + ); + self.context.builder.visited_style = Some(style); + } + + fn finished_applying_properties(&mut self) { + let builder = &mut self.context.builder; + + #[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_any(LonghandIdSet::padding_properties()) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING); + } + + #[cfg(feature = "servo")] + { + if let Some(font) = builder.get_font_if_mutated() { + font.compute_font_hash(); + } + } + } + + fn try_to_use_cached_reset_properties( + &mut self, + cache: Option<&'b RuleCache>, + guards: &StylesheetGuards, + ) -> bool { + let cache = match cache { + Some(cache) => cache, + None => return false, + }; + + let builder = &mut self.context.builder; + + let cached_style = match cache.find(guards, &builder) { + Some(style) => style, + None => return false, + }; + + builder.copy_reset_from(cached_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 two bits correct. + // + // Note that all the properties involved are non-inherited, so we don't + // need to do anything else other than just copying the bits over. + let reset_props_bits = + ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | + ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING; + builder.add_flags(cached_style.flags & reset_props_bits); + + true + } + + /// The default font type (which is stored in FontFamilyList's + /// `mDefaultFontType`) depends on the current lang group and generic font + /// family, so we may need to recompute it if or the family changed. + /// + /// Also, we prioritize non-document fonts here if we need to (see the pref + /// `browser.display.use_document_fonts`). + #[inline] + #[cfg(feature = "gecko")] + fn recompute_default_font_family_type_if_needed(&mut self) { + use crate::gecko_bindings::bindings; + use crate::values::computed::font::GenericFontFamily; + + if !self.seen.contains(LonghandId::XLang) && + !self.seen.contains(LonghandId::FontFamily) { + return; + } + + let use_document_fonts = static_prefs::pref!("browser.display.use_document_fonts") != 0; + let builder = &mut self.context.builder; + let (default_font_type, prioritize_user_fonts) = { + let font = builder.get_font().gecko(); + + // System fonts are all right, and should have the default font type + // set to none already, so bail out early. + if font.mFont.systemFont { + debug_assert_eq!(font.mFont.fontlist.mDefaultFontType, GenericFontFamily::None); + return; + } + + let default_font_type = unsafe { + bindings::Gecko_nsStyleFont_ComputeDefaultFontType( + builder.device.document(), + font.mGenericID, + font.mLanguage.mRawPtr, + ) + }; + + // We prioritize user fonts over document fonts if the pref is set, + // and we don't have a generic family already (or we're using + // cursive or fantasy, since they're ignored, see bug 789788), and + // we have a generic family to actually replace it with. + let prioritize_user_fonts = + !use_document_fonts && + matches!( + font.mGenericID, + GenericFontFamily::None | + GenericFontFamily::Fantasy | + GenericFontFamily::Cursive + ) && + default_font_type != GenericFontFamily::None; + + if !prioritize_user_fonts && default_font_type == font.mFont.fontlist.mDefaultFontType { + // Nothing to do. + return; + } + (default_font_type, prioritize_user_fonts) + }; + + let font = builder.mutate_font().gecko_mut(); + font.mFont.fontlist.mDefaultFontType = default_font_type; + if prioritize_user_fonts { + unsafe { + bindings::Gecko_nsStyleFont_PrioritizeUserFonts(font, default_font_type) + } + } + } + + /// Some keyword sizes depend on the font family and language. + #[cfg(feature = "gecko")] + fn recompute_keyword_font_size_if_needed(&mut self) { + use crate::values::computed::ToComputedValue; + use crate::values::specified; + + if !self.seen.contains(LonghandId::XLang) && + !self.seen.contains(LonghandId::FontFamily) { + return; + } + + let new_size = { + let font = self.context.builder.get_font(); + let info = font.clone_font_size().keyword_info; + let new_size = match info.kw { + specified::FontSizeKeyword::None => return, + _ => { + self.context.for_non_inherited_property = None; + specified::FontSize::Keyword(info).to_computed_value(self.context) + } + }; + + if font.gecko().mScriptUnconstrainedSize == new_size.size { + return; + } + + new_size + }; + + self.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(&mut self) { + use crate::gecko_bindings::bindings; + use crate::values::generics::NonNegative; + + if !self.seen.contains(LonghandId::XLang) && + !self.seen.contains(LonghandId::FontFamily) && + !self.seen.contains(LonghandId::MozMinFontSizeRatio) && + !self.seen.contains(LonghandId::FontSize) { + return; + } + + let builder = &mut self.context.builder; + let min_font_size = { + let font = builder.get_font().gecko(); + 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().gecko_mut().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): Also, why doing this _before_ handling font-size? That + /// sounds wrong. + #[cfg(feature = "gecko")] + fn unzoom_fonts_if_needed(&mut self) { + if !self.seen.contains(LonghandId::XTextZoom) { + return; + } + + let builder = &mut self.context.builder; + + let parent_zoom = builder.get_parent_font().gecko().mAllowZoomAndMinSize; + let zoom = builder.get_font().gecko().mAllowZoomAndMinSize; + if zoom == parent_zoom { + return; + } + debug_assert!( + !zoom, + "We only ever disable text zoom (in svg:text), never enable it" + ); + let device = builder.device; + builder.mutate_font().unzoom_fonts(device); + } + + /// MathML script* attributes do some very weird shit with font-size. + /// + /// Handle them specially here, separate from other font-size stuff. + /// + /// How this should interact with lang="" and font-family-dependent sizes is + /// not clear to me. For now just pretend those don't exist here. + #[cfg(feature = "gecko")] + fn handle_mathml_scriptlevel_if_needed(&mut self) { + use crate::values::generics::NonNegative; + + if !self.seen.contains(LonghandId::MathDepth) && + !self.seen.contains(LonghandId::MozScriptMinSize) && + !self.seen.contains(LonghandId::MozScriptSizeMultiplier) { + return; + } + + // If the user specifies a font-size, just let it be. + if self.seen.contains(LonghandId::FontSize) { + return; + } + + let builder = &mut self.context.builder; + let (new_size, new_unconstrained_size) = { + let font = builder.get_font().gecko(); + let parent_font = builder.get_parent_font().gecko(); + + let delta = + font.mMathDepth.saturating_sub(parent_font.mMathDepth); + + if delta == 0 { + return; + } + + let mut min = parent_font.mScriptMinSize; + if font.mAllowZoomAndMinSize { + min = builder.device.zoom_text(min); + } + + let scale = (parent_font.mScriptSizeMultiplier as f32).powi(delta as i32); + 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 = builder.mutate_font().gecko_mut(); + font.mFont.size = NonNegative(new_size); + font.mSize = NonNegative(new_size); + font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size); + } + + /// Various properties affect how font-size and font-family are computed. + /// + /// These need to be handled here, since relative lengths and ex / ch units + /// for late properties depend on these. + fn fixup_font_stuff(&mut self) { + #[cfg(feature = "gecko")] + { + self.unzoom_fonts_if_needed(); + self.recompute_default_font_family_type_if_needed(); + self.recompute_keyword_font_size_if_needed(); + self.handle_mathml_scriptlevel_if_needed(); + self.constrain_font_size_if_needed() + } + } +} |