tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

values.rs (24637B)


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