1use 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
42pub type Image = generic::Image<Gradient, SpecifiedUrl, Color, Percentage, Resolution>;
45
46size_of_test!(Image, 16);
48
49pub type Gradient = generic::Gradient<
52 LineDirection,
53 Length,
54 LengthPercentage,
55 Position,
56 Angle,
57 AngleOrPercentage,
58 Color,
59>;
60
61pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
65pub type CrossFadeElement = generic::CrossFadeElement<Image, Color, Percentage>;
67pub type CrossFadeImage = generic::CrossFadeImage<Image, Color>;
69
70pub type ImageSet = generic::ImageSet<Image, Resolution>;
72
73pub 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 _ => 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 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
152impl<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#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
176pub enum LineDirection {
177 Angle(Angle),
179 Horizontal(HorizontalPositionKeyword),
181 Vertical(VerticalPositionKeyword),
183 Corner(HorizontalPositionKeyword, VerticalPositionKeyword),
185}
186
187pub 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 #[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 #[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 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 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 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 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 input
340 .try_parse(|input| Percentage::parse_non_negative(context, input))
341 .ok()
342 .map(|p| p.clamp_to_hundred())
343 }
344
345 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 let mut percent = Self::parse_percentage(context, input);
354 let image = CrossFadeImage::parse(context, input, cors_mode, flags)?;
356 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 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 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 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 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 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 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 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 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 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 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 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 GradientCompatMode::Modern => to_ident?,
1065 GradientCompatMode::Moz if to_ident.is_ok() => {
1069 *compat_mode = GradientCompatMode::Modern
1070 },
1071 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#[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 #[cfg(feature = "gecko")]
1350 Optimizespeed,
1351 #[cfg(feature = "gecko")]
1352 Optimizequality,
1353}