1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! CSS handling for the specified value of
6 //! [`image`][image]s
7 //!
8 //! [image]: https://drafts.csswg.org/css-images/#image-values
9 
10 use Atom;
11 use cssparser::{Parser, Token};
12 use custom_properties::SpecifiedValue;
13 use parser::{Parse, ParserContext};
14 use selectors::parser::SelectorParseErrorKind;
15 #[cfg(feature = "servo")]
16 use servo_url::ServoUrl;
17 use std::cmp::Ordering;
18 use std::f32::consts::PI;
19 use std::fmt::{self, Write};
20 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
21 use values::{Either, None_};
22 #[cfg(feature = "gecko")]
23 use values::computed::{Context, Position as ComputedPosition, ToComputedValue};
24 use values::generics::image::{Circle, CompatMode, Ellipse, ColorStop as GenericColorStop};
25 use values::generics::image::{EndingShape as GenericEndingShape, Gradient as GenericGradient};
26 use values::generics::image::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind};
27 use values::generics::image::{Image as GenericImage, LineDirection as GenericsLineDirection};
28 use values::generics::image::{MozImageRect as GenericMozImageRect, ShapeExtent};
29 use values::generics::image::PaintWorklet;
30 use values::generics::position::Position as GenericPosition;
31 use values::specified::{Angle, Color, Length, LengthOrPercentage};
32 use values::specified::{Number, NumberOrPercentage, Percentage, RGBAColor};
33 use values::specified::position::{LegacyPosition, Position, PositionComponent, Side, X, Y};
34 use values::specified::url::SpecifiedImageUrl;
35 
36 /// A specified image layer.
37 pub type ImageLayer = Either<None_, Image>;
38 
39 /// Specified values for an image according to CSS-IMAGES.
40 /// <https://drafts.csswg.org/css-images/#image-values>
41 pub type Image = GenericImage<Gradient, MozImageRect, SpecifiedImageUrl>;
42 
43 /// Specified values for a CSS gradient.
44 /// <https://drafts.csswg.org/css-images/#gradients>
45 #[cfg(not(feature = "gecko"))]
46 pub type Gradient = GenericGradient<
47     LineDirection,
48     Length,
49     LengthOrPercentage,
50     Position,
51     RGBAColor,
52     Angle,
53 >;
54 
55 /// Specified values for a CSS gradient.
56 /// <https://drafts.csswg.org/css-images/#gradients>
57 #[cfg(feature = "gecko")]
58 pub type Gradient = GenericGradient<
59     LineDirection,
60     Length,
61     LengthOrPercentage,
62     GradientPosition,
63     RGBAColor,
64     Angle,
65 >;
66 
67 /// A specified gradient kind.
68 #[cfg(not(feature = "gecko"))]
69 pub type GradientKind = GenericGradientKind<
70     LineDirection,
71     Length,
72     LengthOrPercentage,
73     Position,
74     Angle,
75 >;
76 
77 /// A specified gradient kind.
78 #[cfg(feature = "gecko")]
79 pub type GradientKind = GenericGradientKind<
80     LineDirection,
81     Length,
82     LengthOrPercentage,
83     GradientPosition,
84     Angle,
85 >;
86 
87 /// A specified gradient line direction.
88 #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
89 pub enum LineDirection {
90     /// An angular direction.
91     Angle(Angle),
92     /// A horizontal direction.
93     Horizontal(X),
94     /// A vertical direction.
95     Vertical(Y),
96     /// A direction towards a corner of a box.
97     Corner(X, Y),
98     /// A Position and an Angle for legacy `-moz-` prefixed gradient.
99     /// `-moz-` prefixed linear gradient can contain both a position and an angle but it
100     /// uses legacy syntax for position. That means we can't specify both keyword and
101     /// length for each horizontal/vertical components.
102     #[cfg(feature = "gecko")]
103     MozPosition(Option<LegacyPosition>, Option<Angle>),
104 }
105 
106 /// A binary enum to hold either Position or LegacyPosition.
107 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
108 #[cfg(feature = "gecko")]
109 pub enum GradientPosition {
110     /// 1, 2, 3, 4-valued <position>.
111     Modern(Position),
112     /// 1, 2-valued <position>.
113     Legacy(LegacyPosition),
114 }
115 
116 /// A specified ending shape.
117 pub type EndingShape = GenericEndingShape<Length, LengthOrPercentage>;
118 
119 /// A specified gradient item.
120 pub type GradientItem = GenericGradientItem<RGBAColor, LengthOrPercentage>;
121 
122 /// A computed color stop.
123 pub type ColorStop = GenericColorStop<RGBAColor, LengthOrPercentage>;
124 
125 /// Specified values for `moz-image-rect`
126 /// -moz-image-rect(<uri>, top, right, bottom, left);
127 pub type MozImageRect = GenericMozImageRect<NumberOrPercentage, SpecifiedImageUrl>;
128 
129 impl Parse for Image {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Image, ParseError<'i>>130     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Image, ParseError<'i>> {
131         if let Ok(url) = input.try(|input| SpecifiedImageUrl::parse(context, input)) {
132             return Ok(GenericImage::Url(url));
133         }
134         if let Ok(gradient) = input.try(|i| Gradient::parse(context, i)) {
135             return Ok(GenericImage::Gradient(Box::new(gradient)));
136         }
137         #[cfg(feature = "servo")]
138         {
139             if let Ok(paint_worklet) = input.try(|i| PaintWorklet::parse(context, i)) {
140                 return Ok(GenericImage::PaintWorklet(paint_worklet));
141             }
142         }
143         if let Ok(image_rect) = input.try(|input| MozImageRect::parse(context, input)) {
144             return Ok(GenericImage::Rect(Box::new(image_rect)));
145         }
146         Ok(GenericImage::Element(Image::parse_element(input)?))
147     }
148 }
149 
150 impl Image {
151     /// Creates an already specified image value from an already resolved URL
152     /// for insertion in the cascade.
153     #[cfg(feature = "servo")]
for_cascade(url: ServoUrl) -> Self154     pub fn for_cascade(url: ServoUrl) -> Self {
155         use values::CssUrl;
156         GenericImage::Url(CssUrl::for_cascade(url))
157     }
158 
159     /// Parses a `-moz-element(# <element-id>)`.
parse_element<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Atom, ParseError<'i>>160     fn parse_element<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Atom, ParseError<'i>> {
161         input.try(|i| i.expect_function_matching("-moz-element"))?;
162         let location = input.current_source_location();
163         input.parse_nested_block(|i| {
164             match *i.next()? {
165                 Token::IDHash(ref id) => Ok(Atom::from(id.as_ref())),
166                 ref t => Err(location.new_unexpected_token_error(t.clone())),
167             }
168         })
169     }
170 
171     /// Provides an alternate method for parsing that associates the URL
172     /// with anonymous CORS headers.
parse_with_cors_anonymous<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Image, ParseError<'i>>173     pub fn parse_with_cors_anonymous<'i, 't>(
174         context: &ParserContext,
175         input: &mut Parser<'i, 't>,
176     ) -> Result<Image, ParseError<'i>> {
177         if let Ok(url) = input.try(|input| SpecifiedImageUrl::parse_with_cors_anonymous(context, input)) {
178             return Ok(GenericImage::Url(url));
179         }
180         Self::parse(context, input)
181     }
182 }
183 
184 impl Parse for Gradient {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>185     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
186         enum Shape {
187             Linear,
188             Radial,
189         }
190 
191         // FIXME: remove clone() when lifetimes are non-lexical
192         let func = input.expect_function()?.clone();
193         let result = match_ignore_ascii_case! { &func,
194             "linear-gradient" => {
195                 Some((Shape::Linear, false, CompatMode::Modern))
196             },
197             "-webkit-linear-gradient" => {
198                 Some((Shape::Linear, false, CompatMode::WebKit))
199             },
200             #[cfg(feature = "gecko")]
201             "-moz-linear-gradient" => {
202                 Some((Shape::Linear, false, CompatMode::Moz))
203             },
204             "repeating-linear-gradient" => {
205                 Some((Shape::Linear, true, CompatMode::Modern))
206             },
207             "-webkit-repeating-linear-gradient" => {
208                 Some((Shape::Linear, true, CompatMode::WebKit))
209             },
210             #[cfg(feature = "gecko")]
211             "-moz-repeating-linear-gradient" => {
212                 Some((Shape::Linear, true, CompatMode::Moz))
213             },
214             "radial-gradient" => {
215                 Some((Shape::Radial, false, CompatMode::Modern))
216             },
217             "-webkit-radial-gradient" => {
218                 Some((Shape::Radial, false, CompatMode::WebKit))
219             }
220             #[cfg(feature = "gecko")]
221             "-moz-radial-gradient" => {
222                 Some((Shape::Radial, false, CompatMode::Moz))
223             },
224             "repeating-radial-gradient" => {
225                 Some((Shape::Radial, true, CompatMode::Modern))
226             },
227             "-webkit-repeating-radial-gradient" => {
228                 Some((Shape::Radial, true, CompatMode::WebKit))
229             },
230             #[cfg(feature = "gecko")]
231             "-moz-repeating-radial-gradient" => {
232                 Some((Shape::Radial, true, CompatMode::Moz))
233             },
234             "-webkit-gradient" => {
235                 return input.parse_nested_block(|i| Self::parse_webkit_gradient_argument(context, i));
236             },
237             _ => None,
238         };
239 
240         let (shape, repeating, mut compat_mode) = match result {
241             Some(result) => result,
242             None => return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func.clone()))),
243         };
244 
245         let (kind, items) = input.parse_nested_block(|i| {
246             let shape = match shape {
247                 Shape::Linear => GradientKind::parse_linear(context, i, &mut compat_mode)?,
248                 Shape::Radial => GradientKind::parse_radial(context, i, &mut compat_mode)?,
249             };
250             let items = GradientItem::parse_comma_separated(context, i)?;
251             Ok((shape, items))
252         })?;
253 
254         if items.len() < 2 {
255             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
256         }
257 
258         Ok(Gradient {
259             items: items,
260             repeating: repeating,
261             kind: kind,
262             compat_mode: compat_mode,
263         })
264     }
265 }
266 
267 impl Gradient {
parse_webkit_gradient_argument<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>268     fn parse_webkit_gradient_argument<'i, 't>(
269         context: &ParserContext,
270         input: &mut Parser<'i, 't>,
271     ) -> Result<Self, ParseError<'i>> {
272         type Point = GenericPosition<Component<X>, Component<Y>>;
273 
274         #[derive(Clone, Copy)]
275         enum Component<S> {
276             Center,
277             Number(NumberOrPercentage),
278             Side(S),
279         }
280 
281         impl LineDirection {
282             fn from_points(first: Point, second: Point) -> Self {
283                 let h_ord = first.horizontal.partial_cmp(&second.horizontal);
284                 let v_ord = first.vertical.partial_cmp(&second.vertical);
285                 let (h, v) = match (h_ord, v_ord) {
286                     (Some(h), Some(v)) => (h, v),
287                     _ => return LineDirection::Vertical(Y::Bottom),
288                 };
289                 match (h, v) {
290                     (Ordering::Less, Ordering::Less) => {
291                         LineDirection::Corner(X::Right, Y::Bottom)
292                     },
293                     (Ordering::Less, Ordering::Equal) => {
294                         LineDirection::Horizontal(X::Right)
295                     },
296                     (Ordering::Less, Ordering::Greater) => {
297                         LineDirection::Corner(X::Right, Y::Top)
298                     },
299                     (Ordering::Equal, Ordering::Greater) => {
300                         LineDirection::Vertical(Y::Top)
301                     },
302                     (Ordering::Equal, Ordering::Equal) |
303                     (Ordering::Equal, Ordering::Less) => {
304                         LineDirection::Vertical(Y::Bottom)
305                     },
306                     (Ordering::Greater, Ordering::Less) => {
307                         LineDirection::Corner(X::Left, Y::Bottom)
308                     },
309                     (Ordering::Greater, Ordering::Equal) => {
310                         LineDirection::Horizontal(X::Left)
311                     },
312                     (Ordering::Greater, Ordering::Greater) => {
313                         LineDirection::Corner(X::Left, Y::Top)
314                     },
315                 }
316             }
317         }
318 
319         impl From<Point> for Position {
320             fn from(point: Point) -> Self {
321                 Self::new(point.horizontal.into(), point.vertical.into())
322             }
323         }
324 
325         impl Parse for Point {
326             fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
327                              -> Result<Self, ParseError<'i>> {
328                 input.try(|i| {
329                     let x = Component::parse(context, i)?;
330                     let y = Component::parse(context, i)?;
331 
332                     Ok(Self::new(x, y))
333                 })
334             }
335         }
336 
337         impl<S: Side> From<Component<S>> for NumberOrPercentage {
338             fn from(component: Component<S>) -> Self {
339                 match component {
340                     Component::Center => NumberOrPercentage::Percentage(Percentage::new(0.5)),
341                     Component::Number(number) => number,
342                     Component::Side(side) => {
343                         let p = if side.is_start() { Percentage::zero() } else { Percentage::hundred() };
344                         NumberOrPercentage::Percentage(p)
345                     },
346                 }
347             }
348         }
349 
350         impl<S: Side> From<Component<S>> for PositionComponent<S> {
351             fn from(component: Component<S>) -> Self {
352                 match component {
353                     Component::Center => {
354                         PositionComponent::Center
355                     },
356                     Component::Number(NumberOrPercentage::Number(number)) => {
357                         PositionComponent::Length(Length::from_px(number.value).into())
358                     },
359                     Component::Number(NumberOrPercentage::Percentage(p)) => {
360                         PositionComponent::Length(p.into())
361                     },
362                     Component::Side(side) => {
363                         PositionComponent::Side(side, None)
364                     },
365                 }
366             }
367         }
368 
369         impl<S: Copy + Side> Component<S> {
370             fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
371                 match (NumberOrPercentage::from(*self), NumberOrPercentage::from(*other)) {
372                     (NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => {
373                         a.get().partial_cmp(&b.get())
374                     },
375                     (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => {
376                         a.value.partial_cmp(&b.value)
377                     },
378                     (_, _) => {
379                         None
380                     }
381                 }
382             }
383         }
384 
385         impl<S: Parse> Parse for Component<S> {
386             fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
387                              -> Result<Self, ParseError<'i>> {
388                 if let Ok(side) = input.try(|i| S::parse(context, i)) {
389                     return Ok(Component::Side(side));
390                 }
391                 if let Ok(number) = input.try(|i| NumberOrPercentage::parse(context, i)) {
392                     return Ok(Component::Number(number));
393                 }
394                 input.try(|i| i.expect_ident_matching("center"))?;
395                 Ok(Component::Center)
396             }
397         }
398 
399         let ident = input.expect_ident_cloned()?;
400         input.expect_comma()?;
401 
402         let (kind, reverse_stops) = match_ignore_ascii_case! { &ident,
403             "linear" => {
404                 let first = Point::parse(context, input)?;
405                 input.expect_comma()?;
406                 let second = Point::parse(context, input)?;
407 
408                 let direction = LineDirection::from_points(first, second);
409                 let kind = GenericGradientKind::Linear(direction);
410 
411                 (kind, false)
412             },
413             "radial" => {
414                 let first_point = Point::parse(context, input)?;
415                 input.expect_comma()?;
416                 let first_radius = Number::parse(context, input)?;
417                 input.expect_comma()?;
418                 let second_point = Point::parse(context, input)?;
419                 input.expect_comma()?;
420                 let second_radius = Number::parse(context, input)?;
421 
422                 let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value {
423                     (false, second_point, second_radius)
424                 } else {
425                     (true, first_point, first_radius)
426                 };
427 
428                 let shape = GenericEndingShape::Circle(Circle::Radius(Length::from_px(radius.value)));
429                 let position: Position = point.into();
430 
431                 #[cfg(feature = "gecko")]
432                 {
433                     let kind = GenericGradientKind::Radial(shape, GradientPosition::Modern(position), None);
434                     (kind, reverse_stops)
435                 }
436 
437                 #[cfg(not(feature = "gecko"))]
438                 {
439                     let kind = GenericGradientKind::Radial(shape, position, None);
440                     (kind, reverse_stops)
441                 }
442             },
443             _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))),
444         };
445 
446         let mut items = input.try(|i| {
447             i.expect_comma()?;
448             i.parse_comma_separated(|i| {
449                 let function = i.expect_function()?.clone();
450                 let (color, mut p) = i.parse_nested_block(|i| {
451                     let p = match_ignore_ascii_case! { &function,
452                         "color-stop" => {
453                             let p = match NumberOrPercentage::parse(context, i)? {
454                                 NumberOrPercentage::Number(number) => Percentage::new(number.value),
455                                 NumberOrPercentage::Percentage(p) => p,
456                             };
457                             i.expect_comma()?;
458                             p
459                         },
460                         "from" => Percentage::zero(),
461                         "to" => Percentage::hundred(),
462                         _ => return Err(i.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
463                     };
464                     let color = Color::parse(context, i)?;
465                     if color == Color::CurrentColor {
466                         return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError));
467                     }
468                     Ok((color.into(), p))
469                 })?;
470                 if reverse_stops {
471                     p.reverse();
472                 }
473                 Ok(GenericGradientItem::ColorStop(GenericColorStop {
474                     color: color,
475                     position: Some(p.into()),
476                 }))
477             })
478         }).unwrap_or(vec![]);
479 
480         if items.is_empty() {
481             items = vec![
482                 GenericGradientItem::ColorStop(GenericColorStop {
483                     color: Color::transparent().into(),
484                     position: Some(Percentage::zero().into()),
485                 }),
486                 GenericGradientItem::ColorStop(GenericColorStop {
487                     color: Color::transparent().into(),
488                     position: Some(Percentage::hundred().into()),
489                 }),
490             ];
491         } else if items.len() == 1 {
492             let first = items[0].clone();
493             items.push(first);
494         } else {
495             items.sort_by(|a, b| {
496                 match (a, b) {
497                     (&GenericGradientItem::ColorStop(ref a), &GenericGradientItem::ColorStop(ref b)) => {
498                         match (&a.position, &b.position) {
499                             (&Some(LengthOrPercentage::Percentage(a)), &Some(LengthOrPercentage::Percentage(b))) => {
500                                 return a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal);
501                             },
502                             _ => {},
503                         }
504                     },
505                     _ => {},
506                 }
507                 if reverse_stops {
508                     Ordering::Greater
509                 } else {
510                     Ordering::Less
511                 }
512             })
513         }
514 
515         Ok(GenericGradient {
516             kind: kind,
517             items: items,
518             repeating: false,
519             compat_mode: CompatMode::Modern,
520         })
521     }
522 }
523 
524 impl GradientKind {
525     /// Parses a linear gradient.
526     /// CompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword.
parse_linear<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, compat_mode: &mut CompatMode, ) -> Result<Self, ParseError<'i>>527     fn parse_linear<'i, 't>(
528         context: &ParserContext,
529         input: &mut Parser<'i, 't>,
530         compat_mode: &mut CompatMode,
531     ) -> Result<Self, ParseError<'i>> {
532         let direction = if let Ok(d) = input.try(|i| LineDirection::parse(context, i, compat_mode)) {
533             input.expect_comma()?;
534             d
535         } else {
536             match *compat_mode {
537                 CompatMode::Modern => LineDirection::Vertical(Y::Bottom),
538                 _ => LineDirection::Vertical(Y::Top),
539             }
540         };
541         Ok(GenericGradientKind::Linear(direction))
542     }
543 
parse_radial<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, compat_mode: &mut CompatMode, ) -> Result<Self, ParseError<'i>>544     fn parse_radial<'i, 't>(
545         context: &ParserContext,
546         input: &mut Parser<'i, 't>,
547         compat_mode: &mut CompatMode,
548     ) -> Result<Self, ParseError<'i>> {
549         let (shape, position, angle, moz_position) = match *compat_mode {
550             CompatMode::Modern => {
551                 let shape = input.try(|i| EndingShape::parse(context, i, *compat_mode));
552                 let position = input.try(|i| {
553                     i.expect_ident_matching("at")?;
554                     Position::parse(context, i)
555                 });
556                 (shape, position.ok(), None, None)
557             },
558             CompatMode::WebKit => {
559                 let position = input.try(|i| Position::parse(context, i));
560                 let shape = input.try(|i| {
561                     if position.is_ok() {
562                         i.expect_comma()?;
563                     }
564                     EndingShape::parse(context, i, *compat_mode)
565                 });
566                 (shape, position.ok(), None, None)
567             },
568             // The syntax of `-moz-` prefixed radial gradient is:
569             // -moz-radial-gradient(
570             //   [ [ <position> || <angle> ]?  [ ellipse | [ <length> | <percentage> ]{2} ] , |
571             //     [ <position> || <angle> ]?  [ [ circle | ellipse ] | <extent-keyword> ] , |
572             //   ]?
573             //   <color-stop> [ , <color-stop> ]+
574             // )
575             // where <extent-keyword> = closest-corner | closest-side | farthest-corner | farthest-side |
576             //                          cover | contain
577             // and <color-stop>     = <color> [ <percentage> | <length> ]?
578             CompatMode::Moz => {
579                 let mut position = input.try(|i| LegacyPosition::parse(context, i));
580                 let angle = input.try(|i| Angle::parse(context, i)).ok();
581                 if position.is_err() {
582                     position = input.try(|i| LegacyPosition::parse(context, i));
583                 }
584 
585                 let shape = input.try(|i| {
586                     if position.is_ok() || angle.is_some() {
587                         i.expect_comma()?;
588                     }
589                     EndingShape::parse(context, i, *compat_mode)
590                 });
591 
592                 (shape, None, angle, position.ok())
593             }
594         };
595 
596         if shape.is_ok() || position.is_some() || angle.is_some() || moz_position.is_some() {
597             input.expect_comma()?;
598         }
599 
600         let shape = shape.unwrap_or({
601             GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
602         });
603 
604         #[cfg(feature = "gecko")]
605         {
606             if *compat_mode == CompatMode::Moz {
607                 // If this form can be represented in Modern mode, then convert the compat_mode to Modern.
608                 if angle.is_none() {
609                     *compat_mode = CompatMode::Modern;
610                 }
611                 let position = moz_position.unwrap_or(LegacyPosition::center());
612                 return Ok(GenericGradientKind::Radial(shape, GradientPosition::Legacy(position), angle));
613             }
614         }
615 
616         let position = position.unwrap_or(Position::center());
617         #[cfg(feature = "gecko")]
618         {
619             return Ok(GenericGradientKind::Radial(shape, GradientPosition::Modern(position), angle));
620         }
621         #[cfg(not(feature = "gecko"))]
622         {
623             return Ok(GenericGradientKind::Radial(shape, position, angle));
624         }
625     }
626 }
627 
628 impl GenericsLineDirection for LineDirection {
points_downwards(&self, compat_mode: CompatMode) -> bool629     fn points_downwards(&self, compat_mode: CompatMode) -> bool {
630         match *self {
631             LineDirection::Angle(ref angle) => angle.radians() == PI,
632             LineDirection::Vertical(Y::Bottom)
633                 if compat_mode == CompatMode::Modern => true,
634             LineDirection::Vertical(Y::Top)
635                 if compat_mode != CompatMode::Modern => true,
636             #[cfg(feature = "gecko")]
637             LineDirection::MozPosition(Some(LegacyPosition {
638                 horizontal: ref x,
639                 vertical: ref y,
640             }), None) => {
641                 use values::computed::Percentage as ComputedPercentage;
642                 use values::specified::transform::OriginComponent;
643 
644                 // `50% 0%` is the default value for line direction.
645                 // These percentage values can also be keywords.
646                 let x = match *x {
647                     OriginComponent::Center => true,
648                     OriginComponent::Length(LengthOrPercentage::Percentage(ComputedPercentage(val))) => {
649                         val == 0.5
650                     },
651                     _ => false,
652                 };
653                 let y = match *y {
654                     OriginComponent::Side(Y::Top) => true,
655                     OriginComponent::Length(LengthOrPercentage::Percentage(ComputedPercentage(val))) => {
656                         val == 0.0
657                     },
658                     _ => false,
659                 };
660                 x && y
661             },
662             _ => false,
663         }
664     }
665 
to_css<W>( &self, dest: &mut CssWriter<W>, compat_mode: CompatMode, ) -> fmt::Result where W: Write,666     fn to_css<W>(
667         &self,
668         dest: &mut CssWriter<W>,
669         compat_mode: CompatMode,
670     ) -> fmt::Result
671     where
672         W: Write,
673     {
674         match *self {
675             LineDirection::Angle(angle) => {
676                 angle.to_css(dest)
677             },
678             LineDirection::Horizontal(x) => {
679                 if compat_mode == CompatMode::Modern {
680                     dest.write_str("to ")?;
681                 }
682                 x.to_css(dest)
683             },
684             LineDirection::Vertical(y) => {
685                 if compat_mode == CompatMode::Modern {
686                     dest.write_str("to ")?;
687                 }
688                 y.to_css(dest)
689             },
690             LineDirection::Corner(x, y) => {
691                 if compat_mode == CompatMode::Modern {
692                     dest.write_str("to ")?;
693                 }
694                 x.to_css(dest)?;
695                 dest.write_str(" ")?;
696                 y.to_css(dest)
697             },
698             #[cfg(feature = "gecko")]
699             LineDirection::MozPosition(ref position, ref angle) => {
700                 let mut need_space = false;
701                 if let Some(ref position) = *position {
702                     position.to_css(dest)?;
703                     need_space = true;
704                 }
705                 if let Some(ref angle) = *angle {
706                     if need_space {
707                         dest.write_str(" ")?;
708                     }
709                     angle.to_css(dest)?;
710                 }
711                 Ok(())
712             },
713         }
714     }
715 }
716 
717 impl LineDirection {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, compat_mode: &mut CompatMode, ) -> Result<Self, ParseError<'i>>718     fn parse<'i, 't>(
719         context: &ParserContext,
720         input: &mut Parser<'i, 't>,
721         compat_mode: &mut CompatMode,
722     ) -> Result<Self, ParseError<'i>> {
723         let mut _angle = if *compat_mode == CompatMode::Moz {
724             input.try(|i| Angle::parse(context, i)).ok()
725         } else {
726             // Gradients allow unitless zero angles as an exception, see:
727             // https://github.com/w3c/csswg-drafts/issues/1162
728             if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
729                 return Ok(LineDirection::Angle(angle));
730             }
731             None
732         };
733 
734         input.try(|i| {
735             let to_ident = i.try(|i| i.expect_ident_matching("to"));
736             match *compat_mode {
737                 // `to` keyword is mandatory in modern syntax.
738                 CompatMode::Modern => to_ident?,
739                 // Fall back to Modern compatibility mode in case there is a `to` keyword.
740                 // According to Gecko, `-moz-linear-gradient(to ...)` should serialize like
741                 // `linear-gradient(to ...)`.
742                 CompatMode::Moz if to_ident.is_ok() => *compat_mode = CompatMode::Modern,
743                 // There is no `to` keyword in webkit prefixed syntax. If it's consumed,
744                 // parsing should throw an error.
745                 CompatMode::WebKit if to_ident.is_ok() => {
746                     return Err(i.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("to".into())))
747                 },
748                 _ => {},
749             }
750 
751             #[cfg(feature = "gecko")]
752             {
753                 // `-moz-` prefixed linear gradient can be both Angle and Position.
754                 if *compat_mode == CompatMode::Moz {
755                     let position = i.try(|i| LegacyPosition::parse(context, i)).ok();
756                     if _angle.is_none() {
757                         _angle = i.try(|i| Angle::parse(context, i)).ok();
758                     };
759 
760                     if _angle.is_none() && position.is_none() {
761                         return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError));
762                     }
763                     return Ok(LineDirection::MozPosition(position, _angle));
764                 }
765             }
766 
767             if let Ok(x) = i.try(X::parse) {
768                 if let Ok(y) = i.try(Y::parse) {
769                     return Ok(LineDirection::Corner(x, y));
770                 }
771                 return Ok(LineDirection::Horizontal(x));
772             }
773             let y = Y::parse(i)?;
774             if let Ok(x) = i.try(X::parse) {
775                 return Ok(LineDirection::Corner(x, y));
776             }
777             Ok(LineDirection::Vertical(y))
778         })
779     }
780 }
781 
782 #[cfg(feature = "gecko")]
783 impl ToComputedValue for GradientPosition {
784     type ComputedValue = ComputedPosition;
785 
to_computed_value(&self, context: &Context) -> ComputedPosition786     fn to_computed_value(&self, context: &Context) -> ComputedPosition {
787         match *self {
788             GradientPosition::Modern(ref pos) => pos.to_computed_value(context),
789             GradientPosition::Legacy(ref pos) => pos.to_computed_value(context),
790         }
791     }
792 
from_computed_value(computed: &ComputedPosition) -> Self793     fn from_computed_value(computed: &ComputedPosition) -> Self {
794         GradientPosition::Modern(ToComputedValue::from_computed_value(computed))
795     }
796 }
797 
798 impl EndingShape {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, compat_mode: CompatMode, ) -> Result<Self, ParseError<'i>>799     fn parse<'i, 't>(
800         context: &ParserContext,
801         input: &mut Parser<'i, 't>,
802         compat_mode: CompatMode,
803     ) -> Result<Self, ParseError<'i>> {
804         if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
805             if input.try(|i| i.expect_ident_matching("circle")).is_ok() {
806                 return Ok(GenericEndingShape::Circle(Circle::Extent(extent)));
807             }
808             let _ = input.try(|i| i.expect_ident_matching("ellipse"));
809             return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(extent)));
810         }
811         if input.try(|i| i.expect_ident_matching("circle")).is_ok() {
812             if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
813                 return Ok(GenericEndingShape::Circle(Circle::Extent(extent)));
814             }
815             if compat_mode == CompatMode::Modern {
816                 if let Ok(length) = input.try(|i| Length::parse(context, i)) {
817                     return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
818                 }
819             }
820             return Ok(GenericEndingShape::Circle(Circle::Extent(ShapeExtent::FarthestCorner)));
821         }
822         if input.try(|i| i.expect_ident_matching("ellipse")).is_ok() {
823             if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
824                 return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(extent)));
825             }
826             if compat_mode == CompatMode::Modern {
827                 let pair: Result<_, ParseError> = input.try(|i| {
828                     let x = LengthOrPercentage::parse(context, i)?;
829                     let y = LengthOrPercentage::parse(context, i)?;
830                     Ok((x, y))
831                 });
832                 if let Ok((x, y)) = pair {
833                     return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x, y)));
834                 }
835             }
836             return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)));
837         }
838         // -moz- prefixed radial gradient doesn't allow EndingShape's Length or LengthOrPercentage
839         // to come before shape keyword. Otherwise it conflicts with <position>.
840         if compat_mode != CompatMode::Moz {
841             if let Ok(length) = input.try(|i| Length::parse(context, i)) {
842                 if let Ok(y) = input.try(|i| LengthOrPercentage::parse(context, i)) {
843                     if compat_mode == CompatMode::Modern {
844                         let _ = input.try(|i| i.expect_ident_matching("ellipse"));
845                     }
846                     return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
847                 }
848                 if compat_mode == CompatMode::Modern {
849                     let y = input.try(|i| {
850                         i.expect_ident_matching("ellipse")?;
851                         LengthOrPercentage::parse(context, i)
852                     });
853                     if let Ok(y) = y {
854                         return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
855                     }
856                     let _ = input.try(|i| i.expect_ident_matching("circle"));
857                 }
858 
859                 return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
860             }
861         }
862         input.try(|i| {
863             let x = Percentage::parse(context, i)?;
864             let y = if let Ok(y) = i.try(|i| LengthOrPercentage::parse(context, i)) {
865                 if compat_mode == CompatMode::Modern {
866                     let _ = i.try(|i| i.expect_ident_matching("ellipse"));
867                 }
868                 y
869             } else {
870                 if compat_mode == CompatMode::Modern {
871                     i.expect_ident_matching("ellipse")?;
872                 }
873                 LengthOrPercentage::parse(context, i)?
874             };
875             Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x.into(), y)))
876         })
877     }
878 }
879 
880 impl ShapeExtent {
parse_with_compat_mode<'i, 't>( input: &mut Parser<'i, 't>, compat_mode: CompatMode, ) -> Result<Self, ParseError<'i>>881     fn parse_with_compat_mode<'i, 't>(
882         input: &mut Parser<'i, 't>,
883         compat_mode: CompatMode,
884     ) -> Result<Self, ParseError<'i>> {
885         match Self::parse(input)? {
886             ShapeExtent::Contain | ShapeExtent::Cover if compat_mode == CompatMode::Modern => {
887                 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
888             },
889             ShapeExtent::Contain => Ok(ShapeExtent::ClosestSide),
890             ShapeExtent::Cover => Ok(ShapeExtent::FarthestCorner),
891             keyword => Ok(keyword),
892         }
893     }
894 }
895 
896 impl GradientItem {
parse_comma_separated<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Vec<Self>, ParseError<'i>>897     fn parse_comma_separated<'i, 't>(
898         context: &ParserContext,
899         input: &mut Parser<'i, 't>,
900     ) -> Result<Vec<Self>, ParseError<'i>> {
901         let mut seen_stop = false;
902         let items = input.parse_comma_separated(|input| {
903             if seen_stop {
904                 if let Ok(hint) = input.try(|i| LengthOrPercentage::parse(context, i)) {
905                     seen_stop = false;
906                     return Ok(GenericGradientItem::InterpolationHint(hint));
907                 }
908             }
909             seen_stop = true;
910             ColorStop::parse(context, input).map(GenericGradientItem::ColorStop)
911         })?;
912         if !seen_stop || items.len() < 2 {
913             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
914         }
915         Ok(items)
916     }
917 }
918 
919 impl Parse for ColorStop {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>920     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
921                      -> Result<Self, ParseError<'i>> {
922         Ok(ColorStop {
923             color: RGBAColor::parse(context, input)?,
924             position: input.try(|i| LengthOrPercentage::parse(context, i)).ok(),
925         })
926     }
927 }
928 
929 impl Parse for PaintWorklet {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>930     fn parse<'i, 't>(
931         _context: &ParserContext,
932         input: &mut Parser<'i, 't>,
933     ) -> Result<Self, ParseError<'i>> {
934         input.expect_function_matching("paint")?;
935         input.parse_nested_block(|input| {
936             let name = Atom::from(&**input.expect_ident()?);
937             let arguments = input.try(|input| {
938                 input.expect_comma()?;
939                 input.parse_comma_separated(|input| SpecifiedValue::parse(input))
940             }).unwrap_or(vec![]);
941             Ok(PaintWorklet { name, arguments })
942         })
943     }
944 }
945 
946 impl Parse for MozImageRect {
parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>947     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
948         input.try(|i| i.expect_function_matching("-moz-image-rect"))?;
949         input.parse_nested_block(|i| {
950             let string = i.expect_url_or_string()?;
951             let url = SpecifiedImageUrl::parse_from_string(string.as_ref().to_owned(), context)?;
952             i.expect_comma()?;
953             let top = NumberOrPercentage::parse_non_negative(context, i)?;
954             i.expect_comma()?;
955             let right = NumberOrPercentage::parse_non_negative(context, i)?;
956             i.expect_comma()?;
957             let bottom = NumberOrPercentage::parse_non_negative(context, i)?;
958             i.expect_comma()?;
959             let left = NumberOrPercentage::parse_non_negative(context, i)?;
960             Ok(MozImageRect { url, top, right, bottom, left })
961         })
962     }
963 }
964