diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/style/animation.rs | 2 | ||||
-rw-r--r-- | components/style/applicable_declarations.rs | 139 | ||||
-rw-r--r-- | components/style/custom_properties.rs | 19 | ||||
-rw-r--r-- | components/style/matching.rs | 10 | ||||
-rw-r--r-- | components/style/properties/cascade.rs | 97 | ||||
-rw-r--r-- | components/style/properties/declaration_block.rs | 6 | ||||
-rw-r--r-- | components/style/properties/helpers.mako.rs | 1 | ||||
-rw-r--r-- | components/style/properties/helpers/animated_properties.mako.rs | 8 | ||||
-rw-r--r-- | components/style/properties/properties.mako.rs | 24 | ||||
-rw-r--r-- | components/style/rule_collector.rs | 2 | ||||
-rw-r--r-- | components/style/rule_tree/core.rs | 47 | ||||
-rw-r--r-- | components/style/rule_tree/level.rs | 118 | ||||
-rw-r--r-- | components/style/rule_tree/mod.rs | 85 | ||||
-rw-r--r-- | components/style/stylesheets/layer_rule.rs | 14 | ||||
-rw-r--r-- | components/style/stylesheets/origin.rs | 2 | ||||
-rw-r--r-- | components/style/stylist.rs | 18 | ||||
-rw-r--r-- | components/style/values/mod.rs | 17 |
17 files changed, 356 insertions, 253 deletions
diff --git a/components/style/animation.rs b/components/style/animation.rs index 57ce98141a7..7f0c7e6a588 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -24,6 +24,7 @@ use crate::selector_parser::PseudoElement; use crate::shared_lock::{Locked, SharedRwLock}; use crate::style_resolver::StyleResolverForElement; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::values::animated::{Animate, Procedure}; use crate::values::computed::{Time, TimingFunction}; use crate::values::generics::box_::AnimationIterationCount; @@ -290,6 +291,7 @@ impl IntermediateComputedKeyframe { let rule_node = base_style.rules().clone(); let new_node = context.stylist.rule_tree().update_rule_at_level( CascadeLevel::Animations, + LayerOrder::root(), Some(locked_block.borrow_arc()), &rule_node, &context.guards, diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 7f2d3928d2d..cdb78d05405 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -24,36 +24,102 @@ pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>; /// That's a limit that could be reached in realistic webpages, so we use /// 24 bits and enforce defined behavior in the overflow case. /// +/// Note that right now this restriction could be lifted if wanted (because we +/// no longer stash the cascade level in the remaining bits), but we keep it in +/// place in case we come up with a use-case for them, lacking reports of the +/// current limit being too small. +/// /// [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/ /// RuleSet.h?l=128&rcl=90140ab80b84d0f889abc253410f44ed54ae04f3 -const SOURCE_ORDER_SHIFT: usize = 0; const SOURCE_ORDER_BITS: usize = 24; const SOURCE_ORDER_MAX: u32 = (1 << SOURCE_ORDER_BITS) - 1; -const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX << SOURCE_ORDER_SHIFT; - -/// We pack the cascade level in a single byte, see CascadeLevel::to_byte_lossy -/// for the different trade-offs there. -const CASCADE_LEVEL_SHIFT: usize = SOURCE_ORDER_BITS; - -/// Stores the source order of a block, the cascade level it belongs to, and the -/// counter needed to handle Shadow DOM cascade order properly. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -struct ApplicableDeclarationBits(u32); - -impl ApplicableDeclarationBits { - fn new(source_order: u32, cascade_level: CascadeLevel) -> Self { - Self( - (source_order & SOURCE_ORDER_MASK) | - ((cascade_level.to_byte_lossy() as u32) << CASCADE_LEVEL_SHIFT), - ) +const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX; + +/// The cascade-level+layer order of this declaration. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub struct CascadePriority { + cascade_level: CascadeLevel, + layer_order: LayerOrder, +} + +#[allow(dead_code)] +fn size_assert() { + #[allow(unsafe_code)] + unsafe { std::mem::transmute::<u32, CascadePriority>(0u32) }; +} + +impl PartialOrd for CascadePriority { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for CascadePriority { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.cascade_level + .cmp(&other.cascade_level) + .then_with(|| { + let ordering = self.layer_order.cmp(&other.layer_order); + // https://drafts.csswg.org/css-cascade-5/#cascade-layering + // + // Cascade layers (like declarations) are ordered by order + // of appearance. When comparing declarations that belong to + // different layers, then for normal rules the declaration + // whose cascade layer is last wins, and for important rules + // the declaration whose cascade layer is first wins. + // + // FIXME: This creates somewhat surprising behavior for the + // style attribute, see + // https://github.com/w3c/csswg-drafts/issues/6872 + if self.cascade_level.is_important() { + ordering.reverse() + } else { + ordering + } + }) + } +} + +impl CascadePriority { + /// Construct a new CascadePriority for a given (level, order) pair. + pub fn new(cascade_level: CascadeLevel, layer_order: LayerOrder) -> Self { + Self { cascade_level, layer_order } } - fn source_order(&self) -> u32 { - self.0 & SOURCE_ORDER_MASK + /// Returns the layer order. + #[inline] + pub fn layer_order(&self) -> LayerOrder { + self.layer_order + } + + /// Returns the cascade level. + #[inline] + pub fn cascade_level(&self) -> CascadeLevel { + self.cascade_level + } + + /// Whether this declaration should be allowed if `revert` or `revert-layer` + /// have been specified on a given origin. + /// + /// `self` is the priority at which the `revert` or `revert-layer` keyword + /// have been specified. + pub fn allows_when_reverted(&self, other: &Self, origin_revert: bool) -> bool { + if origin_revert { + other.cascade_level.origin() < self.cascade_level.origin() + } else { + other.unimportant() < self.unimportant() + } } - fn level(&self) -> CascadeLevel { - CascadeLevel::from_byte((self.0 >> CASCADE_LEVEL_SHIFT) as u8) + /// Convert this priority from "important" to "non-important", if needed. + pub fn unimportant(&self) -> Self { + Self::new(self.cascade_level().unimportant(), self.layer_order()) + } + + /// Convert this priority from "non-important" to "important", if needed. + pub fn important(&self) -> Self { + Self::new(self.cascade_level().important(), self.layer_order()) } } @@ -69,11 +135,11 @@ pub struct ApplicableDeclarationBlock { pub source: StyleSource, /// The bits containing the source order, cascade level, and shadow cascade /// order. - bits: ApplicableDeclarationBits, + source_order: u32, /// The specificity of the selector. pub specificity: u32, - /// The layer order of the selector. - pub layer_order: LayerOrder, + /// The cascade priority of the rule. + pub cascade_priority: CascadePriority, } impl ApplicableDeclarationBlock { @@ -86,9 +152,9 @@ impl ApplicableDeclarationBlock { ) -> Self { ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), - bits: ApplicableDeclarationBits::new(0, level), + source_order: 0, specificity: 0, - layer_order: LayerOrder::root(), + cascade_priority: CascadePriority::new(level, LayerOrder::root()), } } @@ -103,29 +169,34 @@ impl ApplicableDeclarationBlock { ) -> Self { ApplicableDeclarationBlock { source, - bits: ApplicableDeclarationBits::new(source_order, level), + source_order: source_order & SOURCE_ORDER_MASK, specificity, - layer_order, + cascade_priority: CascadePriority::new(level, layer_order), } } /// Returns the source order of the block. #[inline] pub fn source_order(&self) -> u32 { - self.bits.source_order() + self.source_order } /// Returns the cascade level of the block. #[inline] pub fn level(&self) -> CascadeLevel { - self.bits.level() + self.cascade_priority.cascade_level() + } + + /// Returns the cascade level of the block. + #[inline] + pub fn layer_order(&self) -> LayerOrder { + self.cascade_priority.layer_order() } /// Convenience method to consume self and return the right thing for the /// rule tree to iterate over. #[inline] - pub fn for_rule_tree(self) -> (StyleSource, CascadeLevel) { - let level = self.level(); - (self.source, level) + pub fn for_rule_tree(self) -> (StyleSource, CascadePriority) { + (self.source, self.cascade_priority) } } diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 427fe03439c..de887396c55 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -6,11 +6,11 @@ //! //! [custom]: https://drafts.csswg.org/css-variables/ +use crate::applicable_declarations::CascadePriority; use crate::hash::map::Entry; use crate::media_queries::Device; use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue}; use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher}; -use crate::stylesheets::{Origin, PerOrigin}; use crate::Atom; use cssparser::{ CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType, @@ -599,10 +599,10 @@ fn parse_env_function<'i, 't>( /// properties. pub struct CustomPropertiesBuilder<'a> { seen: PrecomputedHashSet<&'a Name>, - reverted: PerOrigin<PrecomputedHashSet<&'a Name>>, may_have_cycles: bool, custom_properties: Option<CustomPropertiesMap>, inherited: Option<&'a Arc<CustomPropertiesMap>>, + reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>, device: &'a Device, } @@ -620,14 +620,16 @@ impl<'a> CustomPropertiesBuilder<'a> { } /// Cascade a given custom property declaration. - pub fn cascade(&mut self, declaration: &'a CustomDeclaration, origin: Origin) { + pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) { let CustomDeclaration { ref name, ref value, } = *declaration; - if self.reverted.borrow_for_origin(&origin).contains(&name) { - return; + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + return; + } } let was_already_present = !self.seen.insert(name); @@ -670,11 +672,10 @@ impl<'a> CustomPropertiesBuilder<'a> { map.insert(name.clone(), value); }, CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword { - CSSWideKeyword::Revert => { + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; self.seen.remove(name); - for origin in origin.following_including() { - self.reverted.borrow_mut_for_origin(&origin).insert(name); - } + self.reverted.insert(name, (priority, origin_revert)); }, CSSWideKeyword::Initial => { map.remove(name); diff --git a/components/style/matching.rs b/components/style/matching.rs index 9b267cd8b46..92fdee3f97b 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -23,6 +23,7 @@ use crate::selector_parser::{PseudoElement, RestyleDamage}; use crate::shared_lock::Locked; use crate::style_resolver::ResolvedElementStyles; use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use selectors::matching::ElementSelectorFlags; @@ -92,6 +93,7 @@ trait PrivateMatchMethods: TElement { fn replace_single_rule_node( context: &SharedStyleContext, level: CascadeLevel, + layer_order: LayerOrder, pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, path: &mut StrongRuleNode, ) -> bool { @@ -101,6 +103,7 @@ trait PrivateMatchMethods: TElement { let mut important_rules_changed = false; let new_node = stylist.rule_tree().update_rule_at_level( level, + layer_order, pdb, path, guards, @@ -145,12 +148,14 @@ trait PrivateMatchMethods: TElement { result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_normal(), + LayerOrder::root(), style_attribute, primary_rules, ); result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_important(), + LayerOrder::root(), style_attribute, primary_rules, ); @@ -172,6 +177,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::SMILOverride, + LayerOrder::root(), self.smil_override(), primary_rules, ); @@ -181,6 +187,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::Transitions, + LayerOrder::root(), self.transition_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), @@ -192,6 +199,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::Animations, + LayerOrder::root(), self.animation_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), @@ -589,12 +597,14 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, + LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, + LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 6b8c4cf4d37..dfdb2a2b1c8 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -4,6 +4,7 @@ //! The main cascading algorithm of the style system. +use crate::applicable_declarations::CascadePriority; use crate::context::QuirksMode; use crate::custom_properties::CustomPropertiesBuilder; use crate::dom::TElement; @@ -15,12 +16,13 @@ use crate::properties::{ ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY, }; use crate::rule_cache::{RuleCache, RuleCacheConditions}; -use crate::rule_tree::StrongRuleNode; +use crate::rule_tree::{StrongRuleNode, CascadeLevel}; use crate::selector_parser::PseudoElement; use crate::shared_lock::StylesheetGuards; use crate::style_adjuster::StyleAdjuster; -use crate::stylesheets::{Origin, PerOrigin}; +use crate::stylesheets::{Origin, layer_rule::LayerOrder}; use crate::values::{computed, specified}; +use fxhash::FxHashMap; use servo_arc::Arc; use smallvec::SmallVec; use std::borrow::Cow; @@ -115,6 +117,7 @@ struct DeclarationIterator<'a> { declarations: DeclarationImportanceIterator<'a>, origin: Origin, importance: Importance, + priority: CascadePriority, } impl<'a> DeclarationIterator<'a> { @@ -128,8 +131,9 @@ impl<'a> DeclarationIterator<'a> { let mut iter = Self { guards, current_rule_node: Some(rule_node), - origin: Origin::Author, + origin: Origin::UserAgent, importance: Importance::Normal, + priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), declarations: DeclarationImportanceIterator::default(), restriction, }; @@ -138,10 +142,11 @@ impl<'a> DeclarationIterator<'a> { } fn update_for_node(&mut self, node: &'a StrongRuleNode) { - let origin = node.cascade_level().origin(); - self.origin = origin; - self.importance = node.importance(); - let guard = match origin { + self.priority = node.cascade_priority(); + let level = self.priority.cascade_level(); + self.origin = level.origin(); + self.importance = level.importance(); + let guard = match self.origin { Origin::Author => self.guards.author, Origin::User | Origin::UserAgent => self.guards.ua_or_user, }; @@ -153,7 +158,7 @@ impl<'a> DeclarationIterator<'a> { } impl<'a> Iterator for DeclarationIterator<'a> { - type Item = (&'a PropertyDeclaration, Origin); + type Item = (&'a PropertyDeclaration, CascadePriority); #[inline] fn next(&mut self) -> Option<Self::Item> { @@ -163,20 +168,19 @@ impl<'a> Iterator for DeclarationIterator<'a> { continue; } - let origin = self.origin; if let Some(restriction) = self.restriction { // decl.id() is either a longhand or a custom // property. Custom properties are always allowed, but // longhands are only allowed if they have our // restriction flag set. if let PropertyDeclarationId::Longhand(id) = decl.id() { - if !id.flags().contains(restriction) && origin != Origin::UserAgent { + if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { continue; } } } - return Some((decl, origin)); + return Some((decl, self.priority)); } let next_node = self.current_rule_node.take()?.parent()?; @@ -259,7 +263,7 @@ pub fn apply_declarations<'a, E, I>( ) -> Arc<ComputedValues> where E: TElement, - I: Iterator<Item = (&'a PropertyDeclaration, Origin)>, + I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>, { debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); debug_assert_eq!( @@ -278,14 +282,14 @@ where let inherited_style = parent_style.unwrap_or(device.default_computed_values()); - let mut declarations = SmallVec::<[(&_, Origin); 32]>::new(); + let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new(); let custom_properties = { let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device); - for (declaration, origin) in iter { - declarations.push((declaration, origin)); + for (declaration, priority) in iter { + declarations.push((declaration, priority)); if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, origin); + builder.cascade(declaration, priority); } } @@ -494,7 +498,8 @@ struct Cascade<'a, 'b: 'a> { cascade_mode: CascadeMode<'a>, seen: LonghandIdSet, author_specified: LonghandIdSet, - reverted: PerOrigin<LonghandIdSet>, + reverted_set: LonghandIdSet, + reverted: FxHashMap<LonghandId, (CascadePriority, bool)>, } impl<'a, 'b: 'a> Cascade<'a, 'b> { @@ -504,6 +509,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { cascade_mode, seen: LonghandIdSet::default(), author_specified: LonghandIdSet::default(), + reverted_set: Default::default(), reverted: Default::default(), } } @@ -575,7 +581,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, ) where Phase: CascadePhase, - I: Iterator<Item = (&'decls PropertyDeclaration, Origin)>, + I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>, { let apply_reset = apply_reset == ApplyResetProperties::Yes; @@ -589,7 +595,9 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { let ignore_colors = !self.context.builder.device.use_document_colors(); let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); - for (declaration, origin) in declarations { + for (declaration, priority) in declarations { + let origin = priority.cascade_level().origin(); + let declaration_id = declaration.id(); let longhand_id = match declaration_id { PropertyDeclarationId::Longhand(id) => id, @@ -616,12 +624,12 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; } - if self - .reverted - .borrow_for_origin(&origin) - .contains(physical_longhand_id) - { - continue; + if self.reverted_set.contains(physical_longhand_id) { + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&physical_longhand_id) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + continue; + } + } } // Only a few properties are allowed to depend on the visited state @@ -653,32 +661,31 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { ); } - let css_wide_keyword = declaration.get_css_wide_keyword(); - if let Some(CSSWideKeyword::Revert) = css_wide_keyword { - // We intentionally don't want to insert it into `self.seen`, - // `reverted` takes care of rejecting other declarations as - // needed. - for origin in origin.following_including() { - self.reverted - .borrow_mut_for_origin(&origin) - .insert(physical_longhand_id); - } - continue; - } + let is_unset = match declaration.get_css_wide_keyword() { + Some(keyword) => match keyword { + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; + // We intentionally don't want to insert it into + // `self.seen`, `reverted` takes care of rejecting other + // declarations as needed. + self.reverted_set.insert(physical_longhand_id); + self.reverted.insert(physical_longhand_id, (priority, origin_revert)); + continue; + }, + CSSWideKeyword::Unset => true, + CSSWideKeyword::Inherit => inherited, + CSSWideKeyword::Initial => !inherited, + }, + None => false, + }; self.seen.insert(physical_longhand_id); if origin == Origin::Author { self.author_specified.insert(physical_longhand_id); } - let unset = css_wide_keyword.map_or(false, |css_wide_keyword| match css_wide_keyword { - CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => inherited, - CSSWideKeyword::Initial => !inherited, - CSSWideKeyword::Revert => unreachable!(), - }); - - if unset { + if is_unset { continue; } diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 980d3de11ff..01b390f0fca 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -7,15 +7,17 @@ #![deny(missing_docs)] use super::*; +use crate::applicable_declarations::CascadePriority; use crate::context::QuirksMode; use crate::custom_properties::CustomPropertiesBuilder; use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; use crate::parser::ParserContext; use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; +use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; use crate::shared_lock::Locked; use crate::str::{CssString, CssStringWriter}; -use crate::stylesheets::{CssRuleType, Origin, UrlExtraData}; +use crate::stylesheets::{CssRuleType, Origin, UrlExtraData, layer_rule::LayerOrder}; use crate::values::computed::Context; use cssparser::{parse_important, CowRcStr, DeclarationListParser, ParserInput}; use cssparser::{AtRuleParser, DeclarationParser, Delimiter, ParseErrorKind, Parser}; @@ -898,7 +900,7 @@ impl PropertyDeclarationBlock { for declaration in self.normal_declaration_iter() { if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, Origin::Author); + builder.cascade(declaration, CascadePriority::new(CascadeLevel::same_tree_author_normal(), LayerOrder::root())); } } diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index e46024feba1..91be2d6011b 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -476,6 +476,7 @@ context.builder.inherit_${property.ident}(); % endif } + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => unreachable!("Should never get here"), } return; diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 2f9e692f740..f9067c0e9a8 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -311,9 +311,9 @@ impl AnimationValue { % for prop in data.longhands: % if prop.animatable: LonghandId::${prop.camel_case} => { - // FIXME(emilio, bug 1533327): I think - // CSSWideKeyword::Revert handling is not fine here, but - // what to do instead? + // FIXME(emilio, bug 1533327): I think revert (and + // revert-layer) handling is not fine here, but what to + // do instead? // // Seems we'd need the computed value as if it was // revert, somehow. Treating it as `unset` seems fine @@ -321,6 +321,7 @@ impl AnimationValue { let style_struct = match declaration.keyword { % if not prop.style_struct.inherited: CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | CSSWideKeyword::Unset | % endif CSSWideKeyword::Initial => { @@ -328,6 +329,7 @@ impl AnimationValue { }, % if prop.style_struct.inherited: CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | CSSWideKeyword::Unset | % endif CSSWideKeyword::Inherit => { diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 4ee388dfe6c..cdecfaae895 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1051,6 +1051,8 @@ pub enum CSSWideKeyword { Unset, /// The `revert` keyword. Revert, + /// The `revert-layer` keyword. + RevertLayer, } impl CSSWideKeyword { @@ -1060,22 +1062,28 @@ impl CSSWideKeyword { CSSWideKeyword::Inherit => "inherit", CSSWideKeyword::Unset => "unset", CSSWideKeyword::Revert => "revert", + CSSWideKeyword::RevertLayer => "revert-layer", } } } impl CSSWideKeyword { + /// Parses a CSS wide keyword from a CSS identifier. + pub fn from_ident(ident: &str) -> Result<Self, ()> { + Ok(match_ignore_ascii_case! { ident, + "initial" => CSSWideKeyword::Initial, + "inherit" => CSSWideKeyword::Inherit, + "unset" => CSSWideKeyword::Unset, + "revert" => CSSWideKeyword::Revert, + "revert-layer" if static_prefs::pref!("layout.css.cascade-layers.enabled") => CSSWideKeyword::RevertLayer, + _ => return Err(()), + }) + } + fn parse(input: &mut Parser) -> Result<Self, ()> { let keyword = { let ident = input.expect_ident().map_err(|_| ())?; - match_ignore_ascii_case! { ident, - // If modifying this set of keyword, also update values::CustomIdent::from_ident - "initial" => CSSWideKeyword::Initial, - "inherit" => CSSWideKeyword::Inherit, - "unset" => CSSWideKeyword::Unset, - "revert" => CSSWideKeyword::Revert, - _ => return Err(()), - } + Self::from_ident(ident)? }; input.expect_exhausted().map_err(|_| ())?; Ok(keyword) diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 021c0176416..513c2d91566 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -148,7 +148,7 @@ where f(self); if start != self.rules.len() { self.rules[start..].sort_unstable_by_key(|block| { - (block.layer_order, block.specificity, block.source_order()) + (block.layer_order(), block.specificity, block.source_order()) }); } self.context.current_host = old_host; diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index ae1ba7bed94..fe1214faf65 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -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 + /// The cascade priority. + #[inline] + pub fn cascade_priority(&self) -> CascadePriority { + self.p.cascade_priority + } + + /// The cascade level. + #[inline] pub fn cascade_level(&self) -> CascadeLevel { - self.p.level + self.cascade_priority().cascade_level() } - /// Get the importance that this rule node represents. - pub fn importance(&self) -> Importance { - self.p.level.importance() + /// 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 diff --git a/components/style/rule_tree/level.rs b/components/style/rule_tree/level.rs index c46e63796ad..b8cbe55ed9c 100644 --- a/components/style/rule_tree/level.rs +++ b/components/style/rule_tree/level.rs @@ -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), - } - }, - 4 => Self::SMILOverride, - 5 => Self::Animations, - 6 => { - return Self::AuthorImportant { - 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, }, - 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; + } } } diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index e50382255ca..c8705165776 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -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, diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index ee066d813e2..1b7b0cb169e 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -19,14 +19,16 @@ use smallvec::SmallVec; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; -/// The order of a given layer. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerOrder(u32); +/// The order of a given layer. We use 16 bits so that we can pack LayerOrder +/// and CascadeLevel in a single 32-bit struct. If we need more bits we can go +/// back to packing CascadeLevel in a single byte as we did before. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerOrder(u16); impl LayerOrder { /// The order of the root layer. pub const fn root() -> Self { - Self(std::u32::MAX) + Self(std::u16::MAX) } /// The first cascade layer order. @@ -37,7 +39,9 @@ impl LayerOrder { /// Increment the cascade layer order. #[inline] pub fn inc(&mut self) { - self.0 += 1; + if self.0 != std::u16::MAX { + self.0 += 1; + } } } diff --git a/components/style/stylesheets/origin.rs b/components/style/stylesheets/origin.rs index a65b61fca13..27ad3fa184a 100644 --- a/components/style/stylesheets/origin.rs +++ b/components/style/stylesheets/origin.rs @@ -10,7 +10,7 @@ use std::ops::BitOrAssign; /// Each style rule has an origin, which determines where it enters the cascade. /// /// <https://drafts.csswg.org/css-cascade/#cascading-origins> -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, PartialOrd, Ord)] #[repr(u8)] pub enum Origin { /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent> diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a48a7103f84..c0cc2f238bb 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -4,7 +4,9 @@ //! Selector matching. -use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; +use crate::applicable_declarations::{ + ApplicableDeclarationBlock, ApplicableDeclarationList, CascadePriority, +}; use crate::context::{CascadeInputs, QuirksMode}; use crate::dom::{TElement, TShadowRoot}; use crate::element_state::{DocumentState, ElementState}; @@ -1474,9 +1476,15 @@ impl Stylist { /* pseudo = */ None, self.rule_tree.root(), guards, - block - .declaration_importance_iter() - .map(|(declaration, _)| (declaration, Origin::Author)), + block.declaration_importance_iter().map(|(declaration, _)| { + ( + declaration, + CascadePriority::new( + CascadeLevel::same_tree_author_normal(), + LayerOrder::root(), + ), + ) + }), Some(parent_style), Some(parent_style), Some(parent_style), @@ -1582,7 +1590,7 @@ impl ExtraStyleData { &mut self, guard: &SharedRwLockReadGuard, rule: &Arc<Locked<ScrollTimelineRule>>, - )-> Result<(), FailedAllocationError> { + ) -> Result<(), FailedAllocationError> { let name = rule.read_with(guard).name.as_atom().clone(); self.scroll_timelines .try_insert(name, rule.clone()) diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 626fc32ff18..ad09320c42b 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -438,15 +438,22 @@ impl CustomIdent { ident: &CowRcStr<'i>, excluding: &[&str], ) -> Result<Self, ParseError<'i>> { - let valid = match_ignore_ascii_case! { ident, - "initial" | "inherit" | "unset" | "default" | "revert" => false, - _ => true - }; - if !valid { + use crate::properties::CSSWideKeyword; + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // The CSS-wide keywords are not valid <custom-ident>s. The default + // keyword is reserved and is also not a valid <custom-ident>. + // + if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { return Err( location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) ); } + + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // Excluded keywords are excluded in all ASCII case permutations. + // if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } else { |