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