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 LengthPercentage,
53 NonNegativeLength,
54 NonNegativeLengthPercentage,
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.left.has_modern_syntax() || mix.right.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::oklab()
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 image = match input.try_parse(|i| i.expect_url_or_string()) {
428 Ok(url) => Image::Url(SpecifiedUrl::parse_from_string(
429 url.as_ref().into(),
430 context,
431 cors_mode,
432 )),
433 Err(..) => Image::parse_with_cors_mode(
434 context,
435 input,
436 cors_mode,
437 flags | ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_IMAGE_SET,
438 )?,
439 };
440
441 let mut resolution = input
442 .try_parse(|input| Resolution::parse(context, input))
443 .ok();
444 let mime_type = input.try_parse(Self::parse_type).ok();
445
446 if mime_type.is_some() && resolution.is_none() {
448 resolution = input
449 .try_parse(|input| Resolution::parse(context, input))
450 .ok();
451 }
452
453 let resolution = resolution.unwrap_or_else(|| Resolution::from_x(1.0));
454 let has_mime_type = mime_type.is_some();
455 let mime_type = mime_type.unwrap_or_default();
456
457 Ok(Self {
458 image,
459 resolution,
460 has_mime_type,
461 mime_type,
462 })
463 }
464}
465
466impl Parse for Gradient {
467 fn parse<'i, 't>(
468 context: &ParserContext,
469 input: &mut Parser<'i, 't>,
470 ) -> Result<Self, ParseError<'i>> {
471 enum Shape {
472 Linear,
473 Radial,
474 Conic,
475 }
476
477 let func = input.expect_function()?;
478 let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &func,
479 "linear-gradient" => {
480 (Shape::Linear, false, GradientCompatMode::Modern)
481 },
482 "-webkit-linear-gradient" => {
483 (Shape::Linear, false, GradientCompatMode::WebKit)
484 },
485 #[cfg(feature = "gecko")]
486 "-moz-linear-gradient" => {
487 (Shape::Linear, false, GradientCompatMode::Moz)
488 },
489 "repeating-linear-gradient" => {
490 (Shape::Linear, true, GradientCompatMode::Modern)
491 },
492 "-webkit-repeating-linear-gradient" => {
493 (Shape::Linear, true, GradientCompatMode::WebKit)
494 },
495 #[cfg(feature = "gecko")]
496 "-moz-repeating-linear-gradient" => {
497 (Shape::Linear, true, GradientCompatMode::Moz)
498 },
499 "radial-gradient" => {
500 (Shape::Radial, false, GradientCompatMode::Modern)
501 },
502 "-webkit-radial-gradient" => {
503 (Shape::Radial, false, GradientCompatMode::WebKit)
504 },
505 #[cfg(feature = "gecko")]
506 "-moz-radial-gradient" => {
507 (Shape::Radial, false, GradientCompatMode::Moz)
508 },
509 "repeating-radial-gradient" => {
510 (Shape::Radial, true, GradientCompatMode::Modern)
511 },
512 "-webkit-repeating-radial-gradient" => {
513 (Shape::Radial, true, GradientCompatMode::WebKit)
514 },
515 #[cfg(feature = "gecko")]
516 "-moz-repeating-radial-gradient" => {
517 (Shape::Radial, true, GradientCompatMode::Moz)
518 },
519 "conic-gradient" => {
520 (Shape::Conic, false, GradientCompatMode::Modern)
521 },
522 "repeating-conic-gradient" => {
523 (Shape::Conic, true, GradientCompatMode::Modern)
524 },
525 "-webkit-gradient" => {
526 return input.parse_nested_block(|i| {
527 Self::parse_webkit_gradient_argument(context, i)
528 });
529 },
530 _ => {
531 let func = func.clone();
532 return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func)));
533 }
534 };
535
536 Ok(input.parse_nested_block(|i| {
537 Ok(match shape {
538 Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?,
539 Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?,
540 Shape::Conic => Self::parse_conic(context, i, repeating)?,
541 })
542 })?)
543 }
544}
545
546impl Gradient {
547 fn parse_webkit_gradient_argument<'i, 't>(
548 context: &ParserContext,
549 input: &mut Parser<'i, 't>,
550 ) -> Result<Self, ParseError<'i>> {
551 use crate::values::specified::position::{
552 HorizontalPositionKeyword as X, VerticalPositionKeyword as Y,
553 };
554 type Point = GenericPosition<Component<X>, Component<Y>>;
555
556 #[derive(Clone, Copy, Parse)]
557 enum Component<S> {
558 Center,
559 Number(NumberOrPercentage),
560 Side(S),
561 }
562
563 fn line_direction_from_points(first: Point, second: Point) -> LineDirection {
564 let h_ord = first.horizontal.partial_cmp(&second.horizontal);
565 let v_ord = first.vertical.partial_cmp(&second.vertical);
566 let (h, v) = match (h_ord, v_ord) {
567 (Some(h), Some(v)) => (h, v),
568 _ => return LineDirection::Vertical(Y::Bottom),
569 };
570 match (h, v) {
571 (Ordering::Less, Ordering::Less) => LineDirection::Corner(X::Right, Y::Bottom),
572 (Ordering::Less, Ordering::Equal) => LineDirection::Horizontal(X::Right),
573 (Ordering::Less, Ordering::Greater) => LineDirection::Corner(X::Right, Y::Top),
574 (Ordering::Equal, Ordering::Greater) => LineDirection::Vertical(Y::Top),
575 (Ordering::Equal, Ordering::Equal) | (Ordering::Equal, Ordering::Less) => {
576 LineDirection::Vertical(Y::Bottom)
577 },
578 (Ordering::Greater, Ordering::Less) => LineDirection::Corner(X::Left, Y::Bottom),
579 (Ordering::Greater, Ordering::Equal) => LineDirection::Horizontal(X::Left),
580 (Ordering::Greater, Ordering::Greater) => LineDirection::Corner(X::Left, Y::Top),
581 }
582 }
583
584 impl Parse for Point {
585 fn parse<'i, 't>(
586 context: &ParserContext,
587 input: &mut Parser<'i, 't>,
588 ) -> Result<Self, ParseError<'i>> {
589 input.try_parse(|i| {
590 let x = Component::parse(context, i)?;
591 let y = Component::parse(context, i)?;
592
593 Ok(Self::new(x, y))
594 })
595 }
596 }
597
598 impl<S: Side> Into<NumberOrPercentage> for Component<S> {
599 fn into(self) -> NumberOrPercentage {
600 match self {
601 Component::Center => NumberOrPercentage::Percentage(Percentage::new(0.5)),
602 Component::Number(number) => number,
603 Component::Side(side) => {
604 let p = if side.is_start() {
605 Percentage::zero()
606 } else {
607 Percentage::hundred()
608 };
609 NumberOrPercentage::Percentage(p)
610 },
611 }
612 }
613 }
614
615 impl<S: Side> Into<PositionComponent<S>> for Component<S> {
616 fn into(self) -> PositionComponent<S> {
617 match self {
618 Component::Center => PositionComponent::Center,
619 Component::Number(NumberOrPercentage::Number(number)) => {
620 PositionComponent::Length(Length::from_px(number.value).into())
621 },
622 Component::Number(NumberOrPercentage::Percentage(p)) => {
623 PositionComponent::Length(p.into())
624 },
625 Component::Side(side) => PositionComponent::Side(side, None),
626 }
627 }
628 }
629
630 impl<S: Copy + Side> Component<S> {
631 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
632 match ((*self).into(), (*other).into()) {
633 (NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => {
634 a.get().partial_cmp(&b.get())
635 },
636 (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => {
637 a.value.partial_cmp(&b.value)
638 },
639 (_, _) => None,
640 }
641 }
642 }
643
644 let ident = input.expect_ident_cloned()?;
645 input.expect_comma()?;
646
647 Ok(match_ignore_ascii_case! { &ident,
648 "linear" => {
649 let first = Point::parse(context, input)?;
650 input.expect_comma()?;
651 let second = Point::parse(context, input)?;
652
653 let direction = line_direction_from_points(first, second);
654 let items = Gradient::parse_webkit_gradient_stops(context, input, false)?;
655
656 generic::Gradient::Linear {
657 direction,
658 color_interpolation_method: ColorInterpolationMethod::srgb(),
659 items,
660 flags: generic::GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
662 compat_mode: GradientCompatMode::Modern,
663 }
664 },
665 "radial" => {
666 let first_point = Point::parse(context, input)?;
667 input.expect_comma()?;
668 let first_radius = Number::parse_non_negative(context, input)?;
669 input.expect_comma()?;
670 let second_point = Point::parse(context, input)?;
671 input.expect_comma()?;
672 let second_radius = Number::parse_non_negative(context, input)?;
673
674 let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value {
675 (false, second_point, second_radius)
676 } else {
677 (true, first_point, first_radius)
678 };
679
680 let rad = Circle::Radius(NonNegative(Length::from_px(radius.value)));
681 let shape = generic::EndingShape::Circle(rad);
682 let position = Position::new(point.horizontal.into(), point.vertical.into());
683 let items = Gradient::parse_webkit_gradient_stops(context, input, reverse_stops)?;
684
685 generic::Gradient::Radial {
686 shape,
687 position,
688 color_interpolation_method: ColorInterpolationMethod::srgb(),
689 items,
690 flags: generic::GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
692 compat_mode: GradientCompatMode::Modern,
693 }
694 },
695 _ => {
696 let e = SelectorParseErrorKind::UnexpectedIdent(ident.clone());
697 return Err(input.new_custom_error(e));
698 },
699 })
700 }
701
702 fn parse_webkit_gradient_stops<'i, 't>(
703 context: &ParserContext,
704 input: &mut Parser<'i, 't>,
705 reverse_stops: bool,
706 ) -> Result<LengthPercentageItemList, ParseError<'i>> {
707 let mut items = input
708 .try_parse(|i| {
709 i.expect_comma()?;
710 i.parse_comma_separated(|i| {
711 let function = i.expect_function()?.clone();
712 let (color, mut p) = i.parse_nested_block(|i| {
713 let p = match_ignore_ascii_case! { &function,
714 "color-stop" => {
715 let p = NumberOrPercentage::parse(context, i)?.to_percentage();
716 i.expect_comma()?;
717 p
718 },
719 "from" => Percentage::zero(),
720 "to" => Percentage::hundred(),
721 _ => {
722 return Err(i.new_custom_error(
723 StyleParseErrorKind::UnexpectedFunction(function.clone())
724 ))
725 },
726 };
727 let color = Color::parse(context, i)?;
728 if color == Color::CurrentColor {
729 return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError));
730 }
731 Ok((color.into(), p))
732 })?;
733 if reverse_stops {
734 p.reverse();
735 }
736 Ok(generic::GradientItem::ComplexColorStop {
737 color,
738 position: p.into(),
739 })
740 })
741 })
742 .unwrap_or(vec![]);
743
744 if items.is_empty() {
745 items = vec![
746 generic::GradientItem::ComplexColorStop {
747 color: Color::transparent(),
748 position: LengthPercentage::zero_percent(),
749 },
750 generic::GradientItem::ComplexColorStop {
751 color: Color::transparent(),
752 position: LengthPercentage::hundred_percent(),
753 },
754 ];
755 } else if items.len() == 1 {
756 let first = items[0].clone();
757 items.push(first);
758 } else {
759 items.sort_by(|a, b| {
760 match (a, b) {
761 (
762 &generic::GradientItem::ComplexColorStop {
763 position: ref a_position,
764 ..
765 },
766 &generic::GradientItem::ComplexColorStop {
767 position: ref b_position,
768 ..
769 },
770 ) => match (a_position, b_position) {
771 (&LengthPercentage::Percentage(a), &LengthPercentage::Percentage(b)) => {
772 return a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal);
773 },
774 _ => {},
775 },
776 _ => {},
777 }
778 if reverse_stops {
779 Ordering::Greater
780 } else {
781 Ordering::Less
782 }
783 })
784 }
785 Ok(items.into())
786 }
787
788 fn parse_stops<'i, 't>(
790 context: &ParserContext,
791 input: &mut Parser<'i, 't>,
792 ) -> Result<LengthPercentageItemList, ParseError<'i>> {
793 let items =
794 generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?;
795 if items.is_empty() {
796 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
797 }
798 Ok(items)
799 }
800
801 fn try_parse_color_interpolation_method<'i, 't>(
803 context: &ParserContext,
804 input: &mut Parser<'i, 't>,
805 ) -> Option<ColorInterpolationMethod> {
806 if gradient_color_interpolation_method_enabled() {
807 input
808 .try_parse(|i| ColorInterpolationMethod::parse(context, i))
809 .ok()
810 } else {
811 None
812 }
813 }
814
815 fn parse_linear<'i, 't>(
818 context: &ParserContext,
819 input: &mut Parser<'i, 't>,
820 repeating: bool,
821 mut compat_mode: GradientCompatMode,
822 ) -> Result<Self, ParseError<'i>> {
823 let mut flags = GradientFlags::empty();
824 flags.set(GradientFlags::REPEATING, repeating);
825
826 let mut color_interpolation_method =
827 Self::try_parse_color_interpolation_method(context, input);
828
829 let direction = input
830 .try_parse(|p| LineDirection::parse(context, p, &mut compat_mode))
831 .ok();
832
833 if direction.is_some() && color_interpolation_method.is_none() {
834 color_interpolation_method = Self::try_parse_color_interpolation_method(context, input);
835 }
836
837 if color_interpolation_method.is_some() || direction.is_some() {
839 input.expect_comma()?;
840 }
841
842 let items = Gradient::parse_stops(context, input)?;
843
844 let default = default_color_interpolation_method(&items);
845 let color_interpolation_method = color_interpolation_method.unwrap_or(default);
846 flags.set(
847 GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
848 default == color_interpolation_method,
849 );
850
851 let direction = direction.unwrap_or(match compat_mode {
852 GradientCompatMode::Modern => LineDirection::Vertical(VerticalPositionKeyword::Bottom),
853 _ => LineDirection::Vertical(VerticalPositionKeyword::Top),
854 });
855
856 Ok(Gradient::Linear {
857 direction,
858 color_interpolation_method,
859 items,
860 flags,
861 compat_mode,
862 })
863 }
864
865 fn parse_radial<'i, 't>(
867 context: &ParserContext,
868 input: &mut Parser<'i, 't>,
869 repeating: bool,
870 compat_mode: GradientCompatMode,
871 ) -> Result<Self, ParseError<'i>> {
872 let mut flags = GradientFlags::empty();
873 flags.set(GradientFlags::REPEATING, repeating);
874
875 let mut color_interpolation_method =
876 Self::try_parse_color_interpolation_method(context, input);
877
878 let (shape, position) = match compat_mode {
879 GradientCompatMode::Modern => {
880 let shape = input.try_parse(|i| EndingShape::parse(context, i, compat_mode));
881 let position = input.try_parse(|i| {
882 i.expect_ident_matching("at")?;
883 Position::parse(context, i)
884 });
885 (shape, position.ok())
886 },
887 _ => {
888 let position = input.try_parse(|i| Position::parse(context, i));
889 let shape = input.try_parse(|i| {
890 if position.is_ok() {
891 i.expect_comma()?;
892 }
893 EndingShape::parse(context, i, compat_mode)
894 });
895 (shape, position.ok())
896 },
897 };
898
899 let has_shape_or_position = shape.is_ok() || position.is_some();
900 if has_shape_or_position && color_interpolation_method.is_none() {
901 color_interpolation_method = Self::try_parse_color_interpolation_method(context, input);
902 }
903
904 if has_shape_or_position || color_interpolation_method.is_some() {
905 input.expect_comma()?;
906 }
907
908 let shape = shape.unwrap_or({
909 generic::EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
910 });
911
912 let position = position.unwrap_or(Position::center());
913
914 let items = Gradient::parse_stops(context, input)?;
915
916 let default = default_color_interpolation_method(&items);
917 let color_interpolation_method = color_interpolation_method.unwrap_or(default);
918 flags.set(
919 GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
920 default == color_interpolation_method,
921 );
922
923 Ok(Gradient::Radial {
924 shape,
925 position,
926 color_interpolation_method,
927 items,
928 flags,
929 compat_mode,
930 })
931 }
932
933 fn parse_conic<'i, 't>(
935 context: &ParserContext,
936 input: &mut Parser<'i, 't>,
937 repeating: bool,
938 ) -> Result<Self, ParseError<'i>> {
939 let mut flags = GradientFlags::empty();
940 flags.set(GradientFlags::REPEATING, repeating);
941
942 let mut color_interpolation_method =
943 Self::try_parse_color_interpolation_method(context, input);
944
945 let angle = input.try_parse(|i| {
946 i.expect_ident_matching("from")?;
947 Angle::parse_with_unitless(context, i)
950 });
951 let position = input.try_parse(|i| {
952 i.expect_ident_matching("at")?;
953 Position::parse(context, i)
954 });
955
956 let has_angle_or_position = angle.is_ok() || position.is_ok();
957 if has_angle_or_position && color_interpolation_method.is_none() {
958 color_interpolation_method = Self::try_parse_color_interpolation_method(context, input);
959 }
960
961 if has_angle_or_position || color_interpolation_method.is_some() {
962 input.expect_comma()?;
963 }
964
965 let angle = angle.unwrap_or(Angle::zero());
966
967 let position = position.unwrap_or(Position::center());
968
969 let items = generic::GradientItem::parse_comma_separated(
970 context,
971 input,
972 AngleOrPercentage::parse_with_unitless,
973 )?;
974
975 if items.is_empty() {
976 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
977 }
978
979 let default = default_color_interpolation_method(&items);
980 let color_interpolation_method = color_interpolation_method.unwrap_or(default);
981 flags.set(
982 GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD,
983 default == color_interpolation_method,
984 );
985
986 Ok(Gradient::Conic {
987 angle,
988 position,
989 color_interpolation_method,
990 items,
991 flags,
992 })
993 }
994}
995
996impl generic::LineDirection for LineDirection {
997 fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool {
998 match *self {
999 LineDirection::Angle(ref angle) => angle.degrees() == 180.0,
1000 LineDirection::Vertical(VerticalPositionKeyword::Bottom) => {
1001 compat_mode == GradientCompatMode::Modern
1002 },
1003 LineDirection::Vertical(VerticalPositionKeyword::Top) => {
1004 compat_mode != GradientCompatMode::Modern
1005 },
1006 _ => false,
1007 }
1008 }
1009
1010 fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
1011 where
1012 W: Write,
1013 {
1014 match *self {
1015 LineDirection::Angle(angle) => angle.to_css(dest),
1016 LineDirection::Horizontal(x) => {
1017 if compat_mode == GradientCompatMode::Modern {
1018 dest.write_str("to ")?;
1019 }
1020 x.to_css(dest)
1021 },
1022 LineDirection::Vertical(y) => {
1023 if compat_mode == GradientCompatMode::Modern {
1024 dest.write_str("to ")?;
1025 }
1026 y.to_css(dest)
1027 },
1028 LineDirection::Corner(x, y) => {
1029 if compat_mode == GradientCompatMode::Modern {
1030 dest.write_str("to ")?;
1031 }
1032 x.to_css(dest)?;
1033 dest.write_char(' ')?;
1034 y.to_css(dest)
1035 },
1036 }
1037 }
1038}
1039
1040impl LineDirection {
1041 fn parse<'i, 't>(
1042 context: &ParserContext,
1043 input: &mut Parser<'i, 't>,
1044 compat_mode: &mut GradientCompatMode,
1045 ) -> Result<Self, ParseError<'i>> {
1046 if let Ok(angle) = input.try_parse(|i| Angle::parse_with_unitless(context, i)) {
1049 return Ok(LineDirection::Angle(angle));
1050 }
1051
1052 input.try_parse(|i| {
1053 let to_ident = i.try_parse(|i| i.expect_ident_matching("to"));
1054 match *compat_mode {
1055 GradientCompatMode::Modern => to_ident?,
1057 GradientCompatMode::Moz if to_ident.is_ok() => {
1061 *compat_mode = GradientCompatMode::Modern
1062 },
1063 GradientCompatMode::WebKit if to_ident.is_ok() => {
1066 return Err(
1067 i.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("to".into()))
1068 );
1069 },
1070 _ => {},
1071 }
1072
1073 if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) {
1074 if let Ok(y) = i.try_parse(VerticalPositionKeyword::parse) {
1075 return Ok(LineDirection::Corner(x, y));
1076 }
1077 return Ok(LineDirection::Horizontal(x));
1078 }
1079 let y = VerticalPositionKeyword::parse(i)?;
1080 if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) {
1081 return Ok(LineDirection::Corner(x, y));
1082 }
1083 Ok(LineDirection::Vertical(y))
1084 })
1085 }
1086}
1087
1088impl EndingShape {
1089 fn parse<'i, 't>(
1090 context: &ParserContext,
1091 input: &mut Parser<'i, 't>,
1092 compat_mode: GradientCompatMode,
1093 ) -> Result<Self, ParseError<'i>> {
1094 if let Ok(extent) = input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode))
1095 {
1096 if input
1097 .try_parse(|i| i.expect_ident_matching("circle"))
1098 .is_ok()
1099 {
1100 return Ok(generic::EndingShape::Circle(Circle::Extent(extent)));
1101 }
1102 let _ = input.try_parse(|i| i.expect_ident_matching("ellipse"));
1103 return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent)));
1104 }
1105 if input
1106 .try_parse(|i| i.expect_ident_matching("circle"))
1107 .is_ok()
1108 {
1109 if let Ok(extent) =
1110 input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode))
1111 {
1112 return Ok(generic::EndingShape::Circle(Circle::Extent(extent)));
1113 }
1114 if compat_mode == GradientCompatMode::Modern {
1115 if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) {
1116 return Ok(generic::EndingShape::Circle(Circle::Radius(length)));
1117 }
1118 }
1119 return Ok(generic::EndingShape::Circle(Circle::Extent(
1120 ShapeExtent::FarthestCorner,
1121 )));
1122 }
1123 if input
1124 .try_parse(|i| i.expect_ident_matching("ellipse"))
1125 .is_ok()
1126 {
1127 if let Ok(extent) =
1128 input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode))
1129 {
1130 return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent)));
1131 }
1132 if compat_mode == GradientCompatMode::Modern {
1133 let pair: Result<_, ParseError> = input.try_parse(|i| {
1134 let x = NonNegativeLengthPercentage::parse(context, i)?;
1135 let y = NonNegativeLengthPercentage::parse(context, i)?;
1136 Ok((x, y))
1137 });
1138 if let Ok((x, y)) = pair {
1139 return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(x, y)));
1140 }
1141 }
1142 return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(
1143 ShapeExtent::FarthestCorner,
1144 )));
1145 }
1146 if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) {
1147 if let Ok(y) = input.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) {
1148 if compat_mode == GradientCompatMode::Modern {
1149 let _ = input.try_parse(|i| i.expect_ident_matching("ellipse"));
1150 }
1151 return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(
1152 NonNegative(LengthPercentage::from(length.0)),
1153 y,
1154 )));
1155 }
1156 if compat_mode == GradientCompatMode::Modern {
1157 let y = input.try_parse(|i| {
1158 i.expect_ident_matching("ellipse")?;
1159 NonNegativeLengthPercentage::parse(context, i)
1160 });
1161 if let Ok(y) = y {
1162 return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(
1163 NonNegative(LengthPercentage::from(length.0)),
1164 y,
1165 )));
1166 }
1167 let _ = input.try_parse(|i| i.expect_ident_matching("circle"));
1168 }
1169
1170 return Ok(generic::EndingShape::Circle(Circle::Radius(length)));
1171 }
1172 input.try_parse(|i| {
1173 let x = Percentage::parse_non_negative(context, i)?;
1174 let y = if let Ok(y) = i.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) {
1175 if compat_mode == GradientCompatMode::Modern {
1176 let _ = i.try_parse(|i| i.expect_ident_matching("ellipse"));
1177 }
1178 y
1179 } else {
1180 if compat_mode == GradientCompatMode::Modern {
1181 i.expect_ident_matching("ellipse")?;
1182 }
1183 NonNegativeLengthPercentage::parse(context, i)?
1184 };
1185 Ok(generic::EndingShape::Ellipse(Ellipse::Radii(
1186 NonNegative(LengthPercentage::from(x)),
1187 y,
1188 )))
1189 })
1190 }
1191}
1192
1193impl ShapeExtent {
1194 fn parse_with_compat_mode<'i, 't>(
1195 input: &mut Parser<'i, 't>,
1196 compat_mode: GradientCompatMode,
1197 ) -> Result<Self, ParseError<'i>> {
1198 match Self::parse(input)? {
1199 ShapeExtent::Contain | ShapeExtent::Cover
1200 if compat_mode == GradientCompatMode::Modern =>
1201 {
1202 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1203 },
1204 ShapeExtent::Contain => Ok(ShapeExtent::ClosestSide),
1205 ShapeExtent::Cover => Ok(ShapeExtent::FarthestCorner),
1206 keyword => Ok(keyword),
1207 }
1208 }
1209}
1210
1211impl<T> generic::GradientItem<Color, T> {
1212 fn parse_comma_separated<'i, 't>(
1213 context: &ParserContext,
1214 input: &mut Parser<'i, 't>,
1215 parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>>
1216 + Copy,
1217 ) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> {
1218 let mut items = Vec::new();
1219 let mut seen_stop = false;
1220
1221 loop {
1222 input.parse_until_before(Delimiter::Comma, |input| {
1223 if seen_stop {
1224 if let Ok(hint) = input.try_parse(|i| parse_position(context, i)) {
1225 seen_stop = false;
1226 items.push(generic::GradientItem::InterpolationHint(hint));
1227 return Ok(());
1228 }
1229 }
1230
1231 let stop = generic::ColorStop::parse(context, input, parse_position)?;
1232
1233 if let Ok(multi_position) = input.try_parse(|i| parse_position(context, i)) {
1234 let stop_color = stop.color.clone();
1235 items.push(stop.into_item());
1236 items.push(
1237 generic::ColorStop {
1238 color: stop_color,
1239 position: Some(multi_position),
1240 }
1241 .into_item(),
1242 );
1243 } else {
1244 items.push(stop.into_item());
1245 }
1246
1247 seen_stop = true;
1248 Ok(())
1249 })?;
1250
1251 match input.next() {
1252 Err(_) => break,
1253 Ok(&Token::Comma) => continue,
1254 Ok(_) => unreachable!(),
1255 }
1256 }
1257
1258 if !seen_stop || items.is_empty() {
1259 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1260 }
1261 Ok(items.into())
1262 }
1263}
1264
1265impl<T> generic::ColorStop<Color, T> {
1266 fn parse<'i, 't>(
1267 context: &ParserContext,
1268 input: &mut Parser<'i, 't>,
1269 parse_position: impl for<'i1, 't1> Fn(
1270 &ParserContext,
1271 &mut Parser<'i1, 't1>,
1272 ) -> Result<T, ParseError<'i1>>,
1273 ) -> Result<Self, ParseError<'i>> {
1274 Ok(generic::ColorStop {
1275 color: Color::parse(context, input)?,
1276 position: input.try_parse(|i| parse_position(context, i)).ok(),
1277 })
1278 }
1279}
1280
1281impl PaintWorklet {
1282 #[cfg(feature = "servo")]
1283 fn parse_args<'i>(
1284 context: &ParserContext,
1285 input: &mut Parser<'i, '_>,
1286 ) -> Result<Self, ParseError<'i>> {
1287 use crate::custom_properties::SpecifiedValue;
1288 use servo_arc::Arc;
1289 let name = Atom::from(&**input.expect_ident()?);
1290 let arguments = input
1291 .try_parse(|input| {
1292 input.expect_comma()?;
1293 input.parse_comma_separated(|input| {
1294 SpecifiedValue::parse(input, &context.url_data).map(Arc::new)
1295 })
1296 })
1297 .unwrap_or_default();
1298 Ok(Self { name, arguments })
1299 }
1300}
1301
1302#[allow(missing_docs)]
1304#[derive(
1305 Clone,
1306 Copy,
1307 Debug,
1308 Eq,
1309 Hash,
1310 MallocSizeOf,
1311 Parse,
1312 PartialEq,
1313 SpecifiedValueInfo,
1314 ToCss,
1315 ToComputedValue,
1316 ToResolvedValue,
1317 ToShmem,
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}