style/values/specified/
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//! Specified values.
6//!
7//! TODO(emilio): Enhance docs.
8
9use super::computed::transform::DirectionVector;
10use super::computed::{Context, ToComputedValue};
11use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks;
12use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
13use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
14use super::generics::transform::IsParallelTo;
15use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
16use super::{CSSFloat, CSSInteger};
17use crate::context::QuirksMode;
18use crate::parser::{Parse, ParserContext};
19use crate::values::specified::calc::CalcNode;
20use crate::values::{serialize_atom_identifier, serialize_number, AtomString};
21use crate::{Atom, Namespace, One, Prefix, Zero};
22use cssparser::{Parser, Token};
23use std::fmt::{self, Write};
24use std::ops::Add;
25use style_traits::values::specified::AllowedNumericType;
26use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
27
28pub use self::align::{AlignContent, AlignItems, AlignSelf, ContentDistribution};
29pub use self::align::{JustifyContent, JustifyItems, JustifySelf, SelfAlignment};
30pub use self::angle::{AllowUnitlessZeroAngle, Angle};
31pub use self::animation::{
32    AnimationComposition, AnimationDirection, AnimationDuration, AnimationFillMode,
33    AnimationIterationCount, AnimationName, AnimationPlayState, AnimationTimeline, ScrollAxis,
34    TimelineName, TransitionBehavior, TransitionProperty, ViewTimelineInset, ViewTransitionClass,
35    ViewTransitionName,
36};
37pub use self::background::{BackgroundRepeat, BackgroundSize};
38pub use self::basic_shape::FillRule;
39pub use self::border::{
40    BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice,
41    BorderImageWidth, BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle, LineWidth,
42};
43pub use self::box_::{
44    Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainIntrinsicSize,
45    ContainerName, ContainerType, ContentVisibility, Display, Float, LineClamp, Overflow,
46    OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, PositionProperty, Resize,
47    ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType,
48    ScrollbarGutter, TouchAction, VerticalAlign, WillChange, WillChangeBits, WritingModeProperty,
49    Zoom,
50};
51pub use self::color::{
52    Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust,
53};
54pub use self::column::ColumnCount;
55pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet};
56pub use self::easing::TimingFunction;
57pub use self::effects::{BoxShadow, Filter, SimpleShadow};
58pub use self::flex::FlexBasis;
59pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle};
60pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
61pub use self::font::{
62    FontSize, FontSizeAdjust, FontSizeAdjustFactor, FontSizeKeyword, FontStretch, FontSynthesis, FontSynthesisStyle,
63};
64pub use self::font::{FontVariantAlternates, FontWeight};
65pub use self::font::{FontVariantEastAsian, FontVariationSettings, LineHeight};
66pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale};
67pub use self::image::{EndingShape as GradientEndingShape, Gradient, Image, ImageRendering};
68pub use self::length::{AbsoluteLength, AnchorSizeFunction, CalcLengthPercentage, CharacterWidth};
69pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
70pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
71pub use self::length::{Margin, MaxSize, Size};
72pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant};
73pub use self::length::{
74    NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
75};
76#[cfg(feature = "gecko")]
77pub use self::list::ListStyleType;
78pub use self::list::Quotes;
79pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate};
80pub use self::outline::OutlineStyle;
81pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize};
82pub use self::percentage::{NonNegativePercentage, Percentage};
83pub use self::position::AnchorFunction;
84pub use self::position::AnchorName;
85pub use self::position::AnchorScope;
86pub use self::position::AspectRatio;
87pub use self::position::Inset;
88pub use self::position::PositionAnchor;
89pub use self::position::PositionTryFallbacks;
90pub use self::position::PositionTryOrder;
91pub use self::position::PositionVisibility;
92pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto};
93pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement};
94pub use self::position::{PositionArea, PositionAreaKeyword};
95pub use self::position::{PositionComponent, ZIndex};
96pub use self::ratio::Ratio;
97pub use self::rect::NonNegativeLengthOrNumberRect;
98pub use self::resolution::Resolution;
99pub use self::svg::{DProperty, MozContextProperties};
100pub use self::svg::{SVGLength, SVGOpacity, SVGPaint};
101pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth, VectorEffect};
102pub use self::svg_path::SVGPathData;
103pub use self::text::{HyphenateCharacter, HyphenateLimitChars};
104pub use self::text::RubyPosition;
105pub use self::text::TextAlignLast;
106pub use self::text::TextUnderlinePosition;
107pub use self::text::{InitialLetter, LetterSpacing, LineBreak, TextAlign, TextIndent};
108pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
109pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
110pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform};
111pub use self::time::Time;
112pub use self::transform::{Rotate, Scale, Transform};
113pub use self::transform::{TransformBox, TransformOrigin, TransformStyle, Translate};
114#[cfg(feature = "gecko")]
115pub use self::ui::CursorImage;
116pub use self::ui::{
117    BoolInteger, Cursor, Inert, MozTheme, PointerEvents, ScrollbarColor, UserFocus, UserInput,
118    UserSelect,
119};
120pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
121
122pub mod align;
123pub mod angle;
124pub mod animation;
125pub mod background;
126pub mod basic_shape;
127pub mod border;
128#[path = "box.rs"]
129pub mod box_;
130pub mod calc;
131pub mod color;
132pub mod column;
133pub mod counters;
134pub mod easing;
135pub mod effects;
136pub mod flex;
137pub mod font;
138pub mod grid;
139pub mod image;
140pub mod intersection_observer;
141pub mod length;
142pub mod list;
143pub mod motion;
144pub mod outline;
145pub mod page;
146pub mod percentage;
147pub mod position;
148pub mod ratio;
149pub mod rect;
150pub mod resolution;
151pub mod source_size_list;
152pub mod svg;
153pub mod svg_path;
154pub mod table;
155pub mod text;
156pub mod time;
157pub mod transform;
158pub mod ui;
159pub mod url;
160
161/// <angle> | <percentage>
162/// https://drafts.csswg.org/css-values/#typedef-angle-percentage
163#[allow(missing_docs)]
164#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
165pub enum AngleOrPercentage {
166    Percentage(Percentage),
167    Angle(Angle),
168}
169
170impl AngleOrPercentage {
171    fn parse_internal<'i, 't>(
172        context: &ParserContext,
173        input: &mut Parser<'i, 't>,
174        allow_unitless_zero: AllowUnitlessZeroAngle,
175    ) -> Result<Self, ParseError<'i>> {
176        if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) {
177            return Ok(AngleOrPercentage::Percentage(per));
178        }
179
180        Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle)
181    }
182
183    /// Allow unitless angles, used for conic-gradients as specified by the spec.
184    /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle
185    pub fn parse_with_unitless<'i, 't>(
186        context: &ParserContext,
187        input: &mut Parser<'i, 't>,
188    ) -> Result<Self, ParseError<'i>> {
189        AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
190    }
191}
192
193impl Parse for AngleOrPercentage {
194    fn parse<'i, 't>(
195        context: &ParserContext,
196        input: &mut Parser<'i, 't>,
197    ) -> Result<Self, ParseError<'i>> {
198        AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No)
199    }
200}
201
202/// Parse a `<number>` value, with a given clamping mode.
203fn parse_number_with_clamping_mode<'i, 't>(
204    context: &ParserContext,
205    input: &mut Parser<'i, 't>,
206    clamping_mode: AllowedNumericType,
207) -> Result<Number, ParseError<'i>> {
208    let location = input.current_source_location();
209    match *input.next()? {
210        Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => {
211            Ok(Number {
212                value,
213                calc_clamping_mode: None,
214            })
215        },
216        Token::Function(ref name) => {
217            let function = CalcNode::math_function(context, name, location)?;
218            let value = CalcNode::parse_number(context, input, function)?;
219            Ok(Number {
220                value,
221                calc_clamping_mode: Some(clamping_mode),
222            })
223        },
224        ref t => Err(location.new_unexpected_token_error(t.clone())),
225    }
226}
227
228/// A CSS `<number>` specified value.
229///
230/// https://drafts.csswg.org/css-values-3/#number-value
231#[derive(Clone, Copy, Debug, MallocSizeOf, PartialOrd, ToShmem)]
232pub struct Number {
233    /// The numeric value itself.
234    value: CSSFloat,
235    /// If this number came from a calc() expression, this tells how clamping
236    /// should be done on the value.
237    calc_clamping_mode: Option<AllowedNumericType>,
238}
239
240impl Parse for Number {
241    fn parse<'i, 't>(
242        context: &ParserContext,
243        input: &mut Parser<'i, 't>,
244    ) -> Result<Self, ParseError<'i>> {
245        parse_number_with_clamping_mode(context, input, AllowedNumericType::All)
246    }
247}
248
249impl PartialEq<Number> for Number {
250    fn eq(&self, other: &Number) -> bool {
251        if self.calc_clamping_mode != other.calc_clamping_mode {
252            return false;
253        }
254
255        self.value == other.value || (self.value.is_nan() && other.value.is_nan())
256    }
257}
258
259impl Number {
260    /// Returns a new number with the value `val`.
261    #[inline]
262    fn new_with_clamping_mode(
263        value: CSSFloat,
264        calc_clamping_mode: Option<AllowedNumericType>,
265    ) -> Self {
266        Self {
267            value,
268            calc_clamping_mode,
269        }
270    }
271
272    /// Returns this percentage as a number.
273    pub fn to_percentage(&self) -> Percentage {
274        Percentage::new_with_clamping_mode(self.value, self.calc_clamping_mode)
275    }
276
277    /// Returns a new number with the value `val`.
278    #[inline]
279    pub fn new(val: CSSFloat) -> Self {
280        Self::new_with_clamping_mode(val, None)
281    }
282
283    /// Returns whether this number came from a `calc()` expression.
284    #[inline]
285    pub fn was_calc(&self) -> bool {
286        self.calc_clamping_mode.is_some()
287    }
288
289    /// Returns the numeric value, clamped if needed.
290    #[inline]
291    pub fn get(&self) -> f32 {
292        crate::values::normalize(
293            self.calc_clamping_mode
294                .map_or(self.value, |mode| mode.clamp(self.value)),
295        )
296        .min(f32::MAX)
297        .max(f32::MIN)
298    }
299
300    #[allow(missing_docs)]
301    pub fn parse_non_negative<'i, 't>(
302        context: &ParserContext,
303        input: &mut Parser<'i, 't>,
304    ) -> Result<Number, ParseError<'i>> {
305        parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
306    }
307
308    #[allow(missing_docs)]
309    pub fn parse_at_least_one<'i, 't>(
310        context: &ParserContext,
311        input: &mut Parser<'i, 't>,
312    ) -> Result<Number, ParseError<'i>> {
313        parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
314    }
315
316    /// Clamp to 1.0 if the value is over 1.0.
317    #[inline]
318    pub fn clamp_to_one(self) -> Self {
319        Number {
320            value: self.value.min(1.),
321            calc_clamping_mode: self.calc_clamping_mode,
322        }
323    }
324}
325
326impl ToComputedValue for Number {
327    type ComputedValue = CSSFloat;
328
329    #[inline]
330    fn to_computed_value(&self, _: &Context) -> CSSFloat {
331        self.get()
332    }
333
334    #[inline]
335    fn from_computed_value(computed: &CSSFloat) -> Self {
336        Number {
337            value: *computed,
338            calc_clamping_mode: None,
339        }
340    }
341}
342
343impl ToCss for Number {
344    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
345    where
346        W: Write,
347    {
348        serialize_number(self.value, self.calc_clamping_mode.is_some(), dest)
349    }
350}
351
352impl IsParallelTo for (Number, Number, Number) {
353    fn is_parallel_to(&self, vector: &DirectionVector) -> bool {
354        use euclid::approxeq::ApproxEq;
355        // If a and b is parallel, the angle between them is 0deg, so
356        // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0.
357        let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get());
358        self_vector
359            .cross(*vector)
360            .square_length()
361            .approx_eq(&0.0f32)
362    }
363}
364
365impl SpecifiedValueInfo for Number {}
366
367impl Add for Number {
368    type Output = Self;
369
370    fn add(self, other: Self) -> Self {
371        Self::new(self.get() + other.get())
372    }
373}
374
375impl Zero for Number {
376    #[inline]
377    fn zero() -> Self {
378        Self::new(0.)
379    }
380
381    #[inline]
382    fn is_zero(&self) -> bool {
383        self.get() == 0.
384    }
385}
386
387impl From<Number> for f32 {
388    #[inline]
389    fn from(n: Number) -> Self {
390        n.get()
391    }
392}
393
394impl From<Number> for f64 {
395    #[inline]
396    fn from(n: Number) -> Self {
397        n.get() as f64
398    }
399}
400
401/// A Number which is >= 0.0.
402pub type NonNegativeNumber = NonNegative<Number>;
403
404impl Parse for NonNegativeNumber {
405    fn parse<'i, 't>(
406        context: &ParserContext,
407        input: &mut Parser<'i, 't>,
408    ) -> Result<Self, ParseError<'i>> {
409        parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
410            .map(NonNegative::<Number>)
411    }
412}
413
414impl One for NonNegativeNumber {
415    #[inline]
416    fn one() -> Self {
417        NonNegativeNumber::new(1.0)
418    }
419
420    #[inline]
421    fn is_one(&self) -> bool {
422        self.get() == 1.0
423    }
424}
425
426impl NonNegativeNumber {
427    /// Returns a new non-negative number with the value `val`.
428    pub fn new(val: CSSFloat) -> Self {
429        NonNegative::<Number>(Number::new(val.max(0.)))
430    }
431
432    /// Returns the numeric value.
433    #[inline]
434    pub fn get(&self) -> f32 {
435        self.0.get()
436    }
437}
438
439/// An Integer which is >= 0.
440pub type NonNegativeInteger = NonNegative<Integer>;
441
442impl Parse for NonNegativeInteger {
443    fn parse<'i, 't>(
444        context: &ParserContext,
445        input: &mut Parser<'i, 't>,
446    ) -> Result<Self, ParseError<'i>> {
447        Ok(NonNegative(Integer::parse_non_negative(context, input)?))
448    }
449}
450
451/// A Number which is >= 1.0.
452pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>;
453
454impl Parse for GreaterThanOrEqualToOneNumber {
455    fn parse<'i, 't>(
456        context: &ParserContext,
457        input: &mut Parser<'i, 't>,
458    ) -> Result<Self, ParseError<'i>> {
459        parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
460            .map(GreaterThanOrEqualToOne::<Number>)
461    }
462}
463
464/// <number> | <percentage>
465///
466/// Accepts only non-negative numbers.
467#[allow(missing_docs)]
468#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
469pub enum NumberOrPercentage {
470    Percentage(Percentage),
471    Number(Number),
472}
473
474impl NumberOrPercentage {
475    fn parse_with_clamping_mode<'i, 't>(
476        context: &ParserContext,
477        input: &mut Parser<'i, 't>,
478        type_: AllowedNumericType,
479    ) -> Result<Self, ParseError<'i>> {
480        if let Ok(per) =
481            input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_))
482        {
483            return Ok(NumberOrPercentage::Percentage(per));
484        }
485
486        parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number)
487    }
488
489    /// Parse a non-negative number or percentage.
490    pub fn parse_non_negative<'i, 't>(
491        context: &ParserContext,
492        input: &mut Parser<'i, 't>,
493    ) -> Result<Self, ParseError<'i>> {
494        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
495    }
496
497    /// Convert the number or the percentage to a number.
498    pub fn to_percentage(self) -> Percentage {
499        match self {
500            Self::Percentage(p) => p,
501            Self::Number(n) => n.to_percentage(),
502        }
503    }
504
505    /// Convert the number or the percentage to a number.
506    pub fn to_number(self) -> Number {
507        match self {
508            Self::Percentage(p) => p.to_number(),
509            Self::Number(n) => n,
510        }
511    }
512}
513
514impl Parse for NumberOrPercentage {
515    fn parse<'i, 't>(
516        context: &ParserContext,
517        input: &mut Parser<'i, 't>,
518    ) -> Result<Self, ParseError<'i>> {
519        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
520    }
521}
522
523/// A non-negative <number> | <percentage>.
524pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>;
525
526impl NonNegativeNumberOrPercentage {
527    /// Returns the `100%` value.
528    #[inline]
529    pub fn hundred_percent() -> Self {
530        NonNegative(NumberOrPercentage::Percentage(Percentage::hundred()))
531    }
532
533    /// Return a particular number.
534    #[inline]
535    pub fn new_number(n: f32) -> Self {
536        NonNegative(NumberOrPercentage::Number(Number::new(n)))
537    }
538}
539
540impl Parse for NonNegativeNumberOrPercentage {
541    fn parse<'i, 't>(
542        context: &ParserContext,
543        input: &mut Parser<'i, 't>,
544    ) -> Result<Self, ParseError<'i>> {
545        Ok(NonNegative(NumberOrPercentage::parse_non_negative(
546            context, input,
547        )?))
548    }
549}
550
551/// The value of Opacity is <alpha-value>, which is "<number> | <percentage>".
552/// However, we serialize the specified value as number, so it's ok to store
553/// the Opacity as Number.
554#[derive(
555    Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, SpecifiedValueInfo, ToCss, ToShmem,
556)]
557pub struct Opacity(Number);
558
559impl Parse for Opacity {
560    /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
561    /// and then convert into an Number if it's a Percentage.
562    /// https://drafts.csswg.org/cssom/#serializing-css-values
563    fn parse<'i, 't>(
564        context: &ParserContext,
565        input: &mut Parser<'i, 't>,
566    ) -> Result<Self, ParseError<'i>> {
567        let number = NumberOrPercentage::parse(context, input)?.to_number();
568        Ok(Opacity(number))
569    }
570}
571
572impl ToComputedValue for Opacity {
573    type ComputedValue = CSSFloat;
574
575    #[inline]
576    fn to_computed_value(&self, context: &Context) -> CSSFloat {
577        let value = self.0.to_computed_value(context);
578        if context.for_smil_animation {
579            // SMIL expects to be able to interpolate between out-of-range
580            // opacity values.
581            value
582        } else {
583            value.min(1.0).max(0.0)
584        }
585    }
586
587    #[inline]
588    fn from_computed_value(computed: &CSSFloat) -> Self {
589        Opacity(Number::from_computed_value(computed))
590    }
591}
592
593/// A specified `<integer>`, either a simple integer value or a calc expression.
594/// Note that a calc expression may not actually be an integer; it will be rounded
595/// at computed-value time.
596///
597/// <https://drafts.csswg.org/css-values/#integers>
598#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)]
599pub enum Integer {
600    /// A literal integer value.
601    Literal(CSSInteger),
602    /// A calc expression, whose value will be rounded later if necessary.
603    Calc(CSSFloat),
604}
605
606impl Zero for Integer {
607    #[inline]
608    fn zero() -> Self {
609        Self::new(0)
610    }
611
612    #[inline]
613    fn is_zero(&self) -> bool {
614        *self == 0
615    }
616}
617
618impl One for Integer {
619    #[inline]
620    fn one() -> Self {
621        Self::new(1)
622    }
623
624    #[inline]
625    fn is_one(&self) -> bool {
626        *self == 1
627    }
628}
629
630impl PartialEq<i32> for Integer {
631    fn eq(&self, value: &i32) -> bool {
632        self.value() == *value
633    }
634}
635
636impl Integer {
637    /// Trivially constructs a new `Integer` value.
638    pub fn new(val: CSSInteger) -> Self {
639        Self::Literal(val)
640    }
641
642    /// Returns the (rounded) integer value associated with this value.
643    pub fn value(&self) -> CSSInteger {
644        match *self {
645            Self::Literal(i) => i,
646            Self::Calc(n) => (n + 0.5).floor() as CSSInteger,
647        }
648    }
649
650    /// Trivially constructs a new integer value from a `calc()` expression.
651    fn from_calc(val: CSSFloat) -> Self {
652        Self::Calc(val)
653    }
654}
655
656impl Parse for Integer {
657    fn parse<'i, 't>(
658        context: &ParserContext,
659        input: &mut Parser<'i, 't>,
660    ) -> Result<Self, ParseError<'i>> {
661        let location = input.current_source_location();
662        match *input.next()? {
663            Token::Number {
664                int_value: Some(v), ..
665            } => Ok(Integer::new(v)),
666            Token::Function(ref name) => {
667                let function = CalcNode::math_function(context, name, location)?;
668                let result = CalcNode::parse_number(context, input, function)?;
669                Ok(Integer::from_calc(result))
670            },
671            ref t => Err(location.new_unexpected_token_error(t.clone())),
672        }
673    }
674}
675
676impl Integer {
677    /// Parse an integer value which is at least `min`.
678    pub fn parse_with_minimum<'i, 't>(
679        context: &ParserContext,
680        input: &mut Parser<'i, 't>,
681        min: i32,
682    ) -> Result<Integer, ParseError<'i>> {
683        let value = Integer::parse(context, input)?;
684        // FIXME(emilio): The spec asks us to avoid rejecting it at parse
685        // time except until computed value time.
686        //
687        // It's not totally clear it's worth it though, and no other browser
688        // does this.
689        if value.value() < min {
690            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
691        }
692        Ok(value)
693    }
694
695    /// Parse a non-negative integer.
696    pub fn parse_non_negative<'i, 't>(
697        context: &ParserContext,
698        input: &mut Parser<'i, 't>,
699    ) -> Result<Integer, ParseError<'i>> {
700        Integer::parse_with_minimum(context, input, 0)
701    }
702
703    /// Parse a positive integer (>= 1).
704    pub fn parse_positive<'i, 't>(
705        context: &ParserContext,
706        input: &mut Parser<'i, 't>,
707    ) -> Result<Integer, ParseError<'i>> {
708        Integer::parse_with_minimum(context, input, 1)
709    }
710}
711
712impl ToComputedValue for Integer {
713    type ComputedValue = i32;
714
715    #[inline]
716    fn to_computed_value(&self, _: &Context) -> i32 {
717        self.value()
718    }
719
720    #[inline]
721    fn from_computed_value(computed: &i32) -> Self {
722        Integer::new(*computed)
723    }
724}
725
726impl ToCss for Integer {
727    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
728    where
729        W: Write,
730    {
731        match *self {
732            Integer::Literal(i) => i.to_css(dest),
733            Integer::Calc(n) => {
734                dest.write_str("calc(")?;
735                n.to_css(dest)?;
736                dest.write_char(')')
737            }
738        }
739    }
740}
741
742impl SpecifiedValueInfo for Integer {}
743
744/// A wrapper of Integer, with value >= 1.
745pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
746
747impl Parse for PositiveInteger {
748    #[inline]
749    fn parse<'i, 't>(
750        context: &ParserContext,
751        input: &mut Parser<'i, 't>,
752    ) -> Result<Self, ParseError<'i>> {
753        Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne)
754    }
755}
756
757/// The specified value of a grid `<track-breadth>`
758pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
759
760/// The specified value of a grid `<track-size>`
761pub type TrackSize = GenericTrackSize<LengthPercentage>;
762
763/// The specified value of a grid `<track-size>+`
764pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>;
765
766/// The specified value of a grid `<track-list>`
767/// (could also be `<auto-track-list>` or `<explicit-track-list>`)
768pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
769
770/// The specified value of a `<grid-line>`.
771pub type GridLine = GenericGridLine<Integer>;
772
773/// `<grid-template-rows> | <grid-template-columns>`
774pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
775
776/// rect(...)
777pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
778
779impl Parse for ClipRect {
780    fn parse<'i, 't>(
781        context: &ParserContext,
782        input: &mut Parser<'i, 't>,
783    ) -> Result<Self, ParseError<'i>> {
784        Self::parse_quirky(context, input, AllowQuirks::No)
785    }
786}
787
788impl ClipRect {
789    /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks.
790    fn parse_quirky<'i, 't>(
791        context: &ParserContext,
792        input: &mut Parser<'i, 't>,
793        allow_quirks: AllowQuirks,
794    ) -> Result<Self, ParseError<'i>> {
795        input.expect_function_matching("rect")?;
796
797        fn parse_argument<'i, 't>(
798            context: &ParserContext,
799            input: &mut Parser<'i, 't>,
800            allow_quirks: AllowQuirks,
801        ) -> Result<LengthOrAuto, ParseError<'i>> {
802            LengthOrAuto::parse_quirky(context, input, allow_quirks)
803        }
804
805        input.parse_nested_block(|input| {
806            let top = parse_argument(context, input, allow_quirks)?;
807            let right;
808            let bottom;
809            let left;
810
811            if input.try_parse(|input| input.expect_comma()).is_ok() {
812                right = parse_argument(context, input, allow_quirks)?;
813                input.expect_comma()?;
814                bottom = parse_argument(context, input, allow_quirks)?;
815                input.expect_comma()?;
816                left = parse_argument(context, input, allow_quirks)?;
817            } else {
818                right = parse_argument(context, input, allow_quirks)?;
819                bottom = parse_argument(context, input, allow_quirks)?;
820                left = parse_argument(context, input, allow_quirks)?;
821            }
822
823            Ok(ClipRect {
824                top,
825                right,
826                bottom,
827                left,
828            })
829        })
830    }
831}
832
833/// rect(...) | auto
834pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
835
836impl ClipRectOrAuto {
837    /// Parses a ClipRect or Auto, allowing quirks.
838    pub fn parse_quirky<'i, 't>(
839        context: &ParserContext,
840        input: &mut Parser<'i, 't>,
841        allow_quirks: AllowQuirks,
842    ) -> Result<Self, ParseError<'i>> {
843        if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
844            return Ok(generics::GenericClipRectOrAuto::Rect(v));
845        }
846        input.expect_ident_matching("auto")?;
847        Ok(generics::GenericClipRectOrAuto::Auto)
848    }
849}
850
851/// Whether quirks are allowed in this context.
852#[derive(Clone, Copy, PartialEq)]
853pub enum AllowQuirks {
854    /// Quirks are not allowed.
855    No,
856    /// Quirks are allowed, in quirks mode.
857    Yes,
858    /// Quirks are always allowed, used for SVG lengths.
859    Always,
860}
861
862impl AllowQuirks {
863    /// Returns `true` if quirks are allowed in this context.
864    pub fn allowed(self, quirks_mode: QuirksMode) -> bool {
865        match self {
866            AllowQuirks::Always => true,
867            AllowQuirks::No => false,
868            AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks,
869        }
870    }
871}
872
873/// An attr(...) rule
874///
875/// `[namespace? `|`]? ident`
876#[derive(
877    Clone,
878    Debug,
879    Eq,
880    MallocSizeOf,
881    PartialEq,
882    SpecifiedValueInfo,
883    ToComputedValue,
884    ToResolvedValue,
885    ToShmem,
886)]
887#[css(function)]
888#[repr(C)]
889pub struct Attr {
890    /// Optional namespace prefix.
891    pub namespace_prefix: Prefix,
892    /// Optional namespace URL.
893    pub namespace_url: Namespace,
894    /// Attribute name
895    pub attribute: Atom,
896    /// Fallback value
897    pub fallback: AtomString,
898}
899
900impl Parse for Attr {
901    fn parse<'i, 't>(
902        context: &ParserContext,
903        input: &mut Parser<'i, 't>,
904    ) -> Result<Attr, ParseError<'i>> {
905        input.expect_function_matching("attr")?;
906        input.parse_nested_block(|i| Attr::parse_function(context, i))
907    }
908}
909
910/// Get the Namespace for a given prefix from the namespace map.
911fn get_namespace_for_prefix(prefix: &Prefix, context: &ParserContext) -> Option<Namespace> {
912    context.namespaces.prefixes.get(prefix).cloned()
913}
914
915/// Try to parse a namespace and return it if parsed, or none if there was not one present
916fn parse_namespace<'i, 't>(
917    context: &ParserContext,
918    input: &mut Parser<'i, 't>,
919) -> Result<(Prefix, Namespace), ParseError<'i>> {
920    let ns_prefix = match input.next()? {
921        Token::Ident(ref prefix) => Some(Prefix::from(prefix.as_ref())),
922        Token::Delim('|') => None,
923        _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
924    };
925
926    if ns_prefix.is_some() && !matches!(*input.next_including_whitespace()?, Token::Delim('|')) {
927        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
928    }
929
930    if let Some(prefix) = ns_prefix {
931        let ns = match get_namespace_for_prefix(&prefix, context) {
932            Some(ns) => ns,
933            None => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
934        };
935        Ok((prefix, ns))
936    } else {
937        Ok((Prefix::default(), Namespace::default()))
938    }
939}
940
941impl Attr {
942    /// Parse contents of attr() assuming we have already parsed `attr` and are
943    /// within a parse_nested_block()
944    pub fn parse_function<'i, 't>(
945        context: &ParserContext,
946        input: &mut Parser<'i, 't>,
947    ) -> Result<Attr, ParseError<'i>> {
948        // Syntax is `[namespace? '|']? ident [',' fallback]?`
949        let namespace = input
950            .try_parse(|input| parse_namespace(context, input))
951            .ok();
952        let namespace_is_some = namespace.is_some();
953        let (namespace_prefix, namespace_url) = namespace.unwrap_or_default();
954
955        // If there is a namespace, ensure no whitespace following '|'
956        let attribute = Atom::from(if namespace_is_some {
957            let location = input.current_source_location();
958            match *input.next_including_whitespace()? {
959                Token::Ident(ref ident) => ident.as_ref(),
960                ref t => return Err(location.new_unexpected_token_error(t.clone())),
961            }
962        } else {
963            input.expect_ident()?.as_ref()
964        });
965
966        // Fallback will always be a string value for now as we do not support
967        // attr() types yet.
968        let fallback = input
969            .try_parse(|input| -> Result<AtomString, ParseError<'i>> {
970                input.expect_comma()?;
971                Ok(input.expect_string()?.as_ref().into())
972            })
973            .unwrap_or_default();
974
975        Ok(Attr {
976            namespace_prefix,
977            namespace_url,
978            attribute,
979            fallback,
980        })
981    }
982}
983
984impl ToCss for Attr {
985    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
986    where
987        W: Write,
988    {
989        dest.write_str("attr(")?;
990        if !self.namespace_prefix.is_empty() {
991            serialize_atom_identifier(&self.namespace_prefix, dest)?;
992            dest.write_char('|')?;
993        }
994        serialize_atom_identifier(&self.attribute, dest)?;
995
996        if !self.fallback.is_empty() {
997            dest.write_str(", ")?;
998            self.fallback.to_css(dest)?;
999        }
1000
1001        dest.write_char(')')
1002    }
1003}