use crate::logical_geometry::WritingMode;
use crate::properties::{ComputedValues, StyleBuilder};
use crate::rule_tree::StrongRuleNode;
use crate::selector_parser::PseudoElement;
use crate::shared_lock::StylesheetGuards;
use crate::values::computed::{NonNegativeLength, Zoom};
use crate::values::specified::color::ColorSchemeFlags;
use fxhash::FxHashMap;
use servo_arc::Arc;
use smallvec::SmallVec;
#[derive(Clone, Debug, Default)]
pub struct RuleCacheConditions {
uncacheable: bool,
font_size: Option<NonNegativeLength>,
line_height: Option<NonNegativeLength>,
writing_mode: Option<WritingMode>,
color_scheme: Option<ColorSchemeFlags>,
}
impl RuleCacheConditions {
pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
debug_assert!(self.font_size.map_or(true, |f| f == font_size));
self.font_size = Some(font_size);
}
pub fn set_line_height_dependency(&mut self, line_height: NonNegativeLength) {
debug_assert!(self.line_height.map_or(true, |l| l == line_height));
self.line_height = Some(line_height);
}
pub fn set_color_scheme_dependency(&mut self, color_scheme: ColorSchemeFlags) {
debug_assert!(self.color_scheme.map_or(true, |cs| cs == color_scheme));
self.color_scheme = Some(color_scheme);
}
pub fn set_uncacheable(&mut self) {
self.uncacheable = true;
}
pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
self.writing_mode = Some(writing_mode);
}
fn cacheable(&self) -> bool {
!self.uncacheable
}
}
#[derive(Debug)]
struct CachedConditions {
font_size: Option<NonNegativeLength>,
line_height: Option<NonNegativeLength>,
color_scheme: Option<ColorSchemeFlags>,
writing_mode: Option<WritingMode>,
zoom: Zoom,
}
impl CachedConditions {
fn matches(&self, style: &StyleBuilder) -> bool {
if style.effective_zoom != self.zoom {
return false;
}
if let Some(fs) = self.font_size {
if style.get_font().clone_font_size().computed_size != fs {
return false;
}
}
if let Some(lh) = self.line_height {
let new_line_height =
style
.device
.calc_line_height(&style.get_font(), style.writing_mode, None);
if new_line_height != lh {
return false;
}
}
if let Some(cs) = self.color_scheme {
if style.get_inherited_ui().color_scheme_bits() != cs {
return false;
}
}
if let Some(wm) = self.writing_mode {
if style.writing_mode != wm {
return false;
}
}
true
}
}
pub struct RuleCache {
map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>,
}
impl RuleCache {
pub fn new() -> Self {
Self {
map: FxHashMap::default(),
}
}
fn get_rule_node_for_cache<'r>(
guards: &StylesheetGuards,
mut rule_node: Option<&'r StrongRuleNode>,
) -> Option<&'r StrongRuleNode> {
use crate::rule_tree::CascadeLevel;
while let Some(node) = rule_node {
let priority = node.cascade_priority();
let cascade_level = priority.cascade_level();
let should_try_to_skip =
cascade_level.is_animation() ||
matches!(cascade_level, CascadeLevel::PresHints) ||
priority.layer_order().is_style_attribute_layer();
if !should_try_to_skip {
break;
}
if let Some(source) = node.style_source() {
let decls = source.get().read_with(cascade_level.guard(guards));
if decls.contains_any_reset() {
break;
}
}
rule_node = node.parent();
}
rule_node
}
pub fn find(
&self,
guards: &StylesheetGuards,
builder_with_early_props: &StyleBuilder,
) -> Option<&ComputedValues> {
if builder_with_early_props
.pseudo
.and_then(|p| p.property_restriction())
.is_some()
{
return None;
}
let rules = builder_with_early_props.rules.as_ref();
let rules = Self::get_rule_node_for_cache(guards, rules)?;
let cached_values = self.map.get(rules)?;
for &(ref conditions, ref values) in cached_values.iter() {
if conditions.matches(builder_with_early_props) {
debug!("Using cached reset style with conditions {:?}", conditions);
return Some(&**values);
}
}
None
}
pub fn insert_if_possible(
&mut self,
guards: &StylesheetGuards,
style: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>,
conditions: &RuleCacheConditions,
) -> bool {
if !conditions.cacheable() {
return false;
}
if pseudo.and_then(|p| p.property_restriction()).is_some() {
return false;
}
let rules = style.rules.as_ref();
let rules = match Self::get_rule_node_for_cache(guards, rules) {
Some(r) => r.clone(),
None => return false,
};
debug!(
"Inserting cached reset style with conditions {:?}",
conditions
);
let cached_conditions = CachedConditions {
writing_mode: conditions.writing_mode,
font_size: conditions.font_size,
line_height: conditions.line_height,
color_scheme: conditions.color_scheme,
zoom: style.effective_zoom,
};
self.map
.entry(rules)
.or_default()
.push((cached_conditions, style.clone()));
true
}
}