Skip to main content

style/values/specified/
image.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//! CSS handling for the specified value of
6//! [`image`][image]s
7//!
8//! [image]: https://drafts.csswg.org/css-images/#image-values
9
10use crate::Atom;
11use crate::color::mix::ColorInterpolationMethod;
12use crate::derives::*;
13use crate::parser::{Parse, ParserContext};
14use crate::stylesheets::CorsMode;
15use crate::typed_om::{ImageValue, KeywordValue, ToTyped, TypedValue};
16use crate::values::generics::NonNegative;
17use crate::values::generics::color::{ColorMixFlags, GenericLightDark};
18use crate::values::generics::image::{
19    self as generic, Circle, Ellipse, GradientCompatMode, ShapeExtent,
20};
21use crate::values::generics::image::{GradientFlags, PaintWorklet};
22use crate::values::generics::position::Position as GenericPosition;
23use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword};
24use crate::values::specified::position::{Position, PositionComponent, Side};
25use crate::values::specified::url::SpecifiedUrl;
26use crate::values::specified::{
27    Angle, AngleOrPercentage, Color, Length, LengthPercentage, NonNegativeLength,
28    NonNegativeLengthPercentage, Resolution,
29};
30use crate::values::specified::{Number, NumberOrPercentage, Percentage};
31use cssparser::{Delimiter, Parser, Token, match_ignore_ascii_case};
32use selectors::parser::SelectorParseErrorKind;
33use std::cmp::Ordering;
34use std::fmt::{self, Write};
35use style_traits::{CssString, CssType, CssWriter, KeywordsCollectFn, ParseError};
36use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
37use thin_vec::ThinVec;
38
39/// Specified values for an image according to CSS-IMAGES.
40/// <https://drafts.csswg.org/css-images/#image-values>
41pub type Image = generic::Image<Gradient, SpecifiedUrl, Color, Percentage, Resolution>;
42
43impl ToTyped for Image {
44    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
45        match *self {
46            Image::None => {
47                dest.push(TypedValue::Keyword(KeywordValue(CssString::from("none"))));
48                Ok(())
49            },
50            Image::Url(ref url) => {
51                dest.push(TypedValue::Image(ImageValue::Specified(url.clone())));
52                Ok(())
53            },
54            _ => Err(()),
55        }
56    }
57}
58
59// Images should remain small, see https://github.com/servo/servo/pull/18430
60size_of_test!(Image, 16);
61
62/// Specified values for a CSS gradient.
63/// <https://drafts.csswg.org/css-images/#gradients>
64pub type Gradient = generic::Gradient<
65    LineDirection,
66    Length,
67    LengthPercentage,
68    Position,
69    Angle,
70    AngleOrPercentage,
71    Color,
72>;
73
74/// Specified values for CSS cross-fade
75/// cross-fade( CrossFadeElement, ...)
76/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
77pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
78/// CrossFadeElement = percent? CrossFadeImage
79pub type CrossFadeElement = generic::CrossFadeElement<Image, Color, Percentage>;
80/// CrossFadeImage = image | color
81pub type CrossFadeImage = generic::CrossFadeImage<Image, Color>;
82
83/// `image-set()`
84pub type ImageSet = generic::ImageSet<Image, Resolution>;
85
86/// Each of the arguments to `image-set()`
87pub type ImageSetItem = generic::ImageSetItem<Image, Resolution>;
88
89type LengthPercentageItemList = crate::OwnedSlice<generic::GradientItem<Color, LengthPercentage>>;
90
91impl Color {
92    fn has_modern_syntax(&self) -> bool {
93        match self {
94            Self::Absolute(absolute) => !absolute.color.is_legacy_syntax(),
95            Self::ColorMix(mix) => {
96                if mix.flags.contains(ColorMixFlags::RESULT_IN_MODERN_SYNTAX) {
97                    true
98                } else {
99                    mix.items.iter().any(|item| item.color.has_modern_syntax())
100                }
101            },
102            Self::LightDark(ld) => ld.light.has_modern_syntax() || ld.dark.has_modern_syntax(),
103
104            // The default is that this color doesn't have any modern syntax.
105            _ => false,
106        }
107    }
108}
109
110fn default_color_interpolation_method<T>(
111    items: &[generic::GradientItem<Color, T>],
112) -> ColorInterpolationMethod {
113    let has_modern_syntax_item = items.iter().any(|item| match item {
114        generic::GenericGradientItem::SimpleColorStop(color) => color.has_modern_syntax(),
115        generic::GenericGradientItem::ComplexColorStop { color, .. } => color.has_modern_syntax(),
116        generic::GenericGradientItem::InterpolationHint(_) => false,
117    });
118
119    if has_modern_syntax_item {
120        ColorInterpolationMethod::default()
121    } else {
122        ColorInterpolationMethod::srgb()
123    }
124}
125
126fn cross_fade_enabled() -> bool {
127    crate::pref!("layout.css.cross-fade.enabled")
128}
129
130impl SpecifiedValueInfo for Gradient {
131    const SUPPORTED_TYPES: u8 = CssType::GRADIENT;
132
133    fn collect_completion_keywords(f: KeywordsCollectFn) {
134        // This list here should keep sync with that in Gradient::parse.
135        f(&[
136            "linear-gradient",
137            "-webkit-linear-gradient",
138            "-moz-linear-gradient",
139            "repeating-linear-gradient",
140            "-webkit-repeating-linear-gradient",
141            "-moz-repeating-linear-gradient",
142            "radial-gradient",
143            "-webkit-radial-gradient",
144            "-moz-radial-gradient",
145            "repeating-radial-gradient",
146            "-webkit-repeating-radial-gradient",
147            "-moz-repeating-radial-gradient",
148            "-webkit-gradient",
149            "conic-gradient",
150            "repeating-conic-gradient",
151        ]);
152    }
153}
154
155// Need to manually implement as whether or not cross-fade shows up in
156// completions & etc is dependent on it being enabled.
157impl<Image, Color, Percentage> SpecifiedValueInfo for generic::CrossFade<Image, Color, Percentage> {
158    const SUPPORTED_TYPES: u8 = 0;
159
160    fn collect_completion_keywords(f: KeywordsCollectFn) {
161        if cross_fade_enabled() {
162            f(&["cross-fade"]);
163        }
164    }
165}
166
167impl<Image, Resolution> SpecifiedValueInfo for generic::ImageSet<Image, Resolution> {
168    const SUPPORTED_TYPES: u8 = 0;
169
170    fn collect_completion_keywords(f: KeywordsCollectFn) {
171        f(&["image-set"]);
172    }
173}
174
175/// A specified gradient line direction.
176///
177/// FIXME(emilio): This should be generic over Angle.
178#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
179pub enum LineDirection {
180    /// An angular direction.
181    Angle(Angle),
182    /// A horizontal direction.
183    Horizontal(HorizontalPositionKeyword),
184    /// A vertical direction.
185    Vertical(VerticalPositionKeyword),
186    /// A direction towards a corner of a box.
187    Corner(HorizontalPositionKeyword, VerticalPositionKeyword),
188}
189
190/// A specified ending shape.
191pub type EndingShape = generic::EndingShape<NonNegativeLength, NonNegativeLengthPercentage>;
192
193bitflags! {
194    #[derive(Clone, Copy)]
195    struct ParseImageFlags: u8 {
196        const FORBID_NONE = 1 << 0;
197        const FORBID_IMAGE_SET = 1 << 1;
198        const FORBID_NON_URL = 1 << 2;
199    }
200}
201
202impl Parse for Image {
203    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Image, ParseError> {
204        Image::parse_with_cors_mode(context, input, CorsMode::None, ParseImageFlags::empty())
205    }
206}
207
208impl Image {
209    fn parse_with_cors_mode(
210        context: &ParserContext,
211        input: &mut Parser,
212        cors_mode: CorsMode,
213        flags: ParseImageFlags,
214    ) -> Result<Image, ParseError> {
215        if !flags.contains(ParseImageFlags::FORBID_NONE)
216            && input.try_parse(|i| i.expect_ident_matching("none")).is_ok()
217        {
218            return Ok(generic::Image::None);
219        }
220
221        if let Ok(url) =
222            input.try_parse(|input| SpecifiedUrl::parse_with_cors_mode(context, input, cors_mode))
223        {
224            return Ok(generic::Image::Url(url));
225        }
226
227        if !flags.contains(ParseImageFlags::FORBID_IMAGE_SET)
228            && let Ok(is) =
229                input.try_parse(|input| ImageSet::parse(context, input, cors_mode, flags))
230        {
231            return Ok(generic::Image::ImageSet(Box::new(is)));
232        }
233
234        if flags.contains(ParseImageFlags::FORBID_NON_URL) {
235            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
236        }
237
238        if let Ok(gradient) = input.try_parse(|i| Gradient::parse(context, i)) {
239            return Ok(generic::Image::Gradient(Box::new(gradient)));
240        }
241
242        let function = input.expect_function()?.clone();
243        input.parse_nested_block(|input| Ok(match_ignore_ascii_case! { &function,
244            #[cfg(feature = "servo")]
245            "paint" => Self::PaintWorklet(Box::new(<PaintWorklet>::parse_args(context, input)?)),
246            "cross-fade" if cross_fade_enabled() => Self::CrossFade(Box::new(CrossFade::parse_args(context, input, cors_mode, flags)?)),
247            "image" => Self::Image(Box::new(Color::parse(context, input)?)),
248            "light-dark" if crate::pref!("layout.css.light-dark.images.enabled", gecko = true) => {
249                Self::LightDark(Box::new(GenericLightDark::parse_args_with(input, |input| {
250                    // `none` in `light-dark()` has a special meaning.
251                    Self::parse_with_cors_mode(context, input, cors_mode, flags & !ParseImageFlags::FORBID_NONE)
252                })?))
253            },
254            #[cfg(feature = "gecko")]
255            "-moz-element" => Self::Element(Self::parse_element(input)?),
256            #[cfg(feature = "gecko")]
257            "-moz-symbolic-icon" if context.chrome_rules_enabled() => Self::MozSymbolicIcon(input.expect_ident()?.as_ref().into()),
258            _ => return Err(ParseError::custom(StyleParseErrorKind::UnexpectedFunction)),
259        }))
260    }
261}
262
263impl Image {
264    /// Creates an already specified image value from an already resolved URL
265    /// for insertion in the cascade.
266    #[cfg(feature = "servo")]
267    pub fn for_cascade(url: ::servo_arc::Arc<::url::Url>) -> Self {
268        use crate::values::CssUrl;
269        generic::Image::Url(CssUrl::for_cascade(url))
270    }
271
272    /// Parses a `-moz-element(# <element-id>)`.
273    #[cfg(feature = "gecko")]
274    fn parse_element(input: &mut Parser) -> Result<Atom, ParseError> {
275        Ok(match *input.next()? {
276            Token::IDHash(ref id) => Atom::from(id.as_ref()),
277            _ => return Err(ParseError::unexpected_token()),
278        })
279    }
280
281    /// Provides an alternate method for parsing that associates the URL with
282    /// anonymous CORS headers.
283    pub fn parse_with_cors_anonymous(
284        context: &ParserContext,
285        input: &mut Parser,
286    ) -> Result<Image, ParseError> {
287        Self::parse_with_cors_mode(
288            context,
289            input,
290            CorsMode::Anonymous,
291            ParseImageFlags::empty(),
292        )
293    }
294
295    /// Provides an alternate method for parsing, but forbidding `none`
296    pub fn parse_forbid_none(
297        context: &ParserContext,
298        input: &mut Parser,
299    ) -> Result<Image, ParseError> {
300        Self::parse_with_cors_mode(context, input, CorsMode::None, ParseImageFlags::FORBID_NONE)
301    }
302
303    /// Provides an alternate method for parsing, but only for urls.
304    pub fn parse_only_url(
305        context: &ParserContext,
306        input: &mut Parser,
307    ) -> Result<Image, ParseError> {
308        Self::parse_with_cors_mode(
309            context,
310            input,
311            CorsMode::None,
312            ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_NON_URL,
313        )
314    }
315}
316
317impl CrossFade {
318    /// cross-fade() = cross-fade( <cf-image># )
319    fn parse_args(
320        context: &ParserContext,
321        input: &mut Parser,
322        cors_mode: CorsMode,
323        flags: ParseImageFlags,
324    ) -> Result<Self, ParseError> {
325        let elements = crate::OwnedSlice::from(input.parse_comma_separated(|input| {
326            CrossFadeElement::parse(context, input, cors_mode, flags)
327        })?);
328        Ok(Self { elements })
329    }
330}
331
332impl CrossFadeElement {
333    fn parse_percentage(context: &ParserContext, input: &mut Parser) -> Option<Percentage> {
334        // We clamp our values here as this is the way that Safari and Chrome's
335        // implementation handle out-of-bounds percentages but whether or not
336        // this behavior follows the specification is still being discussed.
337        // See: <https://github.com/w3c/csswg-drafts/issues/5333>
338        let mut p = input
339            .try_parse(|input| Percentage::parse_non_negative(context, input))
340            .ok()?;
341        p.clamp_to_hundred();
342        Some(p)
343    }
344
345    /// <cf-image> = <percentage>? && [ <image> | <color> ]
346    fn parse(
347        context: &ParserContext,
348        input: &mut Parser,
349        cors_mode: CorsMode,
350        flags: ParseImageFlags,
351    ) -> Result<Self, ParseError> {
352        // Try and parse a leading percent sign.
353        let mut percent = Self::parse_percentage(context, input);
354        // Parse the image
355        let image = CrossFadeImage::parse(context, input, cors_mode, flags)?;
356        // Try and parse a trailing percent sign.
357        if percent.is_none() {
358            percent = Self::parse_percentage(context, input);
359        }
360        Ok(Self {
361            percent: percent.into(),
362            image,
363        })
364    }
365}
366
367impl CrossFadeImage {
368    fn parse(
369        context: &ParserContext,
370        input: &mut Parser,
371        cors_mode: CorsMode,
372        flags: ParseImageFlags,
373    ) -> Result<Self, ParseError> {
374        if let Ok(image) = input.try_parse(|input| {
375            Image::parse_with_cors_mode(
376                context,
377                input,
378                cors_mode,
379                flags | ParseImageFlags::FORBID_NONE,
380            )
381        }) {
382            return Ok(Self::Image(image));
383        }
384        Ok(Self::Color(Color::parse(context, input)?))
385    }
386}
387
388impl ImageSet {
389    fn parse(
390        context: &ParserContext,
391        input: &mut Parser,
392        cors_mode: CorsMode,
393        flags: ParseImageFlags,
394    ) -> Result<Self, ParseError> {
395        let function = input.expect_function()?;
396        match_ignore_ascii_case! { &function,
397            "-webkit-image-set" | "image-set" => {},
398            _ => {
399                return Err(ParseError::custom(StyleParseErrorKind::UnexpectedFunction));
400            }
401        }
402        let items = input.parse_nested_block(|input| {
403            input.parse_comma_separated(|input| {
404                ImageSetItem::parse(context, input, cors_mode, flags)
405            })
406        })?;
407        Ok(Self {
408            selected_index: usize::MAX,
409            items: items.into(),
410        })
411    }
412}
413
414impl ImageSetItem {
415    fn parse_type(p: &mut Parser) -> Result<crate::OwnedStr, ParseError> {
416        p.expect_function_matching("type")?;
417        p.parse_nested_block(|input| Ok(input.expect_string()?.as_ref().to_owned().into()))
418    }
419
420    fn parse(
421        context: &ParserContext,
422        input: &mut Parser,
423        cors_mode: CorsMode,
424        flags: ParseImageFlags,
425    ) -> Result<Self, ParseError> {
426        let start = input.position().byte_index();
427        let image = match input.try_parse(|i| i.expect_url_or_string()) {
428            Ok(url) => {
429                let end = input.position().byte_index();
430                Image::Url(SpecifiedUrl::parse_from_string(
431                    url.as_ref().into(),
432                    start,
433                    end,
434                    context,
435                    cors_mode,
436                )?)
437            },
438            Err(..) => Image::parse_with_cors_mode(
439                context,
440                input,
441                cors_mode,
442                flags | ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_IMAGE_SET,
443            )?,
444        };
445
446        let mut resolution = input
447            .try_parse(|input| Resolution::parse(context, input))
448            .ok();
449        let mime_type = input.try_parse(Self::parse_type).ok();
450
451        // Try to parse resolution after type().
452        if mime_type.is_some() && resolution.is_none() {
453            resolution = input
454                .try_parse(|input| Resolution::parse(context, input))
455                .ok();
456        }
457
458        let resolution = resolution.unwrap_or_else(|| Resolution::from_x(1.0));
459        let has_mime_type = mime_type.is_some();
460        let mime_type = mime_type.unwrap_or_default();
461
462        Ok(Self {
463            image,
464            resolution,
465            has_mime_type,
466            mime_type,
467        })
468    }
469}
470
471impl Parse for Gradient {
472    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
473        enum Shape {
474            Linear,
475            Radial,
476            Conic,
477        }
478
479        let func = input.expect_function()?;
480        let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &func,
481            "linear-gradient" => {
482                (Shape::Linear, false, GradientCompatMode::Modern)
483            },
484            "-webkit-linear-gradient" => {
485                (Shape::Linear, false, GradientCompatMode::WebKit)
486            },
487            #[cfg(feature = "gecko")]
488            "-moz-linear-gradient" => {
489                (Shape::Linear, false, GradientCompatMode::Moz)
490            },
491            "repeating-linear-gradient" => {
492                (Shape::Linear, true, GradientCompatMode::Modern)
493            },
494            "-webkit-repeating-linear-gradient" => {
495                (Shape::Linear, true, GradientCompatMode::WebKit)
496            },
497            #[cfg(feature = "gecko")]
498            "-moz-repeating-linear-gradient" => {
499                (Shape::Linear, true, GradientCompatMode::Moz)
500            },
501            "radial-gradient" => {
502                (Shape::Radial, false, GradientCompatMode::Modern)
503            },
504            "-webkit-radial-gradient" => {
505                (Shape::Radial, false, GradientCompatMode::WebKit)
506            },
507            #[cfg(feature = "gecko")]
508            "-moz-radial-gradient" => {
509                (Shape::Radial, false, GradientCompatMode::Moz)
510            },
511            "repeating-radial-gradient" => {
512                (Shape::Radial, true, GradientCompatMode::Modern)
513            },
514            "-webkit-repeating-radial-gradient" => {
515                (Shape::Radial, true, GradientCompatMode::WebKit)
516            },
517            #[cfg(feature = "gecko")]
518            "-moz-repeating-radial-gradient" => {
519                (Shape::Radial, true, GradientCompatMode::Moz)
520            },
521            "conic-gradient" => {
522                (Shape::Conic, false, GradientCompatMode::Modern)
523            },
524            "repeating-conic-gradient" => {
525                (Shape::Conic, true, GradientCompatMode::Modern)
526            },
527            "-webkit-gradient" => {
528                return input.parse_nested_block(|i| {
529                    Self::parse_webkit_gradient_argument(context, i)
530                });
531            },
532            _ => {
533                return Err(ParseError::custom(StyleParseErrorKind::UnexpectedFunction));
534            }
535        };
536
537        input.parse_nested_block(|i| {
538            Ok(match shape {
539                Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?,
540                Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?,
541                Shape::Conic => Self::parse_conic(context, i, repeating)?,
542            })
543        })
544    }
545}
546
547impl Gradient {
548    fn parse_webkit_gradient_argument(
549        context: &ParserContext,
550        input: &mut Parser,
551    ) -> Result<Self, ParseError> {
552        use crate::values::specified::position::{
553            HorizontalPositionKeyword as X, VerticalPositionKeyword as Y,
554        };
555        type Point = GenericPosition<Component<X>, Component<Y>>;
556
557        #[derive(Clone, Parse)]
558        enum Component<S> {
559            Center,
560            Number(NumberOrPercentage),
561            Side(S),
562        }
563
564        fn line_direction_from_points(first: Point, second: Point) -> LineDirection {
565            let h_ord = first.horizontal.partial_cmp(&second.horizontal);
566            let v_ord = first.vertical.partial_cmp(&second.vertical);
567            let (h, v) = match (h_ord, v_ord) {
568                (Some(h), Some(v)) => (h, v),
569                _ => return LineDirection::Vertical(Y::Bottom),
570            };
571            match (h, v) {
572                (Ordering::Less, Ordering::Less) => LineDirection::Corner(X::Right, Y::Bottom),
573                (Ordering::Less, Ordering::Equal) => LineDirection::Horizontal(X::Right),
574                (Ordering::Less, Ordering::Greater) => LineDirection::Corner(X::Right, Y::Top),
575                (Ordering::Equal, Ordering::Greater) => LineDirection::Vertical(Y::Top),
576                (Ordering::Equal, Ordering::Equal) | (Ordering::Equal, Ordering::Less) => {
577                    LineDirection::Vertical(Y::Bottom)
578                },
579                (Ordering::Greater, Ordering::Less) => LineDirection::Corner(X::Left, Y::Bottom),
580                (Ordering::Greater, Ordering::Equal) => LineDirection::Horizontal(X::Left),
581                (Ordering::Greater, Ordering::Greater) => LineDirection::Corner(X::Left, Y::Top),
582            }
583        }
584
585        impl Parse for Point {
586            fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
587                input.try_parse(|i| {
588                    let x = Component::parse(context, i)?;
589                    let y = Component::parse(context, i)?;
590
591                    // TODO(Bug 2037751) - Enable calc()-expressions that can only be resolved at
592                    // computed value time (due to relative lengths, sibling-index(), etc.).
593                    if matches!(&x, Component::Number(NumberOrPercentage::Number(n)) if n.resolve().is_none()) ||
594                        matches!(&y, Component::Number(NumberOrPercentage::Number(n)) if n.resolve().is_none())
595                    {
596                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
597                    }
598
599                    Ok(Self::new(x, y))
600                })
601            }
602        }
603
604        impl<S: Side> From<Component<S>> for NumberOrPercentage {
605            fn from(val: Component<S>) -> Self {
606                match val {
607                    Component::Center => NumberOrPercentage::Percentage(Percentage::new(0.5)),
608                    Component::Number(number) => number,
609                    Component::Side(side) => {
610                        let p = if side.is_start() {
611                            Percentage::zero()
612                        } else {
613                            Percentage::hundred()
614                        };
615                        NumberOrPercentage::Percentage(p)
616                    },
617                }
618            }
619        }
620
621        impl<S: Side> From<Component<S>> for PositionComponent<S> {
622            fn from(val: Component<S>) -> Self {
623                match val {
624                    Component::Center => PositionComponent::Center,
625                    Component::Number(NumberOrPercentage::Number(number)) => {
626                        // Unresolvable calc is rejected in Point::parse.
627                        PositionComponent::Length(Length::from_px(number.resolve().unwrap()).into())
628                    },
629                    Component::Number(NumberOrPercentage::Percentage(p)) => {
630                        PositionComponent::Length(p.to_length_percentage())
631                    },
632                    Component::Side(side) => PositionComponent::Side(side, None),
633                }
634            }
635        }
636
637        impl<S: Copy + Side> Component<S> {
638            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
639                match (self.clone().into(), other.clone().into()) {
640                    (
641                        NumberOrPercentage::Percentage(ref a),
642                        NumberOrPercentage::Percentage(ref b),
643                    ) => a.resolve().partial_cmp(&b.resolve()),
644                    (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => {
645                        a.resolve().partial_cmp(&b.resolve())
646                    },
647                    (_, _) => None,
648                }
649            }
650        }
651
652        let ident = input.expect_ident_cloned()?;
653        input.expect_comma()?;
654
655        Ok(match_ignore_ascii_case! { &ident,
656            "linear" => {
657                let first = Point::parse(context, input)?;
658                input.expect_comma()?;
659                let second = Point::parse(context, input)?;
660
661                let direction = line_direction_from_points(first, second);
662                let items = Gradient::parse_webkit_gradient_stops(context, input, false)?;
663
664                generic::Gradient::Linear {
665                    direction,
666                    color_interpolation_method: ColorInterpolationMethod::srgb(),
667                    items,
668                    // Legacy gradients always use srgb as a default.
669                    flags: generic::GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
670                    compat_mode: GradientCompatMode::Modern,
671                }
672            },
673            "radial" => {
674                let first_point = Point::parse(context, input)?;
675                input.expect_comma()?;
676                let first_radius = Number::parse_non_negative(context, input)?;
677                input.expect_comma()?;
678                let second_point = Point::parse(context, input)?;
679                input.expect_comma()?;
680                let second_radius = Number::parse_non_negative(context, input)?;
681
682                // TODO(Bug 2037751) - Enable calc()-expressions that can only be resolved at
683                // computed value time (due to relative lengths, sibling-index(), etc.).
684                if first_radius.resolve().is_none() || second_radius.resolve().is_none() {
685                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
686                }
687
688                let (reverse_stops, point, radius) = if second_radius.resolve() >= first_radius.resolve() {
689                    (false, second_point, second_radius)
690                } else {
691                    (true, first_point, first_radius)
692                };
693
694                // Unresolvable calc is rejected above.
695                let rad = Circle::Radius(NonNegative(Length::from_px(radius.resolve().unwrap())));
696                let shape = generic::EndingShape::Circle(rad);
697                let position = Position::new(point.horizontal.into(), point.vertical.into());
698                let items = Gradient::parse_webkit_gradient_stops(context, input, reverse_stops)?;
699
700                generic::Gradient::Radial {
701                    shape,
702                    position,
703                    color_interpolation_method: ColorInterpolationMethod::srgb(),
704                    items,
705                    // Legacy gradients always use srgb as a default.
706                    flags: generic::GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
707                    compat_mode: GradientCompatMode::Modern,
708                }
709            },
710            _ => {
711                let e = SelectorParseErrorKind::UnexpectedIdent;
712                return Err(ParseError::custom(e));
713            },
714        })
715    }
716
717    fn parse_webkit_gradient_stops(
718        context: &ParserContext,
719        input: &mut Parser,
720        reverse_stops: bool,
721    ) -> Result<LengthPercentageItemList, ParseError> {
722        let mut items = input
723            .try_parse(|i| {
724                i.expect_comma()?;
725                i.parse_comma_separated(|i| {
726                    let function = i.expect_function()?.clone();
727                    let (color, mut p) = i.parse_nested_block(|i| {
728                        let p = match_ignore_ascii_case! { &function,
729                            "color-stop" => {
730                                // TODO(Bug 2037751) - Enable calc()-expressions that can only be resolved at
731                                // computed value time (due to relative lengths, sibling-index(), etc.).
732                                let Some(p) = NumberOrPercentage::parse(context, i)?.to_percentage() else {
733                                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
734                                };
735                                i.expect_comma()?;
736                                p
737                            },
738                            "from" => Percentage::zero(),
739                            "to" => Percentage::hundred(),
740                            _ => {
741                                return Err(ParseError::custom(
742                                    StyleParseErrorKind::UnexpectedFunction
743                                ))
744                            },
745                        };
746                        let color = Color::parse(context, i)?;
747                        if color == Color::CurrentColor {
748                            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
749                        }
750                        Ok((color, p))
751                    })?;
752                    if reverse_stops {
753                        p.reverse();
754                    }
755                    Ok(generic::GradientItem::ComplexColorStop {
756                        color,
757                        position: p.to_length_percentage(),
758                    })
759                })
760            })
761            .unwrap_or_default();
762
763        if items.is_empty() {
764            items = vec![
765                generic::GradientItem::ComplexColorStop {
766                    color: Color::transparent(),
767                    position: LengthPercentage::zero_percent(),
768                },
769                generic::GradientItem::ComplexColorStop {
770                    color: Color::transparent(),
771                    position: LengthPercentage::hundred_percent(),
772                },
773            ];
774        } else if items.len() == 1 {
775            let first = items[0].clone();
776            items.push(first);
777        } else {
778            items.sort_by(|a, b| {
779                if let (
780                    generic::GradientItem::ComplexColorStop {
781                        position: a_position,
782                        ..
783                    },
784                    generic::GradientItem::ComplexColorStop {
785                        position: b_position,
786                        ..
787                    },
788                ) = (a, b)
789                    && let (&LengthPercentage::Percentage(a), &LengthPercentage::Percentage(b)) =
790                        (a_position, b_position)
791                {
792                    return a.get().partial_cmp(&b.get()).unwrap_or(Ordering::Equal);
793                }
794                if reverse_stops {
795                    Ordering::Greater
796                } else {
797                    Ordering::Less
798                }
799            })
800        }
801        Ok(items.into())
802    }
803
804    /// Not used for -webkit-gradient syntax and conic-gradient
805    fn parse_stops(
806        context: &ParserContext,
807        input: &mut Parser,
808    ) -> Result<LengthPercentageItemList, ParseError> {
809        let items =
810            generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?;
811        if items.is_empty() {
812            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
813        }
814        Ok(items)
815    }
816
817    /// Parses a linear gradient.
818    /// GradientCompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword.
819    fn parse_linear(
820        context: &ParserContext,
821        input: &mut Parser,
822        repeating: bool,
823        mut compat_mode: GradientCompatMode,
824    ) -> Result<Self, ParseError> {
825        let mut flags = GradientFlags::empty();
826        flags.set(GradientFlags::REPEATING, repeating);
827
828        let mut color_interpolation_method = input
829            .try_parse(|i| ColorInterpolationMethod::parse(context, i))
830            .ok();
831
832        let direction = input
833            .try_parse(|p| LineDirection::parse(context, p, &mut compat_mode))
834            .ok();
835
836        if direction.is_some() && color_interpolation_method.is_none() {
837            color_interpolation_method = input
838                .try_parse(|i| ColorInterpolationMethod::parse(context, i))
839                .ok();
840        }
841
842        // If either of the 2 options were specified, we require a comma.
843        if color_interpolation_method.is_some() || direction.is_some() {
844            input.expect_comma()?;
845        }
846
847        let items = Gradient::parse_stops(context, input)?;
848
849        let default = default_color_interpolation_method(&items);
850        let color_interpolation_method = color_interpolation_method.unwrap_or(default);
851        flags.set(
852            GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
853            default == color_interpolation_method,
854        );
855
856        let direction = direction.unwrap_or(match compat_mode {
857            GradientCompatMode::Modern => LineDirection::Vertical(VerticalPositionKeyword::Bottom),
858            _ => LineDirection::Vertical(VerticalPositionKeyword::Top),
859        });
860
861        Ok(Gradient::Linear {
862            direction,
863            color_interpolation_method,
864            items,
865            flags,
866            compat_mode,
867        })
868    }
869
870    /// Parses a radial gradient.
871    fn parse_radial(
872        context: &ParserContext,
873        input: &mut Parser,
874        repeating: bool,
875        compat_mode: GradientCompatMode,
876    ) -> Result<Self, ParseError> {
877        let mut flags = GradientFlags::empty();
878        flags.set(GradientFlags::REPEATING, repeating);
879
880        let mut color_interpolation_method = input
881            .try_parse(|i| ColorInterpolationMethod::parse(context, i))
882            .ok();
883
884        let (shape, position) = match compat_mode {
885            GradientCompatMode::Modern => {
886                let shape = input.try_parse(|i| EndingShape::parse(context, i, compat_mode));
887                let position = input.try_parse(|i| {
888                    i.expect_ident_matching("at")?;
889                    Position::parse(context, i)
890                });
891                (shape, position.ok())
892            },
893            _ => {
894                let position = input.try_parse(|i| Position::parse(context, i));
895                let shape = input.try_parse(|i| {
896                    if position.is_ok() {
897                        i.expect_comma()?;
898                    }
899                    EndingShape::parse(context, i, compat_mode)
900                });
901                (shape, position.ok())
902            },
903        };
904
905        let has_shape_or_position = shape.is_ok() || position.is_some();
906        if has_shape_or_position && color_interpolation_method.is_none() {
907            color_interpolation_method = input
908                .try_parse(|i| ColorInterpolationMethod::parse(context, i))
909                .ok();
910        }
911
912        if has_shape_or_position || color_interpolation_method.is_some() {
913            input.expect_comma()?;
914        }
915
916        let shape = shape.unwrap_or({
917            generic::EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
918        });
919
920        let position = position.unwrap_or(Position::center());
921
922        let items = Gradient::parse_stops(context, input)?;
923
924        let default = default_color_interpolation_method(&items);
925        let color_interpolation_method = color_interpolation_method.unwrap_or(default);
926        flags.set(
927            GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
928            default == color_interpolation_method,
929        );
930
931        Ok(Gradient::Radial {
932            shape,
933            position,
934            color_interpolation_method,
935            items,
936            flags,
937            compat_mode,
938        })
939    }
940
941    /// Parse a conic gradient.
942    fn parse_conic(
943        context: &ParserContext,
944        input: &mut Parser,
945        repeating: bool,
946    ) -> Result<Self, ParseError> {
947        let mut flags = GradientFlags::empty();
948        flags.set(GradientFlags::REPEATING, repeating);
949
950        let mut color_interpolation_method = input
951            .try_parse(|i| ColorInterpolationMethod::parse(context, i))
952            .ok();
953
954        let angle = input.try_parse(|i| {
955            i.expect_ident_matching("from")?;
956            // Spec allows unitless zero start angles
957            // https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle
958            Angle::parse_with_unitless(context, i)
959        });
960        let position = input.try_parse(|i| {
961            i.expect_ident_matching("at")?;
962            Position::parse(context, i)
963        });
964
965        let has_angle_or_position = angle.is_ok() || position.is_ok();
966        if has_angle_or_position && color_interpolation_method.is_none() {
967            color_interpolation_method = input
968                .try_parse(|i| ColorInterpolationMethod::parse(context, i))
969                .ok();
970        }
971
972        if has_angle_or_position || color_interpolation_method.is_some() {
973            input.expect_comma()?;
974        }
975
976        let angle = angle.unwrap_or(Angle::zero());
977
978        let position = position.unwrap_or(Position::center());
979
980        let items = generic::GradientItem::parse_comma_separated(
981            context,
982            input,
983            AngleOrPercentage::parse_with_unitless,
984        )?;
985
986        if items.is_empty() {
987            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
988        }
989
990        let default = default_color_interpolation_method(&items);
991        let color_interpolation_method = color_interpolation_method.unwrap_or(default);
992        flags.set(
993            GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
994            default == color_interpolation_method,
995        );
996
997        Ok(Gradient::Conic {
998            angle,
999            position,
1000            color_interpolation_method,
1001            items,
1002            flags,
1003        })
1004    }
1005}
1006
1007impl generic::LineDirection for LineDirection {
1008    fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool {
1009        match *self {
1010            LineDirection::Angle(ref angle) => {
1011                angle.as_no_calc().is_some_and(|a| a.degrees() == 180.0)
1012            },
1013            LineDirection::Vertical(VerticalPositionKeyword::Bottom) => {
1014                compat_mode == GradientCompatMode::Modern
1015            },
1016            LineDirection::Vertical(VerticalPositionKeyword::Top) => {
1017                compat_mode != GradientCompatMode::Modern
1018            },
1019            _ => false,
1020        }
1021    }
1022
1023    fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
1024    where
1025        W: Write,
1026    {
1027        match *self {
1028            LineDirection::Angle(ref angle) => angle.to_css(dest),
1029            LineDirection::Horizontal(x) => {
1030                if compat_mode == GradientCompatMode::Modern {
1031                    dest.write_str("to ")?;
1032                }
1033                x.to_css(dest)
1034            },
1035            LineDirection::Vertical(y) => {
1036                if compat_mode == GradientCompatMode::Modern {
1037                    dest.write_str("to ")?;
1038                }
1039                y.to_css(dest)
1040            },
1041            LineDirection::Corner(x, y) => {
1042                if compat_mode == GradientCompatMode::Modern {
1043                    dest.write_str("to ")?;
1044                }
1045                x.to_css(dest)?;
1046                dest.write_char(' ')?;
1047                y.to_css(dest)
1048            },
1049        }
1050    }
1051}
1052
1053impl LineDirection {
1054    fn parse(
1055        context: &ParserContext,
1056        input: &mut Parser,
1057        compat_mode: &mut GradientCompatMode,
1058    ) -> Result<Self, ParseError> {
1059        // Gradients allow unitless zero angles as an exception, see:
1060        // https://github.com/w3c/csswg-drafts/issues/1162
1061        if let Ok(angle) = input.try_parse(|i| Angle::parse_with_unitless(context, i)) {
1062            return Ok(LineDirection::Angle(angle));
1063        }
1064
1065        input.try_parse(|i| {
1066            let to_ident = i.try_parse(|i| i.expect_ident_matching("to"));
1067            match *compat_mode {
1068                // `to` keyword is mandatory in modern syntax.
1069                GradientCompatMode::Modern => to_ident?,
1070                // Fall back to Modern compatibility mode in case there is a `to` keyword.
1071                // According to Gecko, `-moz-linear-gradient(to ...)` should serialize like
1072                // `linear-gradient(to ...)`.
1073                GradientCompatMode::Moz if to_ident.is_ok() => {
1074                    *compat_mode = GradientCompatMode::Modern
1075                },
1076                // There is no `to` keyword in webkit prefixed syntax. If it's consumed,
1077                // parsing should throw an error.
1078                GradientCompatMode::WebKit if to_ident.is_ok() => {
1079                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
1080                },
1081                _ => {},
1082            }
1083
1084            if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) {
1085                if let Ok(y) = i.try_parse(VerticalPositionKeyword::parse) {
1086                    return Ok(LineDirection::Corner(x, y));
1087                }
1088                return Ok(LineDirection::Horizontal(x));
1089            }
1090            let y = VerticalPositionKeyword::parse(i)?;
1091            if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) {
1092                return Ok(LineDirection::Corner(x, y));
1093            }
1094            Ok(LineDirection::Vertical(y))
1095        })
1096    }
1097}
1098
1099impl EndingShape {
1100    fn parse(
1101        context: &ParserContext,
1102        input: &mut Parser,
1103        compat_mode: GradientCompatMode,
1104    ) -> Result<Self, ParseError> {
1105        if let Ok(extent) = input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode))
1106        {
1107            if input
1108                .try_parse(|i| i.expect_ident_matching("circle"))
1109                .is_ok()
1110            {
1111                return Ok(generic::EndingShape::Circle(Circle::Extent(extent)));
1112            }
1113            let _ = input.try_parse(|i| i.expect_ident_matching("ellipse"));
1114            return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent)));
1115        }
1116        if input
1117            .try_parse(|i| i.expect_ident_matching("circle"))
1118            .is_ok()
1119        {
1120            if let Ok(extent) =
1121                input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode))
1122            {
1123                return Ok(generic::EndingShape::Circle(Circle::Extent(extent)));
1124            }
1125            if compat_mode == GradientCompatMode::Modern
1126                && let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i))
1127            {
1128                return Ok(generic::EndingShape::Circle(Circle::Radius(length)));
1129            }
1130            return Ok(generic::EndingShape::Circle(Circle::Extent(
1131                ShapeExtent::FarthestCorner,
1132            )));
1133        }
1134        if input
1135            .try_parse(|i| i.expect_ident_matching("ellipse"))
1136            .is_ok()
1137        {
1138            if let Ok(extent) =
1139                input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode))
1140            {
1141                return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent)));
1142            }
1143            if compat_mode == GradientCompatMode::Modern {
1144                let pair: Result<_, ParseError> = input.try_parse(|i| {
1145                    let x = NonNegativeLengthPercentage::parse(context, i)?;
1146                    let y = NonNegativeLengthPercentage::parse(context, i)?;
1147                    Ok((x, y))
1148                });
1149                if let Ok((x, y)) = pair {
1150                    return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(x, y)));
1151                }
1152            }
1153            return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(
1154                ShapeExtent::FarthestCorner,
1155            )));
1156        }
1157        if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) {
1158            if let Ok(y) = input.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) {
1159                if compat_mode == GradientCompatMode::Modern {
1160                    let _ = input.try_parse(|i| i.expect_ident_matching("ellipse"));
1161                }
1162                return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(
1163                    NonNegative(LengthPercentage::from(length.0)),
1164                    y,
1165                )));
1166            }
1167            if compat_mode == GradientCompatMode::Modern {
1168                let y = input.try_parse(|i| {
1169                    i.expect_ident_matching("ellipse")?;
1170                    NonNegativeLengthPercentage::parse(context, i)
1171                });
1172                if let Ok(y) = y {
1173                    return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(
1174                        NonNegative(LengthPercentage::from(length.0)),
1175                        y,
1176                    )));
1177                }
1178                let _ = input.try_parse(|i| i.expect_ident_matching("circle"));
1179            }
1180
1181            return Ok(generic::EndingShape::Circle(Circle::Radius(length)));
1182        }
1183        input.try_parse(|i| {
1184            let x = Percentage::parse_non_negative(context, i)?;
1185            let y = match i.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) {
1186                Ok(y) => {
1187                    if compat_mode == GradientCompatMode::Modern {
1188                        let _ = i.try_parse(|i| i.expect_ident_matching("ellipse"));
1189                    }
1190                    y
1191                },
1192                _ => {
1193                    if compat_mode == GradientCompatMode::Modern {
1194                        i.expect_ident_matching("ellipse")?;
1195                    }
1196                    NonNegativeLengthPercentage::parse(context, i)?
1197                },
1198            };
1199            Ok(generic::EndingShape::Ellipse(Ellipse::Radii(
1200                NonNegative(x.to_length_percentage()),
1201                y,
1202            )))
1203        })
1204    }
1205}
1206
1207impl ShapeExtent {
1208    fn parse_with_compat_mode(
1209        input: &mut Parser,
1210        compat_mode: GradientCompatMode,
1211    ) -> Result<Self, ParseError> {
1212        match Self::parse(input)? {
1213            ShapeExtent::Contain | ShapeExtent::Cover
1214                if compat_mode == GradientCompatMode::Modern =>
1215            {
1216                Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1217            },
1218            ShapeExtent::Contain => Ok(ShapeExtent::ClosestSide),
1219            ShapeExtent::Cover => Ok(ShapeExtent::FarthestCorner),
1220            keyword => Ok(keyword),
1221        }
1222    }
1223}
1224
1225impl<T> generic::GradientItem<Color, T> {
1226    fn parse_comma_separated(
1227        context: &ParserContext,
1228        input: &mut Parser,
1229        parse_position: impl Fn(&ParserContext, &mut Parser) -> Result<T, ParseError> + Copy,
1230    ) -> Result<crate::OwnedSlice<Self>, ParseError> {
1231        let mut items = Vec::new();
1232        let mut seen_stop = false;
1233
1234        loop {
1235            input.parse_until_before(Delimiter::Comma, |input| {
1236                if seen_stop && let Ok(hint) = input.try_parse(|i| parse_position(context, i)) {
1237                    seen_stop = false;
1238                    items.push(generic::GradientItem::InterpolationHint(hint));
1239                    return Ok(());
1240                }
1241
1242                let stop = generic::ColorStop::parse(context, input, parse_position)?;
1243
1244                match input.try_parse(|i| parse_position(context, i)) {
1245                    Ok(multi_position) => {
1246                        let stop_color = stop.color.clone();
1247                        items.push(stop.into_item());
1248                        items.push(
1249                            generic::ColorStop {
1250                                color: stop_color,
1251                                position: Some(multi_position),
1252                            }
1253                            .into_item(),
1254                        );
1255                    },
1256                    _ => {
1257                        items.push(stop.into_item());
1258                    },
1259                }
1260
1261                seen_stop = true;
1262                Ok(())
1263            })?;
1264
1265            match input.next() {
1266                Err(_) => break,
1267                Ok(&Token::Comma) => continue,
1268                Ok(_) => unreachable!(),
1269            }
1270        }
1271
1272        if !seen_stop || items.is_empty() {
1273            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1274        }
1275        Ok(items.into())
1276    }
1277}
1278
1279impl<T> generic::ColorStop<Color, T> {
1280    fn parse(
1281        context: &ParserContext,
1282        input: &mut Parser,
1283        parse_position: impl Fn(&ParserContext, &mut Parser) -> Result<T, ParseError>,
1284    ) -> Result<Self, ParseError> {
1285        Ok(generic::ColorStop {
1286            color: Color::parse(context, input)?,
1287            position: input.try_parse(|i| parse_position(context, i)).ok(),
1288        })
1289    }
1290}
1291
1292impl PaintWorklet {
1293    #[cfg(feature = "servo")]
1294    fn parse_args(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1295        use crate::custom_properties::SpecifiedValue;
1296        use servo_arc::Arc;
1297        let name = Atom::from(&**input.expect_ident()?);
1298        let arguments = input
1299            .try_parse(|input| {
1300                input.expect_comma()?;
1301                input.parse_comma_separated(|input| {
1302                    SpecifiedValue::parse(
1303                        input,
1304                        Some(&context.namespaces.prefixes),
1305                        &context.url_data,
1306                    )
1307                    .map(Arc::new)
1308                })
1309            })
1310            .unwrap_or_default();
1311        Ok(Self { name, arguments })
1312    }
1313}
1314
1315/// https://drafts.csswg.org/css-images/#propdef-image-rendering
1316#[allow(missing_docs)]
1317#[derive(
1318    Clone,
1319    Copy,
1320    Debug,
1321    Eq,
1322    Hash,
1323    MallocSizeOf,
1324    Parse,
1325    PartialEq,
1326    SpecifiedValueInfo,
1327    ToCss,
1328    ToComputedValue,
1329    ToResolvedValue,
1330    ToShmem,
1331    ToTyped,
1332)]
1333#[repr(u8)]
1334pub enum ImageRendering {
1335    Auto,
1336    #[cfg(feature = "gecko")]
1337    Smooth,
1338    #[parse(aliases = "-moz-crisp-edges")]
1339    CrispEdges,
1340    Pixelated,
1341    // From the spec:
1342    //
1343    //     This property previously accepted the values optimizeSpeed and
1344    //     optimizeQuality. These are now deprecated; a user agent must accept
1345    //     them as valid values but must treat them as having the same behavior
1346    //     as crisp-edges and smooth respectively, and authors must not use
1347    //     them.
1348    //
1349    #[cfg(feature = "gecko")]
1350    Optimizespeed,
1351    #[cfg(feature = "gecko")]
1352    Optimizequality,
1353}
1354
1355/// Internal -moz-image-decoding property. This allows images to be forcefully sync-decoded.
1356///
1357/// We use different defaults for sync vs. async decoding, which sometimes cause site issues.
1358#[derive(
1359    Clone,
1360    Copy,
1361    Debug,
1362    Eq,
1363    FromPrimitive,
1364    MallocSizeOf,
1365    Parse,
1366    PartialEq,
1367    SpecifiedValueInfo,
1368    ToComputedValue,
1369    ToCss,
1370    ToResolvedValue,
1371    ToShmem,
1372    ToTyped,
1373)]
1374#[repr(u8)]
1375pub enum ImageDecoding {
1376    /// Use the default heuristics.
1377    Auto,
1378    /// Force sync-decoding.
1379    Sync,
1380}