forked from mirrors/gecko-dev
		
	 96360cdae3
			
		
	
	
		96360cdae3
		
	
	
	
	
		
			
			Same reasoning as the previous commit. Differential Revision: https://phabricator.services.mozilla.com/D146104
		
			
				
	
	
		
			426 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| /* 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
 | |
|         })
 | |
|     }
 | |
| }
 |