style: Add support for the revert-layer keyword

This patch looks bigger than it is, but it's mostly because
of plumbing.

To implement revert-layer we need not only the cascade origin of the
declaration, but the whole cascade level, plus also the layer order.

In order to do this, encapsulate these two things inside a 32-bit
`CascadePriority` struct and plumb it through the rule tree and so on.
This allows us to remove the packing and unpacking of CascadeLevel,
though I kept the ShadowCascadeOrder limit for now in case we need to
reintroduce it.

Fix `!important` behavior of layers while at it (implementing it in
`CascadeLevel::cmp`, spec quote included since it was tricky to find)
since some revert-layer tests were depending on it.

The style attribute test is failing now, but follow-up commit fixes
it, see spec issue.

In terms of the actual keyword implementation, it's sort of
straight-forward: We implement revert and revert-layer in a shared
way, by storing the cascade priority that reverted it.

Differential Revision: https://phabricator.services.mozilla.com/D133372
This commit is contained in:
Emilio Cobos Álvarez 2023-06-06 16:49:13 +02:00 committed by Oriol Brufau
parent 26c10339e3
commit 8bb7d98f0c
17 changed files with 354 additions and 251 deletions

View file

@ -4,8 +4,9 @@
#![allow(unsafe_code)]
use crate::properties::Importance;
use crate::applicable_declarations::CascadePriority;
use crate::shared_lock::StylesheetGuards;
use crate::stylesheets::layer_rule::LayerOrder;
use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
use parking_lot::RwLock;
use smallvec::SmallVec;
@ -66,7 +67,7 @@ impl MallocSizeOf for RuleTree {
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct ChildKey(CascadeLevel, ptr::NonNull<()>);
struct ChildKey(CascadePriority, ptr::NonNull<()>);
unsafe impl Send for ChildKey {}
unsafe impl Sync for ChildKey {}
@ -219,8 +220,8 @@ struct RuleNode {
/// None for the root node.
source: Option<StyleSource>,
/// The cascade level this rule is positioned at.
level: CascadeLevel,
/// The cascade level + layer order this rule is positioned at.
cascade_priority: CascadePriority,
/// The refcount of this node.
///
@ -316,14 +317,14 @@ impl RuleNode {
root: WeakRuleNode,
parent: StrongRuleNode,
source: StyleSource,
level: CascadeLevel,
cascade_priority: CascadePriority,
) -> Self {
debug_assert!(root.p.parent.is_none());
RuleNode {
root: Some(root),
parent: Some(parent),
source: Some(source),
level: level,
cascade_priority,
refcount: AtomicUsize::new(1),
children: Default::default(),
approximate_free_count: AtomicUsize::new(0),
@ -336,7 +337,7 @@ impl RuleNode {
root: None,
parent: None,
source: None,
level: CascadeLevel::UANormal,
cascade_priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()),
refcount: AtomicUsize::new(1),
approximate_free_count: AtomicUsize::new(0),
children: Default::default(),
@ -346,7 +347,7 @@ impl RuleNode {
fn key(&self) -> ChildKey {
ChildKey(
self.level,
self.cascade_priority,
self.source
.as_ref()
.expect("Called key() on the root node")
@ -554,20 +555,20 @@ impl StrongRuleNode {
&self,
root: &StrongRuleNode,
source: StyleSource,
level: CascadeLevel,
cascade_priority: CascadePriority,
) -> StrongRuleNode {
use parking_lot::RwLockUpgradableReadGuard;
debug_assert!(
self.p.level <= level,
self.p.cascade_priority <= cascade_priority,
"Should be ordered (instead {:?} > {:?}), from {:?} and {:?}",
self.p.level,
level,
self.p.cascade_priority,
cascade_priority,
self.p.source,
source,
);
let key = ChildKey(level, source.key());
let key = ChildKey(cascade_priority, source.key());
let children = self.p.children.upgradable_read();
if let Some(child) = children.get(&key, |node| node.p.key()) {
// Sound to call because we read-locked the parent's children.
@ -584,7 +585,7 @@ impl StrongRuleNode {
root.downgrade(),
self.clone(),
source,
level,
cascade_priority,
)));
// Sound to call because we still own a strong reference to
// this node, through the `node` variable itself that we are
@ -602,14 +603,22 @@ impl StrongRuleNode {
self.p.source.as_ref()
}
/// The cascade level for this node
pub fn cascade_level(&self) -> CascadeLevel {
self.p.level
/// The cascade priority.
#[inline]
pub fn cascade_priority(&self) -> CascadePriority {
self.p.cascade_priority
}
/// Get the importance that this rule node represents.
pub fn importance(&self) -> Importance {
self.p.level.importance()
/// The cascade level.
#[inline]
pub fn cascade_level(&self) -> CascadeLevel {
self.cascade_priority().cascade_level()
}
/// The importance.
#[inline]
pub fn importance(&self) -> crate::properties::Importance {
self.cascade_level().importance()
}
/// Returns whether this node has any child, only intended for testing

View file

@ -29,7 +29,7 @@ use crate::stylesheets::Origin;
/// [3]: https://html.spec.whatwg.org/multipage/#presentational-hints
/// [4]: https://drafts.csswg.org/css-scoping/#shadow-cascading
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)]
pub enum CascadeLevel {
/// Normal User-Agent rules.
UANormal,
@ -69,82 +69,44 @@ pub enum CascadeLevel {
}
impl CascadeLevel {
/// Pack this cascade level in a single byte.
///
/// We have 10 levels, which we can represent with 4 bits, and then a
/// cascade order optionally, which we can clamp to three bits max, and
/// represent with a fourth bit for the sign.
///
/// So this creates: SOOODDDD
///
/// Where `S` is the sign of the order (one if negative, 0 otherwise), `O`
/// is the absolute value of the order, and `D`s are the discriminant.
#[inline]
pub fn to_byte_lossy(&self) -> u8 {
let (discriminant, order) = match *self {
Self::UANormal => (0, 0),
Self::UserNormal => (1, 0),
Self::PresHints => (2, 0),
/// Convert this level from "unimportant" to "important".
pub fn important(&self) -> Self {
match *self {
Self::UANormal => Self::UAImportant,
Self::UserNormal => Self::UserImportant,
Self::AuthorNormal {
shadow_cascade_order,
} => (3, shadow_cascade_order.0),
Self::SMILOverride => (4, 0),
Self::Animations => (5, 0),
Self::AuthorImportant {
shadow_cascade_order,
} => (6, shadow_cascade_order.0),
Self::UserImportant => (7, 0),
Self::UAImportant => (8, 0),
Self::Transitions => (9, 0),
};
debug_assert_eq!(discriminant & 0xf, discriminant);
if order == 0 {
return discriminant;
} => Self::AuthorImportant {
shadow_cascade_order: -shadow_cascade_order,
},
Self::PresHints |
Self::SMILOverride |
Self::Animations |
Self::AuthorImportant { .. } |
Self::UserImportant |
Self::UAImportant |
Self::Transitions => *self,
}
let negative = order < 0;
let value = std::cmp::min(order.abs() as u8, 0b111);
(negative as u8) << 7 | value << 4 | discriminant
}
/// Convert back from the single-byte representation of the cascade level
/// explained above.
#[inline]
pub fn from_byte(b: u8) -> Self {
let order = {
let abs = ((b & 0b01110000) >> 4) as i8;
let negative = b & 0b10000000 != 0;
if negative {
-abs
} else {
abs
}
};
let discriminant = b & 0xf;
let level = match discriminant {
0 => Self::UANormal,
1 => Self::UserNormal,
2 => Self::PresHints,
3 => {
return Self::AuthorNormal {
shadow_cascade_order: ShadowCascadeOrder(order),
}
/// Convert this level from "important" to "non-important".
pub fn unimportant(&self) -> Self {
match *self {
Self::UAImportant => Self::UANormal,
Self::UserImportant => Self::UserNormal,
Self::AuthorImportant {
shadow_cascade_order,
} => Self::AuthorNormal {
shadow_cascade_order: -shadow_cascade_order,
},
4 => Self::SMILOverride,
5 => Self::Animations,
6 => {
return Self::AuthorImportant {
shadow_cascade_order: ShadowCascadeOrder(order),
}
},
7 => Self::UserImportant,
8 => Self::UAImportant,
9 => Self::Transitions,
_ => unreachable!("Didn't expect {} as a discriminant", discriminant),
};
debug_assert_eq!(order, 0, "Didn't expect an order value for {:?}", level);
level
Self::PresHints |
Self::SMILOverride |
Self::Animations |
Self::AuthorNormal { .. } |
Self::UserNormal |
Self::UANormal |
Self::Transitions => *self,
}
}
/// Select a lock guard for this level
@ -231,6 +193,12 @@ impl CascadeLevel {
pub struct ShadowCascadeOrder(i8);
impl ShadowCascadeOrder {
/// We keep a maximum of 3 bits of order as a limit so that we can pack
/// CascadeLevel in one byte by using half of it for the order, if that ends
/// up being necessary.
const MAX: i8 = 0b111;
const MIN: i8 = -Self::MAX;
/// A level for the outermost shadow tree (the shadow tree we own, and the
/// ones from the slots we're slotted in).
#[inline]
@ -256,7 +224,9 @@ impl ShadowCascadeOrder {
#[inline]
pub fn dec(&mut self) {
debug_assert!(self.0 < 0);
self.0 = self.0.saturating_sub(1);
if self.0 != Self::MIN {
self.0 -= 1;
}
}
/// The level, moving inwards. We should only move inwards if we're
@ -264,7 +234,9 @@ impl ShadowCascadeOrder {
#[inline]
pub fn inc(&mut self) {
debug_assert_ne!(self.0, -1);
self.0 = self.0.saturating_add(1);
if self.0 != Self::MAX {
self.0 += 1;
}
}
}

View file

@ -6,9 +6,10 @@
//! The rule tree.
use crate::applicable_declarations::ApplicableDeclarationList;
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};
@ -47,21 +48,22 @@ impl RuleTree {
guards: &StylesheetGuards,
) -> StrongRuleNode
where
I: Iterator<Item = (StyleSource, CascadeLevel)>,
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, ShadowCascadeOrder); 4]>::new();
let mut important_user = SmallVec::<[StyleSource; 4]>::new();
let mut important_ua = SmallVec::<[StyleSource; 4]>::new();
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, level) in iter {
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()
@ -70,13 +72,9 @@ impl RuleTree {
if any_important {
found_important = true;
match level {
AuthorNormal {
shadow_cascade_order,
} => {
important_author.push((source.clone(), shadow_cascade_order));
},
UANormal => important_ua.push(source.clone()),
UserNormal => important_user.push(source.clone()),
AuthorNormal { .. } => important_author.push((source.clone(), priority.important())),
UANormal => important_ua.push((source.clone(), priority.important())),
UserNormal => important_user.push((source.clone(), priority.important())),
_ => {},
};
}
@ -98,7 +96,7 @@ impl RuleTree {
debug_assert!(transition.is_none());
transition = Some(source);
} else {
current = current.ensure_child(self.root(), source, level);
current = current.ensure_child(self.root(), source, priority);
}
}
@ -110,10 +108,8 @@ impl RuleTree {
// Insert important declarations, in order of increasing importance,
// followed by any transition rule.
//
// Inner shadow wins over same-tree, which wins over outer-shadow.
//
// We negate the shadow cascade order to preserve the right PartialOrd
// behavior.
// 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
{
@ -129,29 +125,27 @@ impl RuleTree {
// 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(|&(_, order)| -order);
important_author.sort_by_key(|&(_, priority)| priority);
}
for (source, shadow_cascade_order) in important_author.drain(..) {
current = current.ensure_child(
self.root(),
source,
AuthorImportant {
shadow_cascade_order: -shadow_cascade_order,
},
);
for (source, priority) in important_author.drain(..) {
current = current.ensure_child(self.root(), source, priority);
}
for source in important_user.drain(..) {
current = current.ensure_child(self.root(), source, UserImportant);
for (source, priority) in important_user.drain(..) {
current = current.ensure_child(self.root(), source, priority);
}
for source in important_ua.drain(..) {
current = current.ensure_child(self.root(), source, UAImportant);
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, Transitions);
current = current.ensure_child(
self.root(),
source,
CascadePriority::new(Transitions, LayerOrder::root()),
);
}
current
@ -174,18 +168,18 @@ impl RuleTree {
/// 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, CascadeLevel)>,
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, CascadeLevel)>,
I: Iterator<Item = (StyleSource, CascadePriority)>,
{
let mut current = from;
for (source, level) in iter {
current = current.ensure_child(self.root(), source, level);
for (source, priority) in iter {
current = current.ensure_child(self.root(), source, priority);
}
current
}
@ -197,6 +191,7 @@ impl RuleTree {
pub fn update_rule_at_level(
&self,
level: CascadeLevel,
layer_order: LayerOrder,
pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
path: &StrongRuleNode,
guards: &StylesheetGuards,
@ -209,10 +204,10 @@ impl RuleTree {
// First walk up until the first less-or-equally specific rule.
let mut children = SmallVec::<[_; 10]>::new();
while current.cascade_level() > level {
while current.cascade_priority().cascade_level() > level {
children.push((
current.style_source().unwrap().clone(),
current.cascade_level(),
current.cascade_priority(),
));
current = current.parent().unwrap().clone();
}
@ -227,7 +222,7 @@ impl RuleTree {
// 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_level() == level {
if current.cascade_priority().cascade_level() == level {
*important_rules_changed |= level.is_important();
let current_decls = current.style_source().unwrap().as_declarations();
@ -267,7 +262,7 @@ impl RuleTree {
current = current.ensure_child(
self.root(),
StyleSource::from_declarations(pdb.clone_arc()),
level,
CascadePriority::new(level, layer_order),
);
*important_rules_changed = true;
}
@ -276,7 +271,7 @@ impl RuleTree {
current = current.ensure_child(
self.root(),
StyleSource::from_declarations(pdb.clone_arc()),
level,
CascadePriority::new(level, layer_order),
);
}
}
@ -312,7 +307,10 @@ impl RuleTree {
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_level()));
children.push((
node.style_source().unwrap().clone(),
node.cascade_priority(),
));
}
last = node;
}
@ -336,6 +334,7 @@ impl RuleTree {
let mut dummy = false;
self.update_rule_at_level(
CascadeLevel::Transitions,
LayerOrder::root(),
Some(pdb.borrow_arc()),
path,
guards,