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