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