diff --git a/components/style/animation.rs b/components/style/animation.rs index 311d102729f..057019950a4 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -363,11 +363,39 @@ impl PropertyAnimation { GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => { Bezier::new(x1, y1, x2, y2).solve(time, epsilon) }, - GenericTimingFunction::Steps(steps, StepPosition::Start) => { - (time * (steps as f64)).ceil() / (steps as f64) - }, - GenericTimingFunction::Steps(steps, StepPosition::End) => { - (time * (steps as f64)).floor() / (steps as f64) + GenericTimingFunction::Steps(steps, pos) => { + let mut current_step = (time * (steps as f64)).floor() as i32; + + if pos == StepPosition::Start || + pos == StepPosition::JumpStart || + pos == StepPosition::JumpBoth { + current_step = current_step + 1; + } + + // FIXME: We should update current_step according to the "before flag". + // In order to get the before flag, we have to know the current animation phase + // and whether the iteration is reversed. For now, we skip this calculation. + // (i.e. Treat before_flag is unset,) + // https://drafts.csswg.org/css-easing/#step-timing-function-algo + + if time >= 0.0 && current_step < 0 { + current_step = 0; + } + + let jumps = match pos { + StepPosition::JumpBoth => steps + 1, + StepPosition::JumpNone => steps - 1, + StepPosition::JumpStart | + StepPosition::JumpEnd | + StepPosition::Start | + StepPosition::End => steps, + }; + + if time <= 1.0 && current_step > jumps { + current_step = jumps; + } + + (current_step as f64) / (jumps as f64) }, GenericTimingFunction::Keyword(keyword) => { let (x1, x2, y1, y2) = keyword.to_bezier(); diff --git a/components/style/values/generics/easing.rs b/components/style/values/generics/easing.rs index 018f51bba8f..f4f93a45479 100644 --- a/components/style/values/generics/easing.rs +++ b/components/style/values/generics/easing.rs @@ -6,6 +6,7 @@ //! https://drafts.csswg.org/css-easing/#timing-functions use values::CSSFloat; +use parser::ParserContext; /// A generic easing function. #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)] @@ -23,7 +24,8 @@ pub enum TimingFunction { x2: Number, y2: Number, }, - /// `step-start | step-end | steps(, [ start | end ]?)` + /// `step-start | step-end | steps(, [ ]?)` + /// ` = jump-start | jump-end | jump-none | jump-both | start | end` #[css(comma, function)] #[value_info(other_values = "step-start,step-end")] Steps(Integer, #[css(skip_if = "is_end")] StepPosition), @@ -52,18 +54,37 @@ pub enum TimingKeyword { EaseInOut, } +#[cfg(feature = "gecko")] +fn step_position_jump_enabled(_context: &ParserContext) -> bool { + use gecko_bindings::structs; + unsafe { structs::StaticPrefs_sVarCache_layout_css_step_position_jump_enabled } +} + +#[cfg(feature = "servo")] +fn step_position_jump_enabled(_context: &ParserContext) -> bool { + false +} + #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss)] #[repr(u8)] pub enum StepPosition { + #[parse(condition = "step_position_jump_enabled")] + JumpStart, + #[parse(condition = "step_position_jump_enabled")] + JumpEnd, + #[parse(condition = "step_position_jump_enabled")] + JumpNone, + #[parse(condition = "step_position_jump_enabled")] + JumpBoth, Start, End, } #[inline] fn is_end(position: &StepPosition) -> bool { - *position == StepPosition::End + *position == StepPosition::JumpEnd || *position == StepPosition::End } impl TimingFunction { diff --git a/components/style/values/specified/easing.rs b/components/style/values/specified/easing.rs index 1bc92e0dd48..017643cdab7 100644 --- a/components/style/values/specified/easing.rs +++ b/components/style/values/specified/easing.rs @@ -59,8 +59,18 @@ impl Parse for TimingFunction { let steps = Integer::parse_positive(context, i)?; let position = i.try(|i| { i.expect_comma()?; - StepPosition::parse(i) + StepPosition::parse(context, i) }).unwrap_or(StepPosition::End); + + // jump-none accepts a positive integer greater than 1. + // FIXME(emilio): The spec asks us to avoid rejecting it at parse + // time except until computed value time. + // + // It's not totally clear it's worth it though, and no other browser + // does this. + if position == StepPosition::JumpNone && 2 > steps.value() { + return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } Ok(GenericTimingFunction::Steps(steps, position)) }, _ => Err(()),