Skip to main content

style/values/
mod.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//! Common [values][values] used in CSS.
6//!
7//! [values]: https://drafts.csswg.org/css-values/
8
9#![deny(missing_docs)]
10
11use crate::Atom;
12use crate::derives::*;
13use crate::parser::{Parse, ParserContext};
14use crate::typed_om::{KeywordValue, NumericType, NumericValue, ToTyped, TypedValue, UnitValue};
15use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
16use crate::values::generics::position::IsTreeScoped;
17pub use cssparser::{CowRcStr, Parser, serialize_identifier, serialize_name};
18pub use cssparser::{SourceLocation, Token};
19use num_traits::Zero;
20use precomputed_hash::PrecomputedHash;
21use selectors::parser::SelectorParseErrorKind;
22use std::fmt::{self, Debug, Write};
23use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
24use thin_vec::ThinVec;
25use to_shmem::impl_trivial_to_shmem;
26
27pub use crate::url::CssUrl;
28
29pub mod animated;
30pub mod computed;
31pub mod distance;
32pub mod generics;
33pub mod resolved;
34pub mod specified;
35pub mod tagged_numeric;
36
37/// A CSS float value.
38pub type CSSFloat = f32;
39
40/// Normalizes a float value to zero after a set of operations that might turn
41/// it into NaN.
42#[inline]
43pub fn normalize(v: CSSFloat) -> CSSFloat {
44    if v.is_nan() { 0.0 } else { v }
45}
46
47/// Computes the minimum value of the two floats. The CSS Values and Units definition
48/// for min() considers -0 to be less than +0 (whereas Rust considers them equal).
49/// https://drafts.csswg.org/css-values-4/#css-signed-zero
50#[inline]
51pub fn calc_min(a: CSSFloat, b: CSSFloat) -> CSSFloat {
52    match (a.is_sign_negative(), b.is_sign_negative()) {
53        (true, false) => a,
54        (false, true) => b,
55        _ => a.min(b),
56    }
57}
58
59/// Computes the maximum value of the two floats. The CSS Values and Units definition
60/// for max() considers +0 to be greater than -0 (whereas Rust considers them equal).
61/// https://drafts.csswg.org/css-values-4/#css-signed-zero
62#[inline]
63pub fn calc_max(a: CSSFloat, b: CSSFloat) -> CSSFloat {
64    match (a.is_sign_negative(), b.is_sign_negative()) {
65        (true, false) => b,
66        (false, true) => a,
67        _ => a.max(b),
68    }
69}
70
71/// Computes the sign of the given value. The CSS Values and Units definition for
72/// sign() returns +0 or -0 for an input of +0 or -0, respectively (whereas the
73/// Rust f32::signum() function returns +1 or -1, respectively).
74/// https://drafts.csswg.org/css-values-4/#funcdef-sign
75#[inline]
76pub fn calc_sign(value: CSSFloat) -> CSSFloat {
77    if value.is_nan() {
78        f32::NAN
79    } else if value.is_zero() {
80        value
81    } else if value.is_sign_negative() {
82        -1.0
83    } else {
84        1.0
85    }
86}
87
88/// Generates a random integer given a base value and limit.
89/// https://drafts.csswg.org/css-values-5/#generate-a-random-integer
90pub fn calc_random_integer(base: f32, limit: f32) -> f32 {
91    (base * limit).floor()
92}
93
94/// Evaluates a random() function given a base value and its arguments.
95/// https://drafts.csswg.org/css-values-5/#random-evaluation
96pub fn calc_random(base: f32, min: f32, max: f32, step: Option<f32>) -> f32 {
97    if base.is_nan() || min.is_nan() || max.is_nan() || step.is_some_and(f32::is_nan) {
98        return f32::NAN;
99    }
100
101    // Clamps a <number> into the range of a random base value, using the highest
102    // f32 strictly less than one. The random base value has a half-open range of
103    // [0, 1), but the `fixed <number>` format of <random-key> accepts the closed
104    // range of [0, 1]. So if an author specifies `fixed 1`, this value is used
105    // instead as the random base value.
106    let base = normalize(base.max(0.).min((1.0f32).next_down()));
107
108    // "In random(A, B), if A is infinite, the result is infinite."
109    if min.is_infinite() {
110        return min;
111    }
112
113    // "If A is finite, but the difference between A and B is either infinite or
114    // large enough to be treated as infinite in the user agent, the result is
115    // NaN."
116    if !(max - min).is_finite() {
117        return f32::NAN;
118    }
119
120    // "If the maximum value is less than the minimum value, it behaves as if
121    // it's equal to the minimum value."
122    let max = if max < min { min } else { max };
123    let range = max - min;
124
125    let Some(step) = step else {
126        return min + base * range;
127    };
128
129    // "If C is infinite, the result is A."
130    if step.is_infinite() {
131        return min;
132    }
133
134    // "If C is negative, zero, or positive but close enough to zero that the
135    // range for the step multiplier would be infinite in the user agent, the
136    // step must be ignored."
137    if step <= 0.0 {
138        return min + base * range;
139    }
140
141    // "Let epsilon be step / 1000, or the smallest representable value greater
142    // than zero in the numeric type being used if epsilon would round to zero."
143    let epsilon = match step / 1000.0 {
144        e if e > 0.0 => e,
145        _ => (0.0f32).next_up(),
146    };
147
148    // "Let N be the largest integer such that min + N * step is less than or
149    // equal to max."
150    let mut n = (range / step).floor();
151    if n.is_infinite() {
152        return min + base * range;
153    }
154
155    // "If N produces a value that is not within epsilon of max, but N+1 would
156    // produce a value within epsilon of max, set N to N+1."
157    if (min + n * step - max).abs() > epsilon && (min + (n + 1.0) * step - max).abs() <= epsilon {
158        n += 1.0;
159    }
160
161    // "Let step index be a random integer less than N+1, given R. Let value
162    // be min + step index * step."
163    let step_index = calc_random_integer(base, n + 1.0);
164    let value = min + step_index * step;
165
166    // "If step index is N and value is within epsilon of max, return max."
167    if step_index == n && (value - max).abs() <= epsilon {
168        return max;
169    }
170
171    value
172}
173
174/// A CSS integer value.
175pub type CSSInteger = i32;
176
177/// Serialize an identifier which is represented as an atom.
178#[cfg(feature = "gecko")]
179pub fn serialize_atom_identifier<W>(ident: &Atom, dest: &mut W) -> fmt::Result
180where
181    W: Write,
182{
183    ident.with_str(|s| serialize_identifier(s, dest))
184}
185
186/// Serialize an identifier which is represented as an atom.
187#[cfg(feature = "servo")]
188pub fn serialize_atom_identifier<Static, W>(
189    ident: &::string_cache::Atom<Static>,
190    dest: &mut W,
191) -> fmt::Result
192where
193    Static: string_cache::StaticAtomSet,
194    W: Write,
195{
196    serialize_identifier(&ident, dest)
197}
198
199/// Serialize a name which is represented as an Atom.
200#[cfg(feature = "gecko")]
201pub fn serialize_atom_name<W>(ident: &Atom, dest: &mut W) -> fmt::Result
202where
203    W: Write,
204{
205    ident.with_str(|s| serialize_name(s, dest))
206}
207
208/// Serialize a name which is represented as an Atom.
209#[cfg(feature = "servo")]
210pub fn serialize_atom_name<Static, W>(
211    ident: &::string_cache::Atom<Static>,
212    dest: &mut W,
213) -> fmt::Result
214where
215    Static: string_cache::StaticAtomSet,
216    W: Write,
217{
218    serialize_name(&ident, dest)
219}
220
221/// Serialize a number with calc, and NaN/infinity handling (if enabled)
222pub fn serialize_number<W>(v: f32, dest: &mut CssWriter<W>) -> fmt::Result
223where
224    W: Write,
225{
226    serialize_specified_dimension(v, "", /* was_calc = */ false, dest)
227}
228
229/// Serialize a specified dimension with unit, calc, and NaN/infinity handling (if enabled)
230pub fn serialize_specified_dimension<W>(
231    v: f32,
232    unit: &str,
233    was_calc: bool,
234    dest: &mut CssWriter<W>,
235) -> fmt::Result
236where
237    W: Write,
238{
239    if was_calc {
240        dest.write_str("calc(")?;
241    }
242
243    if !v.is_finite() {
244        // https://drafts.csswg.org/css-values/#calc-error-constants:
245        // "While not technically numbers, these keywords act as numeric values,
246        // similar to e and pi. Thus to get an infinite length, for example,
247        // requires an expression like calc(infinity * 1px)."
248
249        if v.is_nan() {
250            dest.write_str("NaN")?;
251        } else if v == f32::INFINITY {
252            dest.write_str("infinity")?;
253        } else if v == f32::NEG_INFINITY {
254            dest.write_str("-infinity")?;
255        }
256
257        if !unit.is_empty() {
258            dest.write_str(" * 1")?;
259        }
260    } else {
261        v.to_css(dest)?;
262    }
263
264    dest.write_str(unit)?;
265
266    if was_calc {
267        dest.write_char(')')?;
268    }
269    Ok(())
270}
271
272/// A CSS string stored as an `Atom`.
273#[repr(transparent)]
274#[derive(
275    Clone,
276    Debug,
277    Default,
278    Deref,
279    Eq,
280    Hash,
281    MallocSizeOf,
282    PartialEq,
283    SpecifiedValueInfo,
284    ToAnimatedValue,
285    ToComputedValue,
286    ToResolvedValue,
287    ToShmem,
288)]
289pub struct AtomString(pub Atom);
290
291#[cfg(feature = "servo")]
292impl AsRef<str> for AtomString {
293    fn as_ref(&self) -> &str {
294        &*self.0
295    }
296}
297
298impl Parse for AtomString {
299    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
300        Ok(Self(Atom::from(input.expect_string()?.as_ref())))
301    }
302}
303
304impl cssparser::ToCss for AtomString {
305    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
306    where
307        W: Write,
308    {
309        // Wrap in quotes to form a string literal
310        dest.write_char('"')?;
311        #[cfg(feature = "servo")]
312        {
313            cssparser::CssStringWriter::new(dest).write_str(self.as_ref())?;
314        }
315        #[cfg(feature = "gecko")]
316        {
317            self.0
318                .with_str(|s| cssparser::CssStringWriter::new(dest).write_str(s))?;
319        }
320        dest.write_char('"')
321    }
322}
323
324impl style_traits::ToCss for AtomString {
325    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
326    where
327        W: Write,
328    {
329        cssparser::ToCss::to_css(self, dest)
330    }
331}
332
333impl PrecomputedHash for AtomString {
334    #[inline]
335    fn precomputed_hash(&self) -> u32 {
336        self.0.precomputed_hash()
337    }
338}
339
340impl From<&str> for AtomString {
341    #[inline]
342    fn from(string: &str) -> Self {
343        Self(Atom::from(string))
344    }
345}
346
347/// A generic CSS `<ident>` stored as an `Atom`.
348#[cfg(feature = "servo")]
349#[repr(transparent)]
350#[derive(Deref)]
351pub struct GenericAtomIdent<Set>(pub string_cache::Atom<Set>)
352where
353    Set: string_cache::StaticAtomSet;
354
355/// A generic CSS `<ident>` stored as an `Atom`, for the default atom set.
356#[cfg(feature = "servo")]
357pub type AtomIdent = GenericAtomIdent<stylo_atoms::AtomStaticSet>;
358
359#[cfg(feature = "servo")]
360impl<Set: string_cache::StaticAtomSet> style_traits::SpecifiedValueInfo for GenericAtomIdent<Set> {}
361
362#[cfg(feature = "servo")]
363impl<Set: string_cache::StaticAtomSet> Default for GenericAtomIdent<Set> {
364    fn default() -> Self {
365        Self(string_cache::Atom::default())
366    }
367}
368
369#[cfg(feature = "servo")]
370impl<Set: string_cache::StaticAtomSet> std::fmt::Debug for GenericAtomIdent<Set> {
371    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
372        self.0.fmt(f)
373    }
374}
375
376#[cfg(feature = "servo")]
377impl<Set: string_cache::StaticAtomSet> std::hash::Hash for GenericAtomIdent<Set> {
378    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
379        self.0.hash(state)
380    }
381}
382
383#[cfg(feature = "servo")]
384impl<Set: string_cache::StaticAtomSet> Eq for GenericAtomIdent<Set> {}
385
386#[cfg(feature = "servo")]
387impl<Set: string_cache::StaticAtomSet> PartialEq for GenericAtomIdent<Set> {
388    fn eq(&self, other: &Self) -> bool {
389        self.0 == other.0
390    }
391}
392
393#[cfg(feature = "servo")]
394impl<Set: string_cache::StaticAtomSet> Clone for GenericAtomIdent<Set> {
395    fn clone(&self) -> Self {
396        Self(self.0.clone())
397    }
398}
399
400#[cfg(feature = "servo")]
401impl<Set: string_cache::StaticAtomSet> to_shmem::ToShmem for GenericAtomIdent<Set> {
402    fn to_shmem(&self, builder: &mut to_shmem::SharedMemoryBuilder) -> to_shmem::Result<Self> {
403        use std::mem::ManuallyDrop;
404
405        let atom = self.0.to_shmem(builder)?;
406        Ok(ManuallyDrop::new(Self(ManuallyDrop::into_inner(atom))))
407    }
408}
409
410#[cfg(feature = "servo")]
411impl<Set: string_cache::StaticAtomSet> malloc_size_of::MallocSizeOf for GenericAtomIdent<Set> {
412    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
413        self.0.size_of(ops)
414    }
415}
416
417#[cfg(feature = "servo")]
418impl<Set: string_cache::StaticAtomSet> cssparser::ToCss for GenericAtomIdent<Set> {
419    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
420    where
421        W: Write,
422    {
423        serialize_atom_identifier(&self.0, dest)
424    }
425}
426
427#[cfg(feature = "servo")]
428impl<Set: string_cache::StaticAtomSet> style_traits::ToCss for GenericAtomIdent<Set> {
429    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
430    where
431        W: Write,
432    {
433        serialize_atom_identifier(&self.0, dest)
434    }
435}
436
437#[cfg(feature = "servo")]
438impl<Set: string_cache::StaticAtomSet> PrecomputedHash for GenericAtomIdent<Set> {
439    #[inline]
440    fn precomputed_hash(&self) -> u32 {
441        self.0.precomputed_hash()
442    }
443}
444
445#[cfg(feature = "servo")]
446impl<'a, Set: string_cache::StaticAtomSet> From<&'a str> for GenericAtomIdent<Set> {
447    #[inline]
448    fn from(string: &str) -> Self {
449        Self(string_cache::Atom::from(string))
450    }
451}
452
453#[cfg(feature = "servo")]
454impl<Set: string_cache::StaticAtomSet> std::borrow::Borrow<string_cache::Atom<Set>>
455    for GenericAtomIdent<Set>
456{
457    #[inline]
458    fn borrow(&self) -> &string_cache::Atom<Set> {
459        &self.0
460    }
461}
462
463#[cfg(feature = "servo")]
464impl<Set: string_cache::StaticAtomSet> GenericAtomIdent<Set> {
465    /// Constructs a new GenericAtomIdent.
466    #[inline]
467    pub fn new(atom: string_cache::Atom<Set>) -> Self {
468        Self(atom)
469    }
470
471    /// Cast an atom ref to an AtomIdent ref.
472    #[inline]
473    pub fn cast<'a>(atom: &'a string_cache::Atom<Set>) -> &'a Self {
474        let ptr = atom as *const _ as *const Self;
475        // safety: repr(transparent)
476        unsafe { &*ptr }
477    }
478}
479
480/// A CSS `<ident>` stored as an `Atom`.
481#[cfg(feature = "gecko")]
482#[repr(transparent)]
483#[derive(
484    Clone, Debug, Default, Deref, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem,
485)]
486pub struct AtomIdent(pub Atom);
487
488#[cfg(feature = "gecko")]
489impl cssparser::ToCss for AtomIdent {
490    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
491    where
492        W: Write,
493    {
494        serialize_atom_identifier(&self.0, dest)
495    }
496}
497
498#[cfg(feature = "gecko")]
499impl style_traits::ToCss for AtomIdent {
500    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
501    where
502        W: Write,
503    {
504        cssparser::ToCss::to_css(self, dest)
505    }
506}
507
508#[cfg(feature = "gecko")]
509impl PrecomputedHash for AtomIdent {
510    #[inline]
511    fn precomputed_hash(&self) -> u32 {
512        self.0.precomputed_hash()
513    }
514}
515
516#[cfg(feature = "gecko")]
517impl From<&str> for AtomIdent {
518    #[inline]
519    fn from(string: &str) -> Self {
520        Self(Atom::from(string))
521    }
522}
523
524#[cfg(feature = "gecko")]
525impl AtomIdent {
526    /// Constructs a new AtomIdent.
527    #[inline]
528    pub fn new(atom: Atom) -> Self {
529        Self(atom)
530    }
531
532    /// Like `Atom::with` but for `AtomIdent`.
533    pub unsafe fn with<F, R>(ptr: *const crate::gecko_bindings::structs::nsAtom, callback: F) -> R
534    where
535        F: FnOnce(&Self) -> R,
536    {
537        unsafe {
538            Atom::with(ptr, |atom: &Atom| {
539                // safety: repr(transparent)
540                let atom = atom as *const Atom as *const AtomIdent;
541                callback(&*atom)
542            })
543        }
544    }
545
546    /// Cast an atom ref to an AtomIdent ref.
547    #[inline]
548    pub fn cast(atom: &Atom) -> &Self {
549        let ptr = atom as *const _ as *const Self;
550        // safety: repr(transparent)
551        unsafe { &*ptr }
552    }
553}
554
555#[cfg(feature = "gecko")]
556impl std::borrow::Borrow<crate::gecko_string_cache::WeakAtom> for AtomIdent {
557    #[inline]
558    fn borrow(&self) -> &crate::gecko_string_cache::WeakAtom {
559        self.0.borrow()
560    }
561}
562
563/// Serialize a value into percentage.
564pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result
565where
566    W: Write,
567{
568    serialize_specified_dimension(value * 100., "%", /* was_calc = */ false, dest)
569}
570
571/// Serialize a value into normalized (no NaN/inf serialization) percentage.
572pub fn serialize_normalized_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result
573where
574    W: Write,
575{
576    (value * 100.).to_css(dest)?;
577    dest.write_char('%')
578}
579
580/// Reify a percentage.
581pub fn reify_percentage(value: CSSFloat, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
582    let numeric_value = NumericValue::Unit(UnitValue {
583        numeric_type: NumericType::percent(),
584        value: value * 100.,
585        unit: CssString::from("percent"),
586    });
587
588    dest.push(TypedValue::Numeric(numeric_value));
589    Ok(())
590}
591
592/// Convenience void type to disable some properties and values through types.
593#[derive(
594    Clone,
595    Copy,
596    Debug,
597    Deserialize,
598    MallocSizeOf,
599    PartialEq,
600    Serialize,
601    SpecifiedValueInfo,
602    ToAnimatedValue,
603    ToComputedValue,
604    ToCss,
605    ToResolvedValue,
606)]
607pub enum Impossible {}
608
609// FIXME(nox): This should be derived but the derive code cannot cope
610// with uninhabited enums.
611impl ComputeSquaredDistance for Impossible {
612    #[inline]
613    fn compute_squared_distance(&self, _other: &Self) -> Result<SquaredDistance, ()> {
614        match *self {}
615    }
616}
617
618impl_trivial_to_shmem!(Impossible);
619
620impl Parse for Impossible {
621    fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<Self, ParseError> {
622        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
623    }
624}
625
626/// A struct representing one of two kinds of values.
627#[derive(
628    Animate,
629    Clone,
630    ComputeSquaredDistance,
631    Copy,
632    MallocSizeOf,
633    PartialEq,
634    Parse,
635    SpecifiedValueInfo,
636    ToAnimatedValue,
637    ToAnimatedZero,
638    ToComputedValue,
639    ToCss,
640    ToResolvedValue,
641    ToShmem,
642)]
643pub enum Either<A, B> {
644    /// The first value.
645    First(A),
646    /// The second kind of value.
647    Second(B),
648}
649
650impl<A: Debug, B: Debug> Debug for Either<A, B> {
651    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
652        match *self {
653            Either::First(ref v) => v.fmt(f),
654            Either::Second(ref v) => v.fmt(f),
655        }
656    }
657}
658
659/// <https://drafts.csswg.org/css-values-4/#custom-idents>
660#[derive(
661    Clone,
662    Debug,
663    Default,
664    Deserialize,
665    Eq,
666    Hash,
667    MallocSizeOf,
668    PartialEq,
669    Serialize,
670    SpecifiedValueInfo,
671    ToAnimatedValue,
672    ToComputedValue,
673    ToResolvedValue,
674    ToShmem,
675)]
676#[repr(C)]
677pub struct CustomIdent(pub Atom);
678
679impl CustomIdent {
680    /// Parse a <custom-ident>
681    ///
682    /// TODO(zrhoffman, bug 1844501): Use CustomIdent::parse in more places instead of
683    /// CustomIdent::from_ident.
684    pub fn parse(input: &mut Parser, invalid: &[&str]) -> Result<Self, ParseError> {
685        let ident = input.expect_ident()?;
686        CustomIdent::from_ident(ident, invalid)
687    }
688
689    /// Parse an already-tokenizer identifier
690    pub fn from_ident<'i>(ident: &CowRcStr<'i>, excluding: &[&str]) -> Result<Self, ParseError> {
691        if !Self::is_valid(ident, excluding) {
692            return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
693        }
694        if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
695            Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
696        } else {
697            Ok(CustomIdent(Atom::from(ident.as_ref())))
698        }
699    }
700
701    fn is_valid(ident: &str, excluding: &[&str]) -> bool {
702        use crate::properties::CSSWideKeyword;
703        // https://drafts.csswg.org/css-values-4/#custom-idents:
704        //
705        //     The CSS-wide keywords are not valid <custom-ident>s. The default
706        //     keyword is reserved and is also not a valid <custom-ident>.
707        if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") {
708            return false;
709        }
710
711        // https://drafts.csswg.org/css-values-4/#custom-idents:
712        //
713        //     Excluded keywords are excluded in all ASCII case permutations.
714        !excluding.iter().any(|s| ident.eq_ignore_ascii_case(s))
715    }
716}
717
718impl ToCss for CustomIdent {
719    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
720    where
721        W: Write,
722    {
723        serialize_atom_identifier(&self.0, dest)
724    }
725}
726
727impl ToTyped for CustomIdent {
728    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
729        // This shouldn't escape identifiers. See bug 2023533.
730        let s = ToCss::to_css_cssstring(self);
731        dest.push(TypedValue::Keyword(KeywordValue(s)));
732        Ok(())
733    }
734}
735
736/// <https://www.w3.org/TR/css-values-4/#dashed-idents>
737/// This is simply an Atom, but will only parse if the identifier starts with "--".
738#[repr(transparent)]
739#[derive(
740    Clone,
741    Debug,
742    Eq,
743    Hash,
744    MallocSizeOf,
745    PartialEq,
746    SpecifiedValueInfo,
747    ToAnimatedValue,
748    ToComputedValue,
749    ToResolvedValue,
750    ToShmem,
751    Serialize,
752    Deserialize,
753)]
754pub struct DashedIdent(pub Atom);
755
756impl DashedIdent {
757    /// Parse an already-tokenizer identifier
758    pub fn from_ident<'i>(ident: &CowRcStr<'i>) -> Result<Self, ParseError> {
759        if !ident.starts_with("--") {
760            return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
761        }
762        Ok(Self(Atom::from(ident.as_ref())))
763    }
764
765    /// Special value for internal use. Useful where we can't use Option<>.
766    pub fn empty() -> Self {
767        Self(atom!(""))
768    }
769
770    /// Check for special internal value.
771    pub fn is_empty(&self) -> bool {
772        self.0 == atom!("")
773    }
774
775    /// Returns an atom with the same value, but without the starting "--".
776    ///
777    /// # Panics
778    ///
779    /// Panics when used on the special `DashedIdent::empty()`.
780    pub(crate) fn undashed(&self) -> Atom {
781        assert!(!self.is_empty(), "Can't undash the empty DashedIdent");
782        #[cfg(feature = "gecko")]
783        let name = &self.0.as_slice()[2..];
784        #[cfg(feature = "servo")]
785        let name = &self.0[2..];
786        Atom::from(name)
787    }
788}
789
790impl IsTreeScoped for DashedIdent {
791    fn is_tree_scoped(&self) -> bool {
792        !self.is_empty()
793    }
794}
795
796impl Parse for DashedIdent {
797    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
798        let ident = input.expect_ident()?;
799        Self::from_ident(ident)
800    }
801}
802
803impl ToCss for DashedIdent {
804    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
805    where
806        W: Write,
807    {
808        serialize_atom_identifier(&self.0, dest)
809    }
810}
811
812/// The <keyframes-name>.
813///
814/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name>
815///
816/// We use a single atom for this. Empty atom represents `none` animation.
817#[repr(transparent)]
818#[derive(
819    Clone,
820    Debug,
821    Eq,
822    Hash,
823    PartialEq,
824    MallocSizeOf,
825    SpecifiedValueInfo,
826    ToComputedValue,
827    ToResolvedValue,
828    ToShmem,
829)]
830pub struct KeyframesName(Atom);
831
832impl KeyframesName {
833    /// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
834    pub fn from_ident(value: &str) -> Self {
835        Self(Atom::from(value))
836    }
837
838    /// Returns the `none` value.
839    pub fn none() -> Self {
840        Self(atom!(""))
841    }
842
843    /// Returns whether this is the special `none` value.
844    pub fn is_none(&self) -> bool {
845        self.0 == atom!("")
846    }
847
848    /// Create a new KeyframesName from Atom.
849    #[cfg(feature = "gecko")]
850    pub fn from_atom(atom: Atom) -> Self {
851        Self(atom)
852    }
853
854    /// The name as an Atom
855    pub fn as_atom(&self) -> &Atom {
856        &self.0
857    }
858}
859
860impl Parse for KeyframesName {
861    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
862        Ok(match *input.next()? {
863            Token::Ident(ref s) => Self(CustomIdent::from_ident(s, &["none"])?.0),
864            // Note that empty <string> should be rejected.
865            Token::QuotedString(ref s) if !s.as_ref().is_empty() => Self(Atom::from(s.as_ref())),
866            _ => return Err(ParseError::unexpected_token()),
867        })
868    }
869}
870
871impl ToCss for KeyframesName {
872    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
873    where
874        W: Write,
875    {
876        if self.is_none() {
877            return dest.write_str("none");
878        }
879
880        fn serialize<W: Write>(string: &str, dest: &mut CssWriter<W>) -> fmt::Result {
881            if CustomIdent::is_valid(string, &["none"]) {
882                serialize_identifier(string, dest)
883            } else {
884                string.to_css(dest)
885            }
886        }
887
888        #[cfg(feature = "gecko")]
889        return self.0.with_str(|s| serialize(s, dest));
890
891        #[cfg(feature = "servo")]
892        return serialize(self.0.as_ref(), dest);
893    }
894}
895
896impl ToTyped for KeyframesName {
897    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
898        let s = ToCss::to_css_cssstring(self);
899        dest.push(TypedValue::Keyword(KeywordValue(s)));
900        Ok(())
901    }
902}