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