From 5fad3b4cde5a8f57dfc4cbeb52e555575529e209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 25 May 2023 20:12:51 +0200 Subject: [PATCH] style: Add some scaffolding for @layer rules Not hooked anywhere yet, so this doesn't change behavior, but adds the basic data model etc. Adding parsing support requires some changes to cssparser to allow the same at rule to be block and statement-like at the same time, so better done separately. Differential Revision: https://phabricator.services.mozilla.com/D124079 --- components/style/invalidation/stylesheets.rs | 4 +- components/style/stylesheets/layer_rule.rs | 171 ++++++++++++++++++ components/style/stylesheets/mod.rs | 23 ++- .../style/stylesheets/rules_iterator.rs | 9 + components/style/stylesheets/stylesheet.rs | 5 +- components/style/stylist.rs | 1 + 6 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 components/style/stylesheets/layer_rule.rs diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index edecc3fc243..f4dc0c5a443 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -555,7 +555,7 @@ impl StylesheetInvalidationSet { self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) }, - Document(..) | Import(..) | Media(..) | Supports(..) => { + Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { if !is_generic_change && !EffectiveRules::is_effective(guard, device, quirks_mode, rule) { @@ -596,7 +596,7 @@ impl StylesheetInvalidationSet { } } }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) => { + Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs new file mode 100644 index 00000000000..873c9e1275c --- /dev/null +++ b/components/style/stylesheets/layer_rule.rs @@ -0,0 +1,171 @@ +/* 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/. */ + +//! A [`@layer`][layer] urle. +//! +//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering + +use crate::parser::{Parse, ParserContext}; +use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; +use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use crate::values::AtomIdent; + +use super::CssRules; + +use cssparser::{Parser, SourceLocation, ToCss as CssParserToCss, Token}; +use servo_arc::Arc; +use smallvec::SmallVec; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; + +/// A ``: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name +#[derive(Clone, Debug, ToShmem)] +pub struct LayerName(SmallVec<[AtomIdent; 1]>); + +impl Parse for LayerName { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut result = SmallVec::new(); + result.push(AtomIdent::from(&**input.expect_ident()?)); + loop { + let next_name = input.try_parse(|input| -> Result> { + match input.next_including_whitespace()? { + Token::Delim('.') => {}, + other => { + let t = other.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + } + + let name = match input.next_including_whitespace()? { + Token::Ident(ref ident) => ident, + other => { + let t = other.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + }; + + Ok(AtomIdent::from(&**name)) + }); + + match next_name { + Ok(name) => result.push(name), + Err(..) => break, + } + } + Ok(LayerName(result)) + } +} + +impl ToCss for LayerName { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + let mut first = true; + for name in self.0.iter() { + if !first { + dest.write_char('.')?; + } + first = false; + name.to_css(dest)?; + } + Ok(()) + } +} + +/// The kind of layer rule this is. +#[derive(Debug, ToShmem)] +pub enum LayerRuleKind { + /// A block `@layer ? { ... }` + Block { + /// The layer name, or `None` if anonymous. + name: Option, + /// The nested rules. + rules: Arc>, + }, + /// A statement `@layer , , ;` + Statement { + /// The list of layers to sort. + names: SmallVec<[LayerName; 3]>, + }, +} + +/// A [`@layer`][layer] urle. +/// +/// [layer]: https://drafts.csswg.org/css-cascade-5/#layering +#[derive(Debug, ToShmem)] +pub struct LayerRule { + /// The kind of layer rule we are. + pub kind: LayerRuleKind, + /// The source position where this media rule was found. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for LayerRule { + fn to_css( + &self, + guard: &SharedRwLockReadGuard, + dest: &mut crate::str::CssStringWriter, + ) -> fmt::Result { + dest.write_str("@layer ")?; + match self.kind { + LayerRuleKind::Block { + ref name, + ref rules, + } => { + if let Some(ref name) = *name { + name.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + } + rules.read_with(guard).to_css_block(guard, dest) + }, + LayerRuleKind::Statement { ref names } => { + let mut writer = CssWriter::new(dest); + let mut first = true; + for name in &**names { + if !first { + writer.write_str(", ")?; + } + first = false; + name.to_css(&mut writer)?; + } + dest.write_char(';') + }, + } + } +} + +impl DeepCloneWithLock for LayerRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + Self { + kind: match self.kind { + LayerRuleKind::Block { + ref name, + ref rules, + } => LayerRuleKind::Block { + name: name.clone(), + rules: Arc::new( + lock.wrap( + rules + .read_with(guard) + .deep_clone_with_lock(lock, guard, params), + ), + ), + }, + LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement { + names: names.clone(), + }, + }, + source_location: self.source_location.clone(), + } + } +} diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index fd9be56b5b5..dbb42667b60 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -11,6 +11,7 @@ mod font_face_rule; pub mod font_feature_values_rule; pub mod import_rule; pub mod keyframes_rule; +mod layer_rule; mod loader; mod media_rule; mod namespace_rule; @@ -49,6 +50,7 @@ pub use self::font_face_rule::FontFaceRule; pub use self::font_feature_values_rule::FontFeatureValuesRule; pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; +pub use self::layer_rule::LayerRule; pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; @@ -257,6 +259,7 @@ pub enum CssRule { Supports(Arc>), Page(Arc>), Document(Arc>), + Layer(Arc>), } impl CssRule { @@ -297,11 +300,14 @@ impl CssRule { CssRule::Document(ref lock) => { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, + + // TODO(emilio): Add memory reporting for @layer rules. + CssRule::Layer(_) => 0, } } } -#[allow(missing_docs)] +/// https://drafts.csswg.org/cssom-1/#dom-cssrule-type #[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] pub enum CssRuleType { // https://drafts.csswg.org/cssom/#the-cssrule-interface @@ -323,10 +329,13 @@ pub enum CssRuleType { Supports = 12, // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface Document = 13, - // https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues + // https://drafts.csswg.org/css-fonts/#om-fontfeaturevalues FontFeatureValues = 14, // https://drafts.csswg.org/css-device-adapt/#css-rule-interface Viewport = 15, + // After viewport, all rules should return 0 from the API, but we still need + // a constant somewhere. + Layer = 16, } #[allow(missing_docs)] @@ -353,6 +362,7 @@ impl CssRule { CssRule::Supports(_) => CssRuleType::Supports, CssRule::Page(_) => CssRuleType::Page, CssRule::Document(_) => CssRuleType::Document, + CssRule::Layer(_) => CssRuleType::Layer, } } @@ -361,6 +371,8 @@ impl CssRule { // CssRule::Charset(..) => State::Start, CssRule::Import(..) => State::Imports, CssRule::Namespace(..) => State::Namespaces, + // TODO(emilio): We'll need something here for non-block layer + // rules. _ => State::Body, } } @@ -485,6 +497,12 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, + CssRule::Layer(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Layer(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), + )) + } } } } @@ -505,6 +523,7 @@ impl ToCssWithGuard for CssRule { CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Layer(ref lock) => lock.read_with(guard).to_css(guard, dest), } } } diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index a7010ff066e..b1921e63e07 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -105,6 +105,15 @@ where } Some(supports_rule.rules.read_with(guard).0.iter()) }, + CssRule::Layer(ref lock) => { + use crate::stylesheets::layer_rule::LayerRuleKind; + + let layer_rule = lock.read_with(guard); + match layer_rule.kind { + LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()), + LayerRuleKind::Statement { .. } => None, + } + } } } diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index b8e7f246c19..91a407f5c32 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -367,7 +367,10 @@ impl SanitizationKind { CssRule::Document(..) | CssRule::Media(..) | CssRule::Supports(..) | - CssRule::Import(..) => false, + CssRule::Import(..) | + // TODO(emilio): Perhaps Layer should not be always sanitized? But + // we sanitize @media and co, so this seems safer for now. + CssRule::Layer(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, diff --git a/components/style/stylist.rs b/components/style/stylist.rs index fb7334d3600..643a1454e00 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2348,6 +2348,7 @@ impl CascadeData { CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | + CssRule::Layer(..) | CssRule::FontFeatureValues(..) => { // Not affected by device changes. continue;