style/stylesheets/
layer_rule.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! A [`@layer`][layer] rule.
6//!
7//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering
8
9use crate::parser::{Parse, ParserContext};
10use crate::shared_lock::{DeepCloneWithLock, Locked};
11use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
12use crate::values::AtomIdent;
13
14use super::CssRules;
15
16use cssparser::{Parser, SourceLocation, Token};
17use servo_arc::Arc;
18use smallvec::SmallVec;
19use std::fmt::{self, Write};
20use style_traits::{CssWriter, ParseError, ToCss};
21
22/// The order of a given layer. We use 16 bits so that we can pack LayerOrder
23/// and CascadeLevel in a single 32-bit struct. If we need more bits we can go
24/// back to packing CascadeLevel in a single byte as we did before.
25#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)]
26pub struct LayerOrder(u16);
27
28impl LayerOrder {
29    /// The order of the root layer.
30    pub const fn root() -> Self {
31        Self(std::u16::MAX - 1)
32    }
33
34    /// The order of the style attribute layer.
35    pub const fn style_attribute() -> Self {
36        Self(std::u16::MAX)
37    }
38
39    /// Returns whether this layer is for the style attribute, which behaves
40    /// differently in terms of !important, see
41    /// https://github.com/w3c/csswg-drafts/issues/6872
42    ///
43    /// (This is a bit silly, mind-you, but it's needed so that revert-layer
44    /// behaves correctly).
45    #[inline]
46    pub fn is_style_attribute_layer(&self) -> bool {
47        *self == Self::style_attribute()
48    }
49
50    /// The first cascade layer order.
51    pub const fn first() -> Self {
52        Self(0)
53    }
54
55    /// Increment the cascade layer order.
56    #[inline]
57    pub fn inc(&mut self) {
58        if self.0 != std::u16::MAX - 1 {
59            self.0 += 1;
60        }
61    }
62}
63
64/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
65#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
66pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
67
68impl LayerName {
69    /// Returns an empty layer name (which isn't a valid final state, so caller
70    /// is responsible to fill up the name before use).
71    pub fn new_empty() -> Self {
72        Self(Default::default())
73    }
74
75    /// Returns a synthesized name for an anonymous layer.
76    pub fn new_anonymous() -> Self {
77        use std::sync::atomic::{AtomicUsize, Ordering};
78        static NEXT_ANONYMOUS_LAYER_NAME: AtomicUsize = AtomicUsize::new(0);
79
80        let mut name = SmallVec::new();
81        let next_id = NEXT_ANONYMOUS_LAYER_NAME.fetch_add(1, Ordering::Relaxed);
82        // The parens don't _technically_ prevent conflicts with authors, as
83        // authors could write escaped parens as part of the identifier, I
84        // think, but highly reduces the possibility.
85        name.push(AtomIdent::from(&*format!("-moz-anon-layer({})", next_id)));
86
87        LayerName(name)
88    }
89
90    /// Returns the names of the layers. That is, for a layer like `foo.bar`,
91    /// it'd return [foo, bar].
92    pub fn layer_names(&self) -> &[AtomIdent] {
93        &self.0
94    }
95}
96
97impl Parse for LayerName {
98    fn parse<'i, 't>(
99        _: &ParserContext,
100        input: &mut Parser<'i, 't>,
101    ) -> Result<Self, ParseError<'i>> {
102        let mut result = SmallVec::new();
103        result.push(AtomIdent::from(&**input.expect_ident()?));
104        loop {
105            let next_name = input.try_parse(|input| -> Result<AtomIdent, ParseError<'i>> {
106                match input.next_including_whitespace()? {
107                    Token::Delim('.') => {},
108                    other => {
109                        let t = other.clone();
110                        return Err(input.new_unexpected_token_error(t));
111                    },
112                }
113
114                let name = match input.next_including_whitespace()? {
115                    Token::Ident(ref ident) => ident,
116                    other => {
117                        let t = other.clone();
118                        return Err(input.new_unexpected_token_error(t));
119                    },
120                };
121
122                Ok(AtomIdent::from(&**name))
123            });
124
125            match next_name {
126                Ok(name) => result.push(name),
127                Err(..) => break,
128            }
129        }
130        Ok(LayerName(result))
131    }
132}
133
134impl ToCss for LayerName {
135    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
136    where
137        W: Write,
138    {
139        let mut first = true;
140        for name in self.0.iter() {
141            if !first {
142                dest.write_char('.')?;
143            }
144            first = false;
145            name.to_css(dest)?;
146        }
147        Ok(())
148    }
149}
150
151#[derive(Debug, ToShmem)]
152/// A block `@layer <name>? { ... }`
153/// https://drafts.csswg.org/css-cascade-5/#layer-block
154pub struct LayerBlockRule {
155    /// The layer name, or `None` if anonymous.
156    pub name: Option<LayerName>,
157    /// The nested rules.
158    pub rules: Arc<Locked<CssRules>>,
159    /// The source position where this rule was found.
160    pub source_location: SourceLocation,
161}
162
163impl ToCssWithGuard for LayerBlockRule {
164    fn to_css(
165        &self,
166        guard: &SharedRwLockReadGuard,
167        dest: &mut crate::str::CssStringWriter,
168    ) -> fmt::Result {
169        dest.write_str("@layer")?;
170        if let Some(ref name) = self.name {
171            dest.write_char(' ')?;
172            name.to_css(&mut CssWriter::new(dest))?;
173        }
174        self.rules.read_with(guard).to_css_block(guard, dest)
175    }
176}
177
178impl DeepCloneWithLock for LayerBlockRule {
179    fn deep_clone_with_lock(
180        &self,
181        lock: &SharedRwLock,
182        guard: &SharedRwLockReadGuard,
183    ) -> Self {
184        Self {
185            name: self.name.clone(),
186            rules: Arc::new(
187                lock.wrap(
188                    self.rules
189                        .read_with(guard)
190                        .deep_clone_with_lock(lock, guard),
191                ),
192            ),
193            source_location: self.source_location.clone(),
194        }
195    }
196}
197
198/// A statement `@layer <name>, <name>, <name>;`
199///
200/// https://drafts.csswg.org/css-cascade-5/#layer-empty
201#[derive(Clone, Debug, ToShmem)]
202pub struct LayerStatementRule {
203    /// The list of layers to sort.
204    pub names: Vec<LayerName>,
205    /// The source position where this rule was found.
206    pub source_location: SourceLocation,
207}
208
209impl ToCssWithGuard for LayerStatementRule {
210    fn to_css(
211        &self,
212        _: &SharedRwLockReadGuard,
213        dest: &mut crate::str::CssStringWriter,
214    ) -> fmt::Result {
215        let mut writer = CssWriter::new(dest);
216        writer.write_str("@layer ")?;
217        let mut first = true;
218        for name in &*self.names {
219            if !first {
220                writer.write_str(", ")?;
221            }
222            first = false;
223            name.to_css(&mut writer)?;
224        }
225        writer.write_char(';')
226    }
227}