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