servo/components/style/properties/cascade.rs
Emilio Cobos Álvarez 1e5806610b style: Don't consider system-ui valid for user font prioritization
Since the user can't configure it, at least from the UI (we could add UI
for it but it's unclear it'd be worth it).

Differential Revision: https://phabricator.services.mozilla.com/D125182
2023-05-30 23:26:00 +02:00

1112 lines
39 KiB
Rust

/* 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::logical_geometry::WritingMode;
use crate::media_queries::Device;
use crate::properties::{
CSSWideKeyword, ComputedValueFlags, ComputedValues, DeclarationImportanceIterator, Importance,
LonghandId, LonghandIdSet, PropertyDeclaration, PropertyDeclarationId, PropertyFlags,
ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY,
};
use crate::rule_cache::{RuleCache, RuleCacheConditions};
use crate::rule_tree::StrongRuleNode;
use crate::selector_parser::PseudoElement;
use crate::shared_lock::StylesheetGuards;
use crate::style_adjuster::StyleAdjuster;
use crate::stylesheets::{Origin, PerOrigin};
use crate::values::{computed, specified};
use servo_arc::Arc;
use smallvec::SmallVec;
use std::borrow::Cow;
use std::cell::RefCell;
/// 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>,
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,
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>,
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,
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>,
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,
quirks_mode,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
};
let using_cached_reset_properties = {
let mut cascade = Cascade::new(&mut context, cascade_mode);
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
cascade.apply_properties::<EarlyProperties, _>(
ApplyResetProperties::Yes,
declarations.iter().cloned(),
&mut shorthand_cache,
);
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(),
&mut shorthand_cache,
);
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(
context: &computed::Context,
longhand_id: LonghandId,
origin: Origin,
declaration: &mut Cow<PropertyDeclaration>,
declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden,
) {
use crate::values::specified::Color;
use crate::values::computed::ToComputedValue;
use cssparser::RGBA;
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 context.builder.pseudo.map_or(false, |p| p.is_color_swatch()) &&
longhand_id == LonghandId::BackgroundColor
{
return;
}
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
// We assume here currentColor is opaque.
let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255));
color.alpha
}
// A few special-cases ahead.
match **declaration {
PropertyDeclaration::BackgroundColor(ref color) => {
// We honor system colors.
if color.is_system() {
return;
}
// 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, context);
if alpha != 0 {
let mut color = context.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 system colors.
if color.0.is_system() {
return;
}
if alpha_channel(&color.0, context) == 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 context.builder.get_parent_inherited_text().clone_color().alpha == 0 {
let color = context.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;
}
}
},
_ => {
// We honor transparent and system colors more generally for all
// colors.
//
// 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.is_system() || alpha_channel(color, context) == 0 {
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, 'cache>(
&mut self,
declaration: &'decl PropertyDeclaration,
cache: &'cache mut ShorthandsWithPropertyReferencesCache,
) -> 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() {
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);
},
_ => {},
}
}
declaration.value.substitute_variables(
declaration.id,
self.context.builder.writing_mode,
self.context.builder.custom_properties.as_ref(),
self.context.quirks_mode,
self.context.device(),
cache,
)
}
#[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,
mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
) 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, &mut shorthand_cache);
// 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,
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),
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.
None, // rule_cache
&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);
}
if self
.author_specified
.contains(LonghandId::FontFamily)
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY);
}
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::FontSynthesis)
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS);
}
#[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.family.is_system_font {
debug_assert_eq!(
font.mFont.family.families.fallback,
GenericFontFamily::None
);
return;
}
let generic = font.mFont.family.families.single_generic().unwrap_or(GenericFontFamily::None);
let default_font_type = unsafe {
bindings::Gecko_nsStyleFont_ComputeDefaultFontType(
builder.device.document(),
generic,
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 &&
default_font_type != GenericFontFamily::None &&
!generic.valid_for_user_font_prioritization();
if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback {
// Nothing to do.
return;
}
(default_font_type, prioritize_user_fonts)
};
let font = builder.mutate_font().gecko_mut();
font.mFont.family.families.fallback = default_font_type;
if prioritize_user_fonts {
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(&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()
}
}
}