summaryrefslogtreecommitdiffstats
path: root/servo/components/style/rule_tree/mod.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /servo/components/style/rule_tree/mod.rs
parentInitial commit. (diff)
downloadfirefox-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.rs426
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
+ })
+ }
+}