From 0f945d081b3d4e8e6880dd6108b607785e765211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 3 Sep 2021 09:12:43 +0000 Subject: [PATCH] Bug 1728348 - Add some scaffolding for @layer rules. r=boris 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 --- .../style/invalidation/stylesheets.rs | 4 +- .../style/stylesheets/layer_rule.rs | 171 ++++++++++++++++++ servo/components/style/stylesheets/mod.rs | 23 ++- .../style/stylesheets/rules_iterator.rs | 9 + .../style/stylesheets/stylesheet.rs | 5 +- servo/components/style/stylist.rs | 1 + 6 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 servo/components/style/stylesheets/layer_rule.rs diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs index edecc3fc2432..f4dc0c5a4435 100644 --- a/servo/components/style/invalidation/stylesheets.rs +++ b/servo/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/servo/components/style/stylesheets/layer_rule.rs b/servo/components/style/stylesheets/layer_rule.rs new file mode 100644 index 000000000000..873c9e1275cd --- /dev/null +++ b/servo/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/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs index fd9be56b5b53..dbb42667b600 100644 --- a/servo/components/style/stylesheets/mod.rs +++ b/servo/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/servo/components/style/stylesheets/rules_iterator.rs b/servo/components/style/stylesheets/rules_iterator.rs index a7010ff066e2..b1921e63e072 100644 --- a/servo/components/style/stylesheets/rules_iterator.rs +++ b/servo/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/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs index b8e7f246c19a..91a407f5c327 100644 --- a/servo/components/style/stylesheets/stylesheet.rs +++ b/servo/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/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index 4027ca6c981c..547f5b48fca6 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -2373,6 +2373,7 @@ impl CascadeData { CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | + CssRule::Layer(..) | CssRule::FontFeatureValues(..) => { // Not affected by device changes. continue;