mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
By modeling it as a separate layer that behaves somewhat specially. See https://github.com/w3c/csswg-drafts/issues/6872. The remaining revert-layer tests that we fail are because either we don't implement a feature (like @property) or because it's used in keyframes (where revert is a bit unspecified and we have existing issues with it). Differential Revision: https://phabricator.services.mozilla.com/D133373
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, RULE_NODE_SIZE};
|
|
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
|
|
})
|
|
}
|
|
}
|