style_traits/
values.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//! Helper types and traits for the handling of CSS values.
6
7use app_units::Au;
8use cssparser::ToCss as CssparserToCss;
9use cssparser::{serialize_string, ParseError, Parser, Token, UnicodeRange};
10use servo_arc::Arc;
11use std::fmt::{self, Write};
12
13/// Serialises a value according to its CSS representation.
14///
15/// This trait is implemented for `str` and its friends, serialising the string
16/// contents as a CSS quoted string.
17///
18/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
19/// * unit variants get serialised as the `snake-case` representation
20///   of their name;
21/// * unit variants whose name starts with "Moz" or "Webkit" are prepended
22///   with a "-";
23/// * if `#[css(comma)]` is found on a variant, its fields are separated by
24///   commas, otherwise, by spaces;
25/// * if `#[css(function)]` is found on a variant, the variant name gets
26///   serialised like unit variants and its fields are surrounded by parentheses;
27/// * if `#[css(iterable)]` is found on a function variant, that variant needs
28///   to have a single member, and that member needs to be iterable. The
29///   iterable will be serialized as the arguments for the function;
30/// * an iterable field can also be annotated with `#[css(if_empty = "foo")]`
31///   to print `"foo"` if the iterator is empty;
32/// * if `#[css(dimension)]` is found on a variant, that variant needs
33///   to have a single member. The variant would be serialized as a CSS
34///   dimension token, like: <member><identifier>;
35/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field
36///   is skipped;
37/// * if `#[css(skip_if = "function")]` is found on a field, the `ToCss` call
38///   for that field is skipped if `function` returns true. This function is
39///   provided the field as an argument;
40/// * if `#[css(contextual_skip_if = "function")]` is found on a field, the
41///   `ToCss` call for that field is skipped if `function` returns true. This
42///   function is given all the fields in the current struct or variant as an
43///   argument;
44/// * `#[css(represents_keyword)]` can be used on bool fields in order to
45///   serialize the field name if the field is true, or nothing otherwise.  It
46///   also collects those keywords for `SpecifiedValueInfo`.
47/// * `#[css(bitflags(single="", mixed="", validate_mixed="", overlapping_bits)]` can
48///   be used to derive parse / serialize / etc on bitflags. The rules for parsing
49///   bitflags are the following:
50///
51///     * `single` flags can only appear on their own. It's common that bitflags
52///       properties at least have one such value like `none` or `auto`.
53///     * `mixed` properties can appear mixed together, but not along any other
54///       flag that shares a bit with itself. For example, if you have three
55///       bitflags like:
56///
57///         FOO = 1 << 0;
58///         BAR = 1 << 1;
59///         BAZ = 1 << 2;
60///         BAZZ = BAR | BAZ;
61///
62///       Then the following combinations won't be valid:
63///
64///         * foo foo: (every flag shares a bit with itself)
65///         * bar bazz: (bazz shares a bit with bar)
66///
67///       But `bar baz` will be valid, as they don't share bits, and so would
68///       `foo` with any other flag, or `bazz` on its own.
69///    * `validate_mixed` can be used to reject invalid mixed combinations, and also to simplify
70///      the type or add default ones if needed.
71///    * `overlapping_bits` enables some tracking during serialization of mixed flags to avoid
72///       serializing variants that can subsume other variants.
73///       In the example above, you could do:
74///         mixed="foo,bazz,bar,baz", overlapping_bits
75///       to ensure that if bazz is serialized, bar and baz aren't, even though
76///       their bits are set. Note that the serialization order is canonical,
77///       and thus depends on the order you specify the flags in.
78///
79/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
80///   implement `Debug` by a single call to `ToCss::to_css`.
81pub trait ToCss {
82    /// Serialize `self` in CSS syntax, writing to `dest`.
83    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
84    where
85        W: Write;
86
87    /// Serialize `self` in CSS syntax and return a string.
88    ///
89    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
90    #[inline]
91    fn to_css_string(&self) -> String {
92        let mut s = String::new();
93        self.to_css(&mut CssWriter::new(&mut s)).unwrap();
94        s
95    }
96
97    /// Serialize `self` in CSS syntax and return a CssString.
98    ///
99    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
100    #[inline]
101    fn to_css_cssstring(&self) -> CssString {
102        let mut s = CssString::new();
103        self.to_css(&mut CssWriter::new(&mut s)).unwrap();
104        s
105    }
106}
107
108impl<'a, T> ToCss for &'a T
109where
110    T: ToCss + ?Sized,
111{
112    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
113    where
114        W: Write,
115    {
116        (*self).to_css(dest)
117    }
118}
119
120impl ToCss for crate::owned_str::OwnedStr {
121    #[inline]
122    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
123    where
124        W: Write,
125    {
126        serialize_string(self, dest)
127    }
128}
129
130impl ToCss for str {
131    #[inline]
132    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
133    where
134        W: Write,
135    {
136        serialize_string(self, dest)
137    }
138}
139
140impl ToCss for String {
141    #[inline]
142    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
143    where
144        W: Write,
145    {
146        serialize_string(self, dest)
147    }
148}
149
150impl<T> ToCss for Option<T>
151where
152    T: ToCss,
153{
154    #[inline]
155    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
156    where
157        W: Write,
158    {
159        self.as_ref().map_or(Ok(()), |value| value.to_css(dest))
160    }
161}
162
163impl ToCss for () {
164    #[inline]
165    fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result
166    where
167        W: Write,
168    {
169        Ok(())
170    }
171}
172
173/// A writer tailored for serialising CSS.
174///
175/// Coupled with SequenceWriter, this allows callers to transparently handle
176/// things like comma-separated values etc.
177pub struct CssWriter<'w, W: 'w> {
178    inner: &'w mut W,
179    prefix: Option<&'static str>,
180}
181
182impl<'w, W> CssWriter<'w, W>
183where
184    W: Write,
185{
186    /// Creates a new `CssWriter`.
187    #[inline]
188    pub fn new(inner: &'w mut W) -> Self {
189        Self {
190            inner,
191            prefix: Some(""),
192        }
193    }
194}
195
196impl<'w, W> Write for CssWriter<'w, W>
197where
198    W: Write,
199{
200    #[inline]
201    fn write_str(&mut self, s: &str) -> fmt::Result {
202        if s.is_empty() {
203            return Ok(());
204        }
205        if let Some(prefix) = self.prefix.take() {
206            // We are going to write things, but first we need to write
207            // the prefix that was set by `SequenceWriter::item`.
208            if !prefix.is_empty() {
209                self.inner.write_str(prefix)?;
210            }
211        }
212        self.inner.write_str(s)
213    }
214
215    #[inline]
216    fn write_char(&mut self, c: char) -> fmt::Result {
217        if let Some(prefix) = self.prefix.take() {
218            // See comment in `write_str`.
219            if !prefix.is_empty() {
220                self.inner.write_str(prefix)?;
221            }
222        }
223        self.inner.write_char(c)
224    }
225}
226
227/// To avoid accidentally instantiating multiple monomorphizations of large
228/// serialization routines, we define explicit concrete types and require
229/// them in those routines. This avoids accidental mixing of String and
230/// nsACString arguments in Gecko, which would cause code size to blow up.
231#[cfg(feature = "gecko")]
232pub type CssStringWriter = ::nsstring::nsACString;
233
234/// String type that coerces to CssStringWriter, used when serialization code
235/// needs to allocate a temporary string. In Gecko, this is backed by
236/// nsCString, which allows the result to be passed directly to C++ without
237/// conversion or copying. This makes it suitable not only for temporary
238/// serialization but also for values that need to cross the Rust/C++ boundary.
239#[cfg(feature = "gecko")]
240pub type CssString = ::nsstring::nsCString;
241
242/// String. The comments for the Gecko types explain the need for this abstraction.
243#[cfg(feature = "servo")]
244pub type CssStringWriter = String;
245
246/// String. The comments for the Gecko types explain the need for this abstraction.
247#[cfg(feature = "servo")]
248pub type CssString = String;
249
250/// Convenience wrapper to serialise CSS values separated by a given string.
251pub struct SequenceWriter<'a, 'b: 'a, W: 'b> {
252    inner: &'a mut CssWriter<'b, W>,
253    separator: &'static str,
254}
255
256impl<'a, 'b, W> SequenceWriter<'a, 'b, W>
257where
258    W: Write + 'b,
259{
260    /// Create a new sequence writer.
261    #[inline]
262    pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self {
263        if inner.prefix.is_none() {
264            // See comment in `item`.
265            inner.prefix = Some("");
266        }
267        Self { inner, separator }
268    }
269
270    /// Serialize the CSS Value with the specific serialization function.
271    #[inline]
272    pub fn write_item<F>(&mut self, f: F) -> fmt::Result
273    where
274        F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result,
275    {
276        // Separate non-generic functions so that this code is not repeated
277        // in every monomorphization with a different type `F` or `W`.
278        // https://github.com/servo/servo/issues/26713
279        fn before(
280            prefix: &mut Option<&'static str>,
281            separator: &'static str,
282        ) -> Option<&'static str> {
283            let old_prefix = *prefix;
284            if old_prefix.is_none() {
285                // If there is no prefix in the inner writer, a previous
286                // call to this method produced output, which means we need
287                // to write the separator next time we produce output again.
288                *prefix = Some(separator);
289            }
290            old_prefix
291        }
292        fn after(
293            old_prefix: Option<&'static str>,
294            prefix: &mut Option<&'static str>,
295            separator: &'static str,
296        ) {
297            match (old_prefix, *prefix) {
298                (_, None) => {
299                    // This call produced output and cleaned up after itself.
300                },
301                (None, Some(p)) => {
302                    // Some previous call to `item` produced output,
303                    // but this one did not, prefix should be the same as
304                    // the one we set.
305                    debug_assert_eq!(separator, p);
306                    // We clean up here even though it's not necessary just
307                    // to be able to do all these assertion checks.
308                    *prefix = None;
309                },
310                (Some(old), Some(new)) => {
311                    // No previous call to `item` produced output, and this one
312                    // either.
313                    debug_assert_eq!(old, new);
314                },
315            }
316        }
317
318        let old_prefix = before(&mut self.inner.prefix, self.separator);
319        f(self.inner)?;
320        after(old_prefix, &mut self.inner.prefix, self.separator);
321        Ok(())
322    }
323
324    /// Serialises a CSS value, writing any separator as necessary.
325    ///
326    /// The separator is never written before any `item` produces any output,
327    /// and is written in subsequent calls only if the `item` produces some
328    /// output on its own again. This lets us handle `Option<T>` fields by
329    /// just not printing anything on `None`.
330    #[inline]
331    pub fn item<T>(&mut self, item: &T) -> fmt::Result
332    where
333        T: ToCss,
334    {
335        self.write_item(|inner| item.to_css(inner))
336    }
337
338    /// Writes a string as-is (i.e. not escaped or wrapped in quotes)
339    /// with any separator as necessary.
340    ///
341    /// See SequenceWriter::item.
342    #[inline]
343    pub fn raw_item(&mut self, item: &str) -> fmt::Result {
344        self.write_item(|inner| inner.write_str(item))
345    }
346}
347
348/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
349/// type to indicate that a serialized list of elements of this type is
350/// separated by commas.
351pub struct Comma;
352
353/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
354/// type to indicate that a serialized list of elements of this type is
355/// separated by spaces.
356pub struct Space;
357
358/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
359/// type to indicate that a serialized list of elements of this type is
360/// separated by commas, but spaces without commas are also allowed when
361/// parsing.
362pub struct CommaWithSpace;
363
364/// A trait satisfied by the types corresponding to separators.
365pub trait Separator {
366    /// The separator string that the satisfying separator type corresponds to.
367    fn separator() -> &'static str;
368
369    /// Parses a sequence of values separated by this separator.
370    ///
371    /// The given closure is called repeatedly for each item in the sequence.
372    ///
373    /// Successful results are accumulated in a vector.
374    ///
375    /// This method returns `Err(_)` the first time a closure does or if
376    /// the separators aren't correct.
377    fn parse<'i, 't, F, T, E>(
378        parser: &mut Parser<'i, 't>,
379        parse_one: F,
380    ) -> Result<Vec<T>, ParseError<'i, E>>
381    where
382        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>;
383}
384
385impl Separator for Comma {
386    fn separator() -> &'static str {
387        ", "
388    }
389
390    fn parse<'i, 't, F, T, E>(
391        input: &mut Parser<'i, 't>,
392        parse_one: F,
393    ) -> Result<Vec<T>, ParseError<'i, E>>
394    where
395        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
396    {
397        input.parse_comma_separated(parse_one)
398    }
399}
400
401impl Separator for Space {
402    fn separator() -> &'static str {
403        " "
404    }
405
406    fn parse<'i, 't, F, T, E>(
407        input: &mut Parser<'i, 't>,
408        mut parse_one: F,
409    ) -> Result<Vec<T>, ParseError<'i, E>>
410    where
411        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
412    {
413        input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
414        let mut results = vec![parse_one(input)?];
415        loop {
416            input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
417            if let Ok(item) = input.try_parse(&mut parse_one) {
418                results.push(item);
419            } else {
420                return Ok(results);
421            }
422        }
423    }
424}
425
426impl Separator for CommaWithSpace {
427    fn separator() -> &'static str {
428        ", "
429    }
430
431    fn parse<'i, 't, F, T, E>(
432        input: &mut Parser<'i, 't>,
433        mut parse_one: F,
434    ) -> Result<Vec<T>, ParseError<'i, E>>
435    where
436        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>,
437    {
438        input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
439        let mut results = vec![parse_one(input)?];
440        loop {
441            input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
442            let comma_location = input.current_source_location();
443            let comma = input.try_parse(|i| i.expect_comma()).is_ok();
444            input.skip_whitespace(); // Unnecessary for correctness, but may help try_parse() rewind less.
445            if let Ok(item) = input.try_parse(&mut parse_one) {
446                results.push(item);
447            } else if comma {
448                return Err(comma_location.new_unexpected_token_error(Token::Comma));
449            } else {
450                break;
451            }
452        }
453        Ok(results)
454    }
455}
456
457/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
458/// separated by some delimiter `delim`.
459pub trait OneOrMoreSeparated {
460    /// Associated type indicating which separator is used.
461    type S: Separator;
462}
463
464impl OneOrMoreSeparated for UnicodeRange {
465    type S = Comma;
466}
467
468impl<T> ToCss for Vec<T>
469where
470    T: ToCss + OneOrMoreSeparated,
471{
472    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
473    where
474        W: Write,
475    {
476        let mut iter = self.iter();
477        iter.next().unwrap().to_css(dest)?;
478        for item in iter {
479            dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;
480            item.to_css(dest)?;
481        }
482        Ok(())
483    }
484}
485
486impl<T> ToCss for Box<T>
487where
488    T: ?Sized + ToCss,
489{
490    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
491    where
492        W: Write,
493    {
494        (**self).to_css(dest)
495    }
496}
497
498impl<T> ToCss for Arc<T>
499where
500    T: ?Sized + ToCss,
501{
502    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
503    where
504        W: Write,
505    {
506        (**self).to_css(dest)
507    }
508}
509
510impl ToCss for Au {
511    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
512    where
513        W: Write,
514    {
515        self.to_f64_px().to_css(dest)?;
516        dest.write_str("px")
517    }
518}
519
520macro_rules! impl_to_css_for_predefined_type {
521    ($name: ty) => {
522        impl<'a> ToCss for $name {
523            fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
524            where
525                W: Write,
526            {
527                ::cssparser::ToCss::to_css(self, dest)
528            }
529        }
530    };
531}
532
533impl_to_css_for_predefined_type!(f32);
534impl_to_css_for_predefined_type!(i8);
535impl_to_css_for_predefined_type!(i32);
536impl_to_css_for_predefined_type!(u8);
537impl_to_css_for_predefined_type!(u16);
538impl_to_css_for_predefined_type!(u32);
539impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
540impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
541
542/// Helper types for the handling of specified values.
543pub mod specified {
544    use crate::ParsingMode;
545
546    /// Whether to allow negative lengths or not.
547    #[repr(u8)]
548    #[derive(
549        Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, PartialOrd, Serialize, ToShmem,
550    )]
551    pub enum AllowedNumericType {
552        /// Allow all kind of numeric values.
553        All,
554        /// Allow only non-negative numeric values.
555        NonNegative,
556        /// Allow only numeric values greater or equal to 1.0.
557        AtLeastOne,
558        /// Allow only numeric values from 0 to 1.0.
559        ZeroToOne,
560    }
561
562    impl Default for AllowedNumericType {
563        #[inline]
564        fn default() -> Self {
565            AllowedNumericType::All
566        }
567    }
568
569    impl AllowedNumericType {
570        /// Whether the value fits the rules of this numeric type.
571        #[inline]
572        pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool {
573            if parsing_mode.allows_all_numeric_values() {
574                return true;
575            }
576            match *self {
577                AllowedNumericType::All => true,
578                AllowedNumericType::NonNegative => val >= 0.0,
579                AllowedNumericType::AtLeastOne => val >= 1.0,
580                AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0,
581            }
582        }
583
584        /// Clamp the value following the rules of this numeric type.
585        #[inline]
586        pub fn clamp(&self, val: f32) -> f32 {
587            match *self {
588                AllowedNumericType::All => val,
589                AllowedNumericType::NonNegative => val.max(0.),
590                AllowedNumericType::AtLeastOne => val.max(1.),
591                AllowedNumericType::ZeroToOne => val.max(0.).min(1.),
592            }
593        }
594    }
595}
596
597/// A property-agnostic representation of a value, used by Typed OM.
598///
599/// `TypedValue` is the internal counterpart of the various `CSSStyleValue`
600/// subclasses defined by the Typed OM specification. It captures values that
601/// can be represented independently of any particular property.
602#[derive(Clone, Debug)]
603#[repr(C)]
604pub enum TypedValue {
605    /// A keyword value (e.g. `"block"`, `"none"`, `"thin"`).
606    ///
607    /// Keywords are stored as a `CssString` so they can be represented and
608    /// transferred independently of any specific property. This corresponds
609    /// to `CSSKeywordValue` in the Typed OM specification.
610    Keyword(CssString),
611}
612
613/// Reifies a value into its Typed OM representation.
614///
615/// This trait is the Typed OM analogue of [`ToCss`]. Instead of serializing
616/// values into CSS syntax, it converts them into [`TypedValue`]s that can be
617/// exposed to the DOM as `CSSStyleValue` subclasses.
618///
619/// This trait is derivable with `#[derive(ToTyped)]`. The derived
620/// implementation behaves as follows:
621///
622/// * For enums whose variants are all unit variants (representing keywords),
623///   it automatically reifies the value as [`TypedValue::Keyword`], using the
624///   same serialization logic as [`ToCss`].
625/// * For all other cases, the derived implementation does not attempt to reify
626///   anything and falls back to the default method (which always returns
627///   `None`).
628///
629/// Over time, the derive may be extended to cover additional common patterns,
630/// similar to how `ToCss` supports multiple attribute annotations.
631pub trait ToTyped {
632    /// Attempt to convert `self` into a [`TypedValue`].
633    ///
634    /// Returns `Some(TypedValue)` if the value can be reified into a
635    /// property-agnostic CSSStyleValue subclass. Returns `None` if the value
636    /// is unrepresentable, in which case reification produces a property-tied
637    /// CSSStyleValue instead.
638    fn to_typed(&self) -> Option<TypedValue> {
639        None
640    }
641}
642
643impl<T> ToTyped for Box<T>
644where
645    T: ?Sized + ToTyped,
646{
647    fn to_typed(&self) -> Option<TypedValue> {
648        (**self).to_typed()
649    }
650}
651
652impl ToTyped for Au {
653    fn to_typed(&self) -> Option<TypedValue> {
654        // XXX Should return TypedValue::Numeric in px units once that variant
655        // is available. Tracked in bug 1990419.
656        None
657    }
658}
659
660macro_rules! impl_to_typed_for_predefined_type {
661    ($name: ty) => {
662        impl<'a> ToTyped for $name {
663            fn to_typed(&self) -> Option<TypedValue> {
664                // XXX Should return TypedValue::Numeric with unit "number"
665                // once that variant is available. Tracked in bug 1990419.
666                None
667            }
668        }
669    };
670}
671
672impl_to_typed_for_predefined_type!(f32);
673impl_to_typed_for_predefined_type!(i8);
674impl_to_typed_for_predefined_type!(i32);