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;
#[derive(Copy, Clone)]
#[allow(missing_docs)]
pub enum FirstLineReparenting<'a> {
No,
Yes {
style_to_reparent: &'a ComputedValues,
},
}
pub fn cascade<E>(
stylist: &Stylist,
pseudo: Option<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
first_line_reparenting: FirstLineReparenting,
visited_rules: Option<&StrongRuleNode>,
cascade_input_flags: ComputedValueFlags,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
cascade_rules(
stylist,
pseudo,
rule_node,
guards,
parent_style,
layout_parent_style,
first_line_reparenting,
CascadeMode::Unvisited { visited_rules },
cascade_input_flags,
rule_cache,
rule_cache_conditions,
element,
)
}
struct DeclarationIterator<'a> {
guards: &'a StylesheetGuards<'a>,
restriction: Option<PropertyFlags>,
current_rule_node: Option<&'a StrongRuleNode>,
declarations: DeclarationImportanceIterator<'a>,
origin: Origin,
importance: Importance,
priority: CascadePriority,
}
impl<'a> DeclarationIterator<'a> {
#[inline]
fn new(
rule_node: &'a StrongRuleNode,
guards: &'a StylesheetGuards,
pseudo: Option<&PseudoElement>,
) -> Self {
let restriction = pseudo.and_then(|p| p.property_restriction());
let mut iter = Self {
guards,
current_rule_node: Some(rule_node),
origin: Origin::UserAgent,
importance: Importance::Normal,
priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()),
declarations: DeclarationImportanceIterator::default(),
restriction,
};
iter.update_for_node(rule_node);
iter
}
fn update_for_node(&mut self, node: &'a StrongRuleNode) {
self.priority = node.cascade_priority();
let level = self.priority.cascade_level();
self.origin = level.origin();
self.importance = level.importance();
let guard = match self.origin {
Origin::Author => self.guards.author,
Origin::User | Origin::UserAgent => self.guards.ua_or_user,
};
self.declarations = match node.style_source() {
Some(source) => source.read(guard).declaration_importance_iter(),
None => DeclarationImportanceIterator::default(),
};
}
}
impl<'a> Iterator for DeclarationIterator<'a> {
type Item = (&'a PropertyDeclaration, CascadePriority);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some((decl, importance)) = self.declarations.next_back() {
if self.importance != importance {
continue;
}
if let Some(restriction) = self.restriction {
if let PropertyDeclarationId::Longhand(id) = decl.id() {
if !id.flags().contains(restriction) && self.origin != Origin::UserAgent {
continue;
}
}
}
return Some((decl, self.priority));
}
let next_node = self.current_rule_node.take()?.parent()?;
self.current_rule_node = Some(next_node);
self.update_for_node(next_node);
}
}
}
fn cascade_rules<E>(
stylist: &Stylist,
pseudo: Option<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
first_line_reparenting: FirstLineReparenting,
cascade_mode: CascadeMode,
cascade_input_flags: ComputedValueFlags,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
apply_declarations(
stylist,
pseudo,
rule_node,
guards,
DeclarationIterator::new(rule_node, guards, pseudo),
parent_style,
layout_parent_style,
first_line_reparenting,
cascade_mode,
cascade_input_flags,
rule_cache,
rule_cache_conditions,
element,
)
}
#[derive(Clone, Copy)]
pub enum CascadeMode<'a, 'b> {
Unvisited {
visited_rules: Option<&'a StrongRuleNode>,
},
Visited {
unvisited_context: &'a computed::Context<'b>,
},
}
fn iter_declarations<'builder, 'decls: 'builder>(
iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
declarations: &mut Declarations<'decls>,
mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>,
) {
for (declaration, priority) in iter {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
if let Some(ref mut builder) = custom_builder {
builder.cascade(declaration, priority);
}
} else {
let id = declaration.id().as_longhand().unwrap();
declarations.note_declaration(declaration, priority, id);
if CustomPropertiesBuilder::might_have_non_custom_dependency(id, declaration) {
if let Some(ref mut builder) = custom_builder {
builder.maybe_note_non_custom_dependency(id, declaration);
}
}
}
}
}
pub fn apply_declarations<'a, E, I>(
stylist: &'a Stylist,
pseudo: Option<&'a PseudoElement>,
rules: &StrongRuleNode,
guards: &StylesheetGuards,
iter: I,
parent_style: Option<&'a ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
first_line_reparenting: FirstLineReparenting<'a>,
cascade_mode: CascadeMode,
cascade_input_flags: ComputedValueFlags,
rule_cache: Option<&'a RuleCache>,
rule_cache_conditions: &'a mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement + 'a,
I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>,
{
debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
let device = stylist.device();
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
let container_size_query =
ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some());
let mut context = computed::Context::new(
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;
using_cached_reset_properties = false;
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));
builder.build(DeferFontRelativeCustomPropertyResolution::Yes)
};
cascade.apply_prioritary_properties(&mut context, &declarations, &mut shorthand_cache);
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 {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
context.builder.build()
}
type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>;
fn tweak_when_ignoring_colors(
context: &computed::Context,
longhand_id: LonghandId,
origin: Origin,
declaration: &mut Cow<PropertyDeclaration>,
declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden,
) {
use crate::values::computed::ToComputedValue;
use crate::values::specified::Color;
if !longhand_id.ignored_when_document_colors_disabled() {
return;
}
let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent);
if is_ua_or_user_rule {
return;
}
#[cfg(feature = "gecko")]
{
let forced = context
.builder
.get_inherited_text()
.clone_forced_color_adjust();
if forced == computed::ForcedColorAdjust::None {
return;
}
}
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 {
color
.to_computed_value(context)
.resolve_to_absolute(&AbsoluteColor::BLACK)
.alpha
}
match **declaration {
PropertyDeclaration::CSSWideKeyword(..) => return,
PropertyDeclaration::BackgroundColor(ref color) => {
if color.honored_in_forced_colors_mode(true) {
return;
}
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) => {
if color
.0
.honored_in_forced_colors_mode(true)
{
return;
}
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()),
))
}
},
#[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;
}
}
},
_ => {
if let Some(color) = declaration.color_value() {
if color.honored_in_forced_colors_mode(false) {
return;
}
}
},
}
*declaration.to_mut() =
PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert);
}
type DeclarationIndex = u16;
#[derive(Copy, Clone)]
struct PrioritaryDeclarationPosition {
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,
}
#[derive(Default)]
struct Declarations<'a> {
has_prioritary_properties: bool,
longhand_declarations: SmallVec<[Declaration<'a>; 64]>,
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 {
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 {
position.most_important = new_index;
} else {
self.longhand_declarations[position.least_important as usize].next_index = new_index;
}
position.least_important = new_index;
}
fn note_declaration(
&mut self,
decl: &'a PropertyDeclaration,
priority: CascadePriority,
id: LonghandId,
) {
if let Some(id) = PrioritaryPropertyId::from_longhand(id) {
self.note_prioritary_property(id);
}
self.longhand_declarations.push(Declaration {
decl,
priority,
next_index: 0,
});
}
}
struct Cascade<'b> {
first_line_reparenting: FirstLineReparenting<'b>,
ignore_colors: bool,
seen: LonghandIdSet,
author_specified: LonghandIdSet,
reverted_set: LonghandIdSet,
reverted: FxHashMap<LonghandId, (CascadePriority, bool)>,
declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden,
}
impl<'b> Cascade<'b> {
fn new(first_line_reparenting: FirstLineReparenting<'b>, ignore_colors: bool) -> Self {
Self {
first_line_reparenting,
ignore_colors,
seen: LonghandIdSet::default(),
author_specified: LonghandIdSet::default(),
reverted_set: Default::default(),
reverted: Default::default(),
declarations_to_apply_unless_overridden: Default::default(),
}
}
fn substitute_variables_if_needed<'cache, 'decl>(
&self,
context: &mut computed::Context,
shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
declaration: &'decl PropertyDeclaration,
) -> Cow<'decl, PropertyDeclaration>
where
'cache: 'decl,
{
let declaration = match *declaration {
PropertyDeclaration::WithVariables(ref declaration) => declaration,
ref d => return Cow::Borrowed(d),
};
if !declaration.id.inherited() {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
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; }
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,
) {
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());
self.recompute_font_size_for_zoom_change(&mut context.builder);
}
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);
}
}
#[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);
}
}
apply!(FontWeight);
apply!(FontStretch);
apply!(FontStyle);
#[cfg(feature = "gecko")]
apply!(FontSizeAdjust);
#[cfg(feature = "gecko")]
apply!(ForcedColorAdjust);
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);
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;
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());
(CASCADE_PROPERTY[longhand_id as usize])(&declaration, context);
}
fn compute_visited_style_if_needed<E>(
&self,
context: &mut computed::Context,
element: Option<E>,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
visited_rules: &StrongRuleNode,
guards: &StylesheetGuards,
) where
E: TElement,
{
let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link();
macro_rules! visited_parent {
($parent:expr) => {
if is_link {
$parent
} else {
$parent.map(|p| p.visited_style().unwrap_or(p))
}
};
}
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,
},
Default::default(),
None, &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);
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
}
#[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
};
builder.mutate_font().mFont.family.families =
FontFamily::generic(default_font_type).families.clone();
}
#[inline]
#[cfg(feature = "gecko")]
fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) {
use crate::gecko_bindings::bindings;
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);
}
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);
}
#[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;
}
#[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));
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);
}
#[cfg(feature = "gecko")]
fn recompute_math_font_size_if_needed(&self, context: &mut computed::Context) {
use crate::values::generics::NonNegative;
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;
fn scale_factor_for_math_depth_change(
parent_math_depth: i32,
computed_math_depth: i32,
parent_script_percent_scale_down: Option<f32>,
parent_script_script_percent_scale_down: Option<f32>,
) -> f32 {
let mut a = parent_math_depth;
let mut b = computed_math_depth;
let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE;
let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c);
let scale_between_0_and_2 =
parent_script_script_percent_scale_down.unwrap_or_else(|| c * c);
let mut s = 1.0;
let mut invert_scale_factor = false;
if a == b {
return s;
}
if b < a {
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);
}
let scale = {
let font_metrics = context.query_font_metrics(
FontBaseSize::InheritedStyle,
FontMetricsOrientation::Horizontal,
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. {
if parent_size <= min {
(parent_size, new_unconstrained_size)
} else {
(min.max(new_size), new_unconstrained_size)
}
} else {
(
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);
}
}