/* 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::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(&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, { 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, { 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, { 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>>, path: &StrongRuleNode, guards: &StylesheetGuards, important_rules_changed: &mut bool, ) -> Option { // 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(); } let cascade_priority = CascadePriority::new(level, layer_order); // 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 + priority we're replacing, // which holds because we only use this for HTML style attribute, animations and transition // rules. // // This is certainly true for HTML style attribute rules, animations, transitions, and // SMIL. if current.cascade_priority() == cascade_priority { *important_rules_changed |= level.is_important(); let current_decls = current.style_source().unwrap().get(); // 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) = pdb { // 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, ¤t_decls.borrow_arc()); if is_here_already { debug!("Picking the fast path in rule replacement"); return None; } } 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()), cascade_priority, ); *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()), cascade_priority, ); } } } // 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 } } 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.current.map(|node| { self.current = node.parent(); node }) } }