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