diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /servo/components/style/rule_tree/mod.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream/115.8.0esr.tar.xz firefox-esr-upstream/115.8.0esr.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/rule_tree/mod.rs')
-rw-r--r-- | servo/components/style/rule_tree/mod.rs | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/servo/components/style/rule_tree/mod.rs b/servo/components/style/rule_tree/mod.rs new file mode 100644 index 0000000000..01510e6207 --- /dev/null +++ b/servo/components/style/rule_tree/mod.rs @@ -0,0 +1,426 @@ +/* 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/. */ + +#![deny(unsafe_code)] + +//! The rule tree. + +use crate::applicable_declarations::{ApplicableDeclarationList, CascadePriority}; +use crate::properties::{LonghandIdSet, PropertyDeclarationBlock}; +use crate::shared_lock::{Locked, StylesheetGuards}; +use crate::stylesheets::layer_rule::LayerOrder; +use servo_arc::{Arc, ArcBorrow}; +use smallvec::SmallVec; +use std::io::{self, Write}; + +mod core; +mod level; +mod map; +mod source; +mod unsafe_box; + +pub use self::core::{RuleTree, StrongRuleNode}; +pub use self::level::{CascadeLevel, ShadowCascadeOrder}; +pub use self::source::StyleSource; + +impl RuleTree { + fn dump<W: Write>(&self, guards: &StylesheetGuards, writer: &mut W) { + let _ = writeln!(writer, " + RuleTree"); + self.root().dump(guards, writer, 0); + } + + /// Dump the rule tree to stdout. + pub fn dump_stdout(&self, guards: &StylesheetGuards) { + let mut stdout = io::stdout(); + self.dump(guards, &mut stdout); + } + + /// Inserts the given rules, that must be in proper order by specifity, and + /// returns the corresponding rule node representing the last inserted one. + /// + /// !important rules are detected and inserted into the appropriate position + /// in the rule tree. This allows selector matching to ignore importance, + /// while still maintaining the appropriate cascade order in the rule tree. + pub fn insert_ordered_rules_with_important<'a, I>( + &self, + iter: I, + guards: &StylesheetGuards, + ) -> StrongRuleNode + where + I: Iterator<Item = (StyleSource, CascadePriority)>, + { + use self::CascadeLevel::*; + let mut current = self.root().clone(); + + let mut found_important = false; + + let mut important_author = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut important_user = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut important_ua = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut transition = None; + + for (source, priority) in iter { + let level = priority.cascade_level(); + debug_assert!(!level.is_important(), "Important levels handled internally"); + + let any_important = { + let pdb = source.read(level.guard(guards)); + pdb.any_important() + }; + + if any_important { + found_important = true; + match level { + AuthorNormal { .. } => { + important_author.push((source.clone(), priority.important())) + }, + UANormal => important_ua.push((source.clone(), priority.important())), + UserNormal => important_user.push((source.clone(), priority.important())), + _ => {}, + }; + } + + // We don't optimize out empty rules, even though we could. + // + // Inspector relies on every rule being inserted in the normal level + // at least once, in order to return the rules with the correct + // specificity order. + // + // TODO(emilio): If we want to apply these optimizations without + // breaking inspector's expectations, we'd need to run + // selector-matching again at the inspector's request. That may or + // may not be a better trade-off. + if matches!(level, Transitions) && found_important { + // There can be at most one transition, and it will come at + // the end of the iterator. Stash it and apply it after + // !important rules. + debug_assert!(transition.is_none()); + transition = Some(source); + } else { + current = current.ensure_child(self.root(), source, priority); + } + } + + // Early-return in the common case of no !important declarations. + if !found_important { + return current; + } + + // Insert important declarations, in order of increasing importance, + // followed by any transition rule. + // + // Important rules are sorted differently from unimportant ones by + // shadow order and cascade order. + if !important_author.is_empty() && + important_author.first().unwrap().1 != important_author.last().unwrap().1 + { + // We only need to sort if the important rules come from + // different trees, but we need this sort to be stable. + // + // FIXME(emilio): This could maybe be smarter, probably by chunking + // the important rules while inserting, and iterating the outer + // chunks in reverse order. + // + // That is, if we have rules with levels like: -1 -1 -1 0 0 0 1 1 1, + // we're really only sorting the chunks, while keeping elements + // inside the same chunk already sorted. Seems like we could try to + // keep a SmallVec-of-SmallVecs with the chunks and just iterate the + // outer in reverse. + important_author.sort_by_key(|&(_, priority)| priority); + } + + for (source, priority) in important_author.drain(..) { + current = current.ensure_child(self.root(), source, priority); + } + + for (source, priority) in important_user.drain(..) { + current = current.ensure_child(self.root(), source, priority); + } + + for (source, priority) in important_ua.drain(..) { + current = current.ensure_child(self.root(), source, priority); + } + + if let Some(source) = transition { + current = current.ensure_child( + self.root(), + source, + CascadePriority::new(Transitions, LayerOrder::root()), + ); + } + + current + } + + /// Given a list of applicable declarations, insert the rules and return the + /// corresponding rule node. + pub fn compute_rule_node( + &self, + applicable_declarations: &mut ApplicableDeclarationList, + guards: &StylesheetGuards, + ) -> StrongRuleNode { + self.insert_ordered_rules_with_important( + applicable_declarations.drain(..).map(|d| d.for_rule_tree()), + guards, + ) + } + + /// Insert the given rules, that must be in proper order by specifity, and + /// return the corresponding rule node representing the last inserted one. + pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode + where + I: Iterator<Item = (StyleSource, CascadePriority)>, + { + self.insert_ordered_rules_from(self.root().clone(), iter) + } + + fn insert_ordered_rules_from<'a, I>(&self, from: StrongRuleNode, iter: I) -> StrongRuleNode + where + I: Iterator<Item = (StyleSource, CascadePriority)>, + { + let mut current = from; + for (source, priority) in iter { + current = current.ensure_child(self.root(), source, priority); + } + current + } + + /// Replaces a rule in a given level (if present) for another rule. + /// + /// Returns the resulting node that represents the new path, or None if + /// the old path is still valid. + pub fn update_rule_at_level( + &self, + level: CascadeLevel, + layer_order: LayerOrder, + pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, + path: &StrongRuleNode, + guards: &StylesheetGuards, + important_rules_changed: &mut bool, + ) -> Option<StrongRuleNode> { + // TODO(emilio): Being smarter with lifetimes we could avoid a bit of + // the refcount churn. + let mut current = path.clone(); + *important_rules_changed = false; + + // First walk up until the first less-or-equally specific rule. + let mut children = SmallVec::<[_; 10]>::new(); + while current.cascade_priority().cascade_level() > level { + children.push(( + current.style_source().unwrap().clone(), + current.cascade_priority(), + )); + current = current.parent().unwrap().clone(); + } + + // Then remove the one at the level we want to replace, if any. + // + // NOTE: Here we assume that only one rule can be at the level we're + // replacing. + // + // This is certainly true for HTML style attribute rules, animations and + // transitions, but could not be so for SMIL animations, which we'd need + // to special-case (isn't hard, it's just about removing the `if` and + // special cases, and replacing them for a `while` loop, avoiding the + // optimizations). + if current.cascade_priority().cascade_level() == level { + *important_rules_changed |= level.is_important(); + + let current_decls = current.style_source().unwrap().as_declarations(); + + // If the only rule at the level we're replacing is exactly the + // same as `pdb`, we're done, and `path` is still valid. + if let (Some(ref pdb), Some(ref current_decls)) = (pdb, current_decls) { + // If the only rule at the level we're replacing is exactly the + // same as `pdb`, we're done, and `path` is still valid. + // + // TODO(emilio): Another potential optimization is the one where + // we can just replace the rule at that level for `pdb`, and + // then we don't need to re-create the children, and `path` is + // also equally valid. This is less likely, and would require an + // in-place mutation of the source, which is, at best, fiddly, + // so let's skip it for now. + let is_here_already = ArcBorrow::ptr_eq(pdb, current_decls); + if is_here_already { + debug!("Picking the fast path in rule replacement"); + return None; + } + } + + if current_decls.is_some() { + current = current.parent().unwrap().clone(); + } + } + + // Insert the rule if it's relevant at this level in the cascade. + // + // These optimizations are likely to be important, because the levels + // where replacements apply (style and animations) tend to trigger + // pretty bad styling cases already. + if let Some(pdb) = pdb { + if level.is_important() { + if pdb.read_with(level.guard(guards)).any_important() { + current = current.ensure_child( + self.root(), + StyleSource::from_declarations(pdb.clone_arc()), + CascadePriority::new(level, layer_order), + ); + *important_rules_changed = true; + } + } else { + if pdb.read_with(level.guard(guards)).any_normal() { + current = current.ensure_child( + self.root(), + StyleSource::from_declarations(pdb.clone_arc()), + CascadePriority::new(level, layer_order), + ); + } + } + } + + // Now the rule is in the relevant place, push the children as + // necessary. + let rule = self.insert_ordered_rules_from(current, children.drain(..).rev()); + Some(rule) + } + + /// Returns new rule nodes without Transitions level rule. + pub fn remove_transition_rule_if_applicable(&self, path: &StrongRuleNode) -> StrongRuleNode { + // Return a clone if there is no transition level. + if path.cascade_level() != CascadeLevel::Transitions { + return path.clone(); + } + + path.parent().unwrap().clone() + } + + /// Returns new rule node without rules from declarative animations. + pub fn remove_animation_rules(&self, path: &StrongRuleNode) -> StrongRuleNode { + // Return a clone if there are no animation rules. + if !path.has_animation_or_transition_rules() { + return path.clone(); + } + + let iter = path + .self_and_ancestors() + .take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride); + let mut last = path; + let mut children = SmallVec::<[_; 10]>::new(); + for node in iter { + if !node.cascade_level().is_animation() { + children.push(( + node.style_source().unwrap().clone(), + node.cascade_priority(), + )); + } + last = node; + } + + let rule = self + .insert_ordered_rules_from(last.parent().unwrap().clone(), children.drain(..).rev()); + rule + } + + /// Returns new rule node by adding animation rules at transition level. + /// The additional rules must be appropriate for the transition + /// level of the cascade, which is the highest level of the cascade. + /// (This is the case for one current caller, the cover rule used + /// for CSS transitions.) + pub fn add_animation_rules_at_transition_level( + &self, + path: &StrongRuleNode, + pdb: Arc<Locked<PropertyDeclarationBlock>>, + guards: &StylesheetGuards, + ) -> StrongRuleNode { + let mut dummy = false; + self.update_rule_at_level( + CascadeLevel::Transitions, + LayerOrder::root(), + Some(pdb.borrow_arc()), + path, + guards, + &mut dummy, + ) + .expect("Should return a valid rule node") + } +} + +impl StrongRuleNode { + /// Get an iterator for this rule node and its ancestors. + pub fn self_and_ancestors(&self) -> SelfAndAncestors { + SelfAndAncestors { + current: Some(self), + } + } + + /// Returns true if there is either animation or transition level rule. + pub fn has_animation_or_transition_rules(&self) -> bool { + self.self_and_ancestors() + .take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride) + .any(|node| node.cascade_level().is_animation()) + } + + /// Get a set of properties whose CascadeLevel are higher than Animations + /// but not equal to Transitions. + /// + /// If there are any custom properties, we set the boolean value of the + /// returned tuple to true. + pub fn get_properties_overriding_animations( + &self, + guards: &StylesheetGuards, + ) -> (LonghandIdSet, bool) { + use crate::properties::PropertyDeclarationId; + + // We want to iterate over cascade levels that override the animations + // level, i.e. !important levels and the transitions level. + // + // However, we actually want to skip the transitions level because + // although it is higher in the cascade than animations, when both + // transitions and animations are present for a given element and + // property, transitions are suppressed so that they don't actually + // override animations. + let iter = self + .self_and_ancestors() + .skip_while(|node| node.cascade_level() == CascadeLevel::Transitions) + .take_while(|node| node.cascade_level() > CascadeLevel::Animations); + let mut result = (LonghandIdSet::new(), false); + for node in iter { + let style = node.style_source().unwrap(); + for (decl, important) in style + .read(node.cascade_level().guard(guards)) + .declaration_importance_iter() + { + // Although we are only iterating over cascade levels that + // override animations, in a given property declaration block we + // can have a mixture of !important and non-!important + // declarations but only the !important declarations actually + // override animations. + if important.important() { + match decl.id() { + PropertyDeclarationId::Longhand(id) => result.0.insert(id), + PropertyDeclarationId::Custom(_) => result.1 = true, + } + } + } + } + result + } +} + +/// An iterator over a rule node and its ancestors. +#[derive(Clone)] +pub struct SelfAndAncestors<'a> { + current: Option<&'a StrongRuleNode>, +} + +impl<'a> Iterator for SelfAndAncestors<'a> { + type Item = &'a StrongRuleNode; + + fn next(&mut self) -> Option<Self::Item> { + self.current.map(|node| { + self.current = node.parent(); + node + }) + } +} |