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