Auto merge of #17783 - BorisChiou:stylo/animation/restrictions, r=nox

stylo: Bug 1374233 - Clamp interpolated values for properties which need to be restricted

Some properties only accept non-negative values, or values greater than or equal to one. It is possible to produce an negative interpolated values while using negative timing functions, so we have to apply a restriction to these values to avoid getting invalid values.

For example, line-height must be non-negative, but the output progress of some timing functions (e,g. cubic-bezier(0.25, -2, 0.75, 1)) may be a negative value, so the interpolated result of line-height is also negative.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix Bug 1374233.
- [X] These changes do not require tests because we have tests in Gecko side already.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17783)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-08-07 11:05:17 -05:00 committed by GitHub
commit 016ea11cba
56 changed files with 1039 additions and 371 deletions

View file

@ -4,11 +4,10 @@
//! Specified types for CSS values related to borders.
use app_units::Au;
use cssparser::Parser;
use parser::{Parse, ParserContext};
use style_traits::ParseError;
use values::computed::{Context, ToComputedValue};
use values::computed::{Context, NonNegativeAu, ToComputedValue};
use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
use values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth;
use values::generics::border::BorderImageSlice as GenericBorderImageSlice;
@ -72,7 +71,7 @@ impl Parse for BorderSideWidth {
}
impl ToComputedValue for BorderSideWidth {
type ComputedValue = Au;
type ComputedValue = NonNegativeAu;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
@ -84,12 +83,12 @@ impl ToComputedValue for BorderSideWidth {
BorderSideWidth::Medium => Length::from_px(3.).to_computed_value(context),
BorderSideWidth::Thick => Length::from_px(5.).to_computed_value(context),
BorderSideWidth::Length(ref length) => length.to_computed_value(context)
}
}.into()
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
BorderSideWidth::Length(ToComputedValue::from_computed_value(computed))
BorderSideWidth::Length(ToComputedValue::from_computed_value(&computed.0))
}
}

View file

@ -9,28 +9,29 @@ use parser::{Parse, ParserContext};
use style_traits::{ParseError, StyleParseError};
#[cfg(not(feature = "gecko"))]
use values::Impossible;
use values::computed::{Context, Number as ComputedNumber, ToComputedValue};
use values::computed::{Context, NonNegativeNumber as ComputedNonNegativeNumber, ToComputedValue};
use values::computed::effects::BoxShadow as ComputedBoxShadow;
use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
use values::generics::NonNegative;
use values::generics::effects::BoxShadow as GenericBoxShadow;
use values::generics::effects::Filter as GenericFilter;
use values::generics::effects::SimpleShadow as GenericSimpleShadow;
use values::specified::{Angle, NumberOrPercentage};
use values::specified::color::Color;
use values::specified::length::Length;
use values::specified::length::{Length, NonNegativeLength};
#[cfg(feature = "gecko")]
use values::specified::url::SpecifiedUrl;
/// A specified value for a single shadow of the `box-shadow` property.
pub type BoxShadow = GenericBoxShadow<Option<Color>, Length, Option<Length>>;
pub type BoxShadow = GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>;
/// A specified value for a single `filter`.
#[cfg(feature = "gecko")]
pub type Filter = GenericFilter<Angle, Factor, Length, SimpleShadow>;
pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, SimpleShadow>;
/// A specified value for a single `filter`.
#[cfg(not(feature = "gecko"))]
pub type Filter = GenericFilter<Angle, Factor, Length, Impossible>;
pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, Impossible>;
/// A value for the `<factor>` parts in `Filter`.
#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
@ -48,25 +49,25 @@ impl Parse for Factor {
}
impl ToComputedValue for Factor {
type ComputedValue = ComputedNumber;
type ComputedValue = ComputedNonNegativeNumber;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
use values::computed::NumberOrPercentage;
match self.0.to_computed_value(context) {
NumberOrPercentage::Number(n) => n,
NumberOrPercentage::Percentage(p) => p.0,
NumberOrPercentage::Number(n) => n.into(),
NumberOrPercentage::Percentage(p) => p.0.into(),
}
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
Factor(NumberOrPercentage::Number(ToComputedValue::from_computed_value(computed)))
Factor(NumberOrPercentage::Number(ToComputedValue::from_computed_value(&computed.0)))
}
}
/// A specified value for the `drop-shadow()` filter.
pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<Length>>;
pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>;
impl Parse for BoxShadow {
fn parse<'i, 't>(
@ -91,7 +92,7 @@ impl Parse for BoxShadow {
let (blur, spread) = match i.try::<_, _, ParseError>(|i| Length::parse_non_negative(context, i)) {
Ok(blur) => {
let spread = i.try(|i| Length::parse(context, i)).ok();
(Some(blur), spread)
(Some(blur.into()), spread)
},
Err(_) => (None, None),
};
@ -162,7 +163,7 @@ impl Parse for Filter {
let function = input.expect_function()?.clone();
input.parse_nested_block(|i| {
try_match_ident_ignore_ascii_case! { function,
"blur" => Ok(GenericFilter::Blur(Length::parse_non_negative(context, i)?)),
"blur" => Ok(GenericFilter::Blur((Length::parse_non_negative(context, i)?).into())),
"brightness" => Ok(GenericFilter::Brightness(Factor::parse(context, i)?)),
"contrast" => Ok(GenericFilter::Contrast(Factor::parse(context, i)?)),
"grayscale" => Ok(GenericFilter::Grayscale(Factor::parse(context, i)?)),
@ -192,7 +193,7 @@ impl Parse for SimpleShadow {
color: color,
horizontal: horizontal,
vertical: vertical,
blur: blur,
blur: blur.map(NonNegative::<Length>),
})
}
}
@ -208,7 +209,7 @@ impl ToComputedValue for SimpleShadow {
horizontal: self.horizontal.to_computed_value(context),
vertical: self.vertical.to_computed_value(context),
blur:
self.blur.as_ref().unwrap_or(&Length::zero()).to_computed_value(context),
self.blur.as_ref().unwrap_or(&NonNegativeLength::zero()).to_computed_value(context),
}
}

View file

@ -21,6 +21,8 @@ use super::{AllowQuirks, Number, ToComputedValue};
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, None_, Normal};
use values::ExtremumLength;
use values::computed::{self, Context};
use values::generics::NonNegative;
use values::specified::NonNegativeNumber;
use values::specified::calc::CalcNode;
pub use values::specified::calc::CalcLengthOrPercentage;
@ -92,8 +94,8 @@ impl FontBaseSize {
pub fn resolve(&self, context: &Context) -> Au {
match *self {
FontBaseSize::Custom(size) => size,
FontBaseSize::CurrentStyle => context.style().get_font().clone_font_size(),
FontBaseSize::InheritedStyle => context.style().get_parent_font().clone_font_size(),
FontBaseSize::CurrentStyle => context.style().get_font().clone_font_size().0,
FontBaseSize::InheritedStyle => context.style().get_parent_font().clone_font_size().0,
}
}
}
@ -703,6 +705,57 @@ impl<T: Parse> Either<Length, T> {
}
}
/// A wrapper of Length, whose value must be >= 0.
pub type NonNegativeLength = NonNegative<Length>;
impl From<NoCalcLength> for NonNegativeLength {
#[inline]
fn from(len: NoCalcLength) -> Self {
NonNegative::<Length>(Length::NoCalc(len))
}
}
impl From<Length> for NonNegativeLength {
#[inline]
fn from(len: Length) -> Self {
NonNegative::<Length>(len)
}
}
impl<T: Parse> Parse for Either<NonNegativeLength, T> {
#[inline]
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
if let Ok(v) = input.try(|input| T::parse(context, input)) {
return Ok(Either::Second(v));
}
Length::parse_internal(context, input, AllowedLengthType::NonNegative, AllowQuirks::No)
.map(NonNegative::<Length>).map(Either::First)
}
}
impl NonNegativeLength {
/// Returns a `zero` length.
#[inline]
pub fn zero() -> Self {
Length::zero().into()
}
/// Get an absolute length from a px value.
#[inline]
pub fn from_px(px_value: CSSFloat) -> Self {
Length::from_px(px_value.max(0.)).into()
}
}
/// Either a NonNegativeLength or the `normal` keyword.
pub type NonNegativeLengthOrNormal = Either<NonNegativeLength, Normal>;
/// Either a NonNegativeLength or the `auto` keyword.
pub type NonNegativeLengthOrAuto = Either<NonNegativeLength, Auto>;
/// Either a NonNegativeLength or a NonNegativeNumber value.
pub type NonNegativeLengthOrNumber = Either<NonNegativeLength, NonNegativeNumber>;
/// A percentage value.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@ -1185,6 +1238,41 @@ impl Parse for LengthOrPercentageOrNone {
}
}
/// A wrapper of LengthOrPercentage, whose value must be >= 0.
pub type NonNegativeLengthOrPercentage = NonNegative<LengthOrPercentage>;
impl From<NoCalcLength> for NonNegativeLengthOrPercentage {
#[inline]
fn from(len: NoCalcLength) -> Self {
NonNegative::<LengthOrPercentage>(LengthOrPercentage::from(len))
}
}
impl Parse for NonNegativeLengthOrPercentage {
#[inline]
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
LengthOrPercentage::parse_non_negative(context, input).map(NonNegative::<LengthOrPercentage>)
}
}
impl NonNegativeLengthOrPercentage {
#[inline]
/// Returns a `zero` length.
pub fn zero() -> Self {
NonNegative::<LengthOrPercentage>(LengthOrPercentage::zero())
}
/// Parses a length or a percentage, allowing the unitless length quirk.
/// https://quirks.spec.whatwg.org/#the-unitless-length-quirk
#[inline]
pub fn parse_quirky<'i, 't>(context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks) -> Result<Self, ParseError<'i>> {
LengthOrPercentage::parse_non_negative_quirky(context, input, allow_quirks)
.map(NonNegative::<LengthOrPercentage>)
}
}
/// Either a `<length>` or the `none` keyword.
pub type LengthOrNone = Either<Length, None_>;

View file

@ -18,6 +18,7 @@ use style_traits::{ToCss, ParseError, StyleParseError};
use style_traits::values::specified::AllowedNumericType;
use super::{Auto, CSSFloat, CSSInteger, Either, None_};
use super::computed::{self, Context, ToComputedValue};
use super::generics::{GreaterThanOrEqualToOne, NonNegative};
use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
use super::generics::grid::TrackList as GenericTrackList;
use values::computed::ComputedValueAsSpecified;
@ -41,9 +42,10 @@ pub use self::length::{FontRelativeLength, Length, LengthOrNone, LengthOrNumber}
pub use self::length::{LengthOrPercentage, LengthOrPercentageOrAuto};
pub use self::length::{LengthOrPercentageOrNone, MaxLength, MozLength};
pub use self::length::{NoCalcLength, Percentage, ViewportPercentageLength};
pub use self::length::NonNegativeLengthOrPercentage;
pub use self::rect::LengthOrNumberRect;
pub use self::position::{Position, PositionComponent};
pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind, SVGStrokeDashArray};
pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind, SVGStrokeDashArray, SVGWidth};
pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
pub use self::transform::{TimingFunction, TransformOrigin};
pub use super::generics::grid::GridLine;
@ -533,6 +535,33 @@ impl ToCss for Number {
}
}
/// A Number which is >= 0.0.
pub type NonNegativeNumber = NonNegative<Number>;
impl Parse for NonNegativeNumber {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
.map(NonNegative::<Number>)
}
}
impl NonNegativeNumber {
/// Returns a new non-negative number with the value `val`.
pub fn new(val: CSSFloat) -> Self {
NonNegative::<Number>(Number::new(val.max(0.)))
}
}
/// A Number which is >= 1.0.
pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>;
impl Parse for GreaterThanOrEqualToOneNumber {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
.map(GreaterThanOrEqualToOne::<Number>)
}
}
/// <number> | <percentage>
///
/// Accepts only non-negative numbers.
@ -713,6 +742,19 @@ impl IntegerOrAuto {
}
}
/// A wrapper of Integer, with value >= 1.
pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
impl Parse for PositiveInteger {
#[inline]
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne::<Integer>)
}
}
/// PositiveInteger | auto
pub type PositiveIntegerOrAuto = Either<PositiveInteger, Auto>;
#[allow(missing_docs)]
pub type UrlOrNone = Either<SpecifiedUrl, None_>;
@ -732,19 +774,8 @@ pub type GridTemplateComponent = GenericGridTemplateComponent<LengthOrPercentage
/// <length> | <percentage> | <number>
pub type LengthOrPercentageOrNumber = Either<Number, LengthOrPercentage>;
impl LengthOrPercentageOrNumber {
/// parse a <length-percentage> | <number> enforcing that the contents aren't negative
pub fn parse_non_negative<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<Self, ParseError<'i>> {
// NB: Parse numbers before Lengths so we are consistent about how to
// recognize and serialize "0".
if let Ok(num) = input.try(|i| Number::parse_non_negative(context, i)) {
return Ok(Either::First(num))
}
LengthOrPercentage::parse_non_negative(context, input).map(Either::Second)
}
}
/// NonNegativeLengthOrPercentage | NonNegativeNumber
pub type NonNegativeLengthOrPercentageOrNumber = Either<NonNegativeNumber, NonNegativeLengthOrPercentage>;
#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]

View file

@ -8,7 +8,7 @@ use cssparser::Parser;
use parser::{Parse, ParserContext};
use style_traits::{CommaWithSpace, ParseError, Separator, StyleParseError};
use values::generics::svg as generic;
use values::specified::{LengthOrPercentageOrNumber, Opacity};
use values::specified::{LengthOrPercentageOrNumber, NonNegativeLengthOrPercentageOrNumber, Opacity};
use values::specified::color::RGBAColor;
/// Specified SVG Paint value
@ -54,30 +54,38 @@ impl Parse for SVGLength {
}
}
impl SVGLength {
/// parse a non-negative SVG length
pub fn parse_non_negative<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<Self, ParseError<'i>> {
input.try(|i| LengthOrPercentageOrNumber::parse_non_negative(context, i))
.map(Into::into)
.or_else(|_| parse_context_value(input, generic::SVGLength::ContextValue))
}
}
impl From<LengthOrPercentageOrNumber> for SVGLength {
fn from(length: LengthOrPercentageOrNumber) -> Self {
generic::SVGLength::Length(length)
}
}
/// A non-negative version of SVGLength.
pub type SVGWidth = generic::SVGLength<NonNegativeLengthOrPercentageOrNumber>;
impl Parse for SVGWidth {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<Self, ParseError<'i>> {
input.try(|i| NonNegativeLengthOrPercentageOrNumber::parse(context, i))
.map(Into::into)
.or_else(|_| parse_context_value(input, generic::SVGLength::ContextValue))
}
}
impl From<NonNegativeLengthOrPercentageOrNumber> for SVGWidth {
fn from(length: NonNegativeLengthOrPercentageOrNumber) -> Self {
generic::SVGLength::Length(length)
}
}
/// [ <length> | <percentage> | <number> ]# | context-value
pub type SVGStrokeDashArray = generic::SVGStrokeDashArray<LengthOrPercentageOrNumber>;
pub type SVGStrokeDashArray = generic::SVGStrokeDashArray<NonNegativeLengthOrPercentageOrNumber>;
impl Parse for SVGStrokeDashArray {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<Self, ParseError<'i>> {
if let Ok(values) = input.try(|i| CommaWithSpace::parse(i, |i| {
LengthOrPercentageOrNumber::parse_non_negative(context, i)
NonNegativeLengthOrPercentageOrNumber::parse(context, i)
})) {
Ok(generic::SVGStrokeDashArray::Values(values))
} else if let Ok(_) = input.try(|i| i.expect_ident_matching("none")) {

View file

@ -14,8 +14,9 @@ use values::computed::text::LineHeight as ComputedLineHeight;
use values::generics::text::InitialLetter as GenericInitialLetter;
use values::generics::text::LineHeight as GenericLineHeight;
use values::generics::text::Spacing;
use values::specified::{AllowQuirks, Integer, Number};
use values::specified::{AllowQuirks, Integer, NonNegativeNumber, Number};
use values::specified::length::{FontRelativeLength, Length, LengthOrPercentage, NoCalcLength};
use values::specified::length::NonNegativeLengthOrPercentage;
/// A specified type for the `initial-letter` property.
pub type InitialLetter = GenericInitialLetter<Number, Integer>;
@ -27,7 +28,7 @@ pub type LetterSpacing = Spacing<Length>;
pub type WordSpacing = Spacing<LengthOrPercentage>;
/// A specified value for the `line-height` property.
pub type LineHeight = GenericLineHeight<Number, LengthOrPercentage>;
pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthOrPercentage>;
impl Parse for InitialLetter {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
@ -58,11 +59,11 @@ impl Parse for WordSpacing {
impl Parse for LineHeight {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
if let Ok(number) = input.try(|i| Number::parse_non_negative(context, i)) {
if let Ok(number) = input.try(|i| NonNegativeNumber::parse(context, i)) {
return Ok(GenericLineHeight::Number(number))
}
if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
return Ok(GenericLineHeight::Length(lop))
if let Ok(nlop) = input.try(|i| NonNegativeLengthOrPercentage::parse(context, i)) {
return Ok(GenericLineHeight::Length(nlop))
}
let ident = input.expect_ident()?;
match ident {
@ -94,24 +95,29 @@ impl ToComputedValue for LineHeight {
GenericLineHeight::Number(number) => {
GenericLineHeight::Number(number.to_computed_value(context))
},
GenericLineHeight::Length(LengthOrPercentage::Length(ref length)) => {
GenericLineHeight::Length(context.maybe_zoom_text(length.to_computed_value(context)))
},
GenericLineHeight::Length(LengthOrPercentage::Percentage(p)) => {
let font_relative_length =
Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(p.0)));
GenericLineHeight::Length(font_relative_length.to_computed_value(context))
},
GenericLineHeight::Length(LengthOrPercentage::Calc(ref calc)) => {
let computed_calc = calc.to_computed_value_zoomed(context);
let font_relative_length =
Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(computed_calc.percentage())));
let absolute_length = computed_calc.unclamped_length();
let computed_length = computed_calc.clamping_mode.clamp(
absolute_length + font_relative_length.to_computed_value(context)
);
GenericLineHeight::Length(computed_length)
},
GenericLineHeight::Length(ref non_negative_lop) => {
let result = match non_negative_lop.0 {
LengthOrPercentage::Length(ref length) => {
context.maybe_zoom_text(length.to_computed_value(context).into())
},
LengthOrPercentage::Percentage(ref p) => {
let font_relative_length =
Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(p.0)));
font_relative_length.to_computed_value(context).into()
}
LengthOrPercentage::Calc(ref calc) => {
let computed_calc = calc.to_computed_value_zoomed(context);
let font_relative_length =
Length::NoCalc(NoCalcLength::FontRelative(
FontRelativeLength::Em(computed_calc.percentage())));
let absolute_length = computed_calc.unclamped_length();
computed_calc.clamping_mode.clamp(
absolute_length + font_relative_length.to_computed_value(context)
).into()
}
};
GenericLineHeight::Length(result)
}
}
}
@ -126,12 +132,10 @@ impl ToComputedValue for LineHeight {
GenericLineHeight::MozBlockHeight
},
GenericLineHeight::Number(ref number) => {
GenericLineHeight::Number(Number::from_computed_value(number))
GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
},
GenericLineHeight::Length(ref length) => {
GenericLineHeight::Length(LengthOrPercentage::Length(
NoCalcLength::from_computed_value(length)
))
GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
}
}
}