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