tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

easing.rs (8601B)


      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 https://mozilla.org/MPL/2.0/. */
      4 
      5 //! Specified types for CSS Easing functions.
      6 use crate::parser::{Parse, ParserContext};
      7 use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuilder};
      8 use crate::values::computed::easing::TimingFunction as ComputedTimingFunction;
      9 use crate::values::computed::{Context, ToComputedValue};
     10 use crate::values::generics::easing::TimingFunction as GenericTimingFunction;
     11 use crate::values::generics::easing::{StepPosition, TimingKeyword};
     12 use crate::values::specified::{AnimationName, Integer, Number, Percentage};
     13 use cssparser::{match_ignore_ascii_case, Delimiter, Parser, Token};
     14 use selectors::parser::SelectorParseErrorKind;
     15 use style_traits::{ParseError, StyleParseErrorKind};
     16 
     17 /// A specified timing function.
     18 pub type TimingFunction = GenericTimingFunction<Integer, Number, PiecewiseLinearFunction>;
     19 
     20 impl Parse for TimingFunction {
     21    fn parse<'i, 't>(
     22        context: &ParserContext,
     23        input: &mut Parser<'i, 't>,
     24    ) -> Result<Self, ParseError<'i>> {
     25        if let Ok(keyword) = input.try_parse(TimingKeyword::parse) {
     26            return Ok(GenericTimingFunction::Keyword(keyword));
     27        }
     28        if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
     29            let position = match_ignore_ascii_case! { &ident,
     30                "step-start" => StepPosition::Start,
     31                "step-end" => StepPosition::End,
     32                _ => {
     33                    return Err(input.new_custom_error(
     34                        SelectorParseErrorKind::UnexpectedIdent(ident.clone())
     35                    ));
     36                },
     37            };
     38            return Ok(GenericTimingFunction::Steps(Integer::new(1), position));
     39        }
     40        let location = input.current_source_location();
     41        let function = input.expect_function()?.clone();
     42        input.parse_nested_block(move |i| {
     43            match_ignore_ascii_case! { &function,
     44                "cubic-bezier" => Self::parse_cubic_bezier(context, i),
     45                "steps" => Self::parse_steps(context, i),
     46                "linear" => Self::parse_linear_function(context, i),
     47                _ => Err(location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
     48            }
     49        })
     50    }
     51 }
     52 
     53 impl TimingFunction {
     54    fn parse_cubic_bezier<'i, 't>(
     55        context: &ParserContext,
     56        input: &mut Parser<'i, 't>,
     57    ) -> Result<Self, ParseError<'i>> {
     58        let x1 = Number::parse(context, input)?;
     59        input.expect_comma()?;
     60        let y1 = Number::parse(context, input)?;
     61        input.expect_comma()?;
     62        let x2 = Number::parse(context, input)?;
     63        input.expect_comma()?;
     64        let y2 = Number::parse(context, input)?;
     65 
     66        if x1.get() < 0.0 || x1.get() > 1.0 || x2.get() < 0.0 || x2.get() > 1.0 {
     67            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
     68        }
     69 
     70        Ok(GenericTimingFunction::CubicBezier { x1, y1, x2, y2 })
     71    }
     72 
     73    fn parse_steps<'i, 't>(
     74        context: &ParserContext,
     75        input: &mut Parser<'i, 't>,
     76    ) -> Result<Self, ParseError<'i>> {
     77        let steps = Integer::parse_positive(context, input)?;
     78        let position = input
     79            .try_parse(|i| {
     80                i.expect_comma()?;
     81                StepPosition::parse(context, i)
     82            })
     83            .unwrap_or(StepPosition::End);
     84 
     85        // jump-none accepts a positive integer greater than 1.
     86        // FIXME(emilio): The spec asks us to avoid rejecting it at parse
     87        // time except until computed value time.
     88        //
     89        // It's not totally clear it's worth it though, and no other browser
     90        // does this.
     91        if position == StepPosition::JumpNone && steps.value() <= 1 {
     92            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
     93        }
     94        Ok(GenericTimingFunction::Steps(steps, position))
     95    }
     96 
     97    fn parse_linear_function<'i, 't>(
     98        context: &ParserContext,
     99        input: &mut Parser<'i, 't>,
    100    ) -> Result<Self, ParseError<'i>> {
    101        let mut builder = PiecewiseLinearFunctionBuilder::default();
    102        let mut num_specified_stops = 0;
    103        // Closely follows `parse_comma_separated`, but can generate multiple entries for one comma-separated entry.
    104        loop {
    105            input.parse_until_before(Delimiter::Comma, |i| {
    106                let builder = &mut builder;
    107                let mut input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
    108                let mut input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
    109 
    110                let output = Number::parse(context, i)?;
    111                if input_start.is_none() {
    112                    debug_assert!(input_end.is_none(), "Input end parsed without input start?");
    113                    input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
    114                    input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
    115                }
    116                builder.push(output.into(), input_start.map(|v| v.get()).into());
    117                num_specified_stops += 1;
    118                if input_end.is_some() {
    119                    debug_assert!(
    120                        input_start.is_some(),
    121                        "Input end valid but not input start?"
    122                    );
    123                    builder.push(output.into(), input_end.map(|v| v.get()).into());
    124                }
    125 
    126                Ok(())
    127            })?;
    128 
    129            match input.next() {
    130                Err(_) => break,
    131                Ok(&Token::Comma) => continue,
    132                Ok(_) => unreachable!(),
    133            }
    134        }
    135        // By spec, specifying only a single stop makes the function invalid, even if that single entry may generate
    136        // two entries.
    137        if num_specified_stops < 2 {
    138            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    139        }
    140 
    141        Ok(GenericTimingFunction::LinearFunction(builder.build()))
    142    }
    143 
    144    /// Returns true if the name matches any keyword.
    145    #[inline]
    146    pub fn match_keywords(name: &AnimationName) -> bool {
    147        if let Some(name) = name.as_atom() {
    148            #[cfg(feature = "gecko")]
    149            return name.with_str(|n| TimingKeyword::from_ident(n).is_ok());
    150            #[cfg(feature = "servo")]
    151            return TimingKeyword::from_ident(name).is_ok();
    152        }
    153        false
    154    }
    155 }
    156 
    157 // We need this for converting the specified TimingFunction into computed TimingFunction without
    158 // Context (for some FFIs in glue.rs). In fact, we don't really need Context to get the computed
    159 // value of TimingFunction.
    160 impl TimingFunction {
    161    /// Generate the ComputedTimingFunction without Context.
    162    pub fn to_computed_value_without_context(&self) -> ComputedTimingFunction {
    163        match &self {
    164            GenericTimingFunction::Steps(steps, pos) => {
    165                GenericTimingFunction::Steps(steps.value(), *pos)
    166            },
    167            GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
    168                GenericTimingFunction::CubicBezier {
    169                    x1: x1.get(),
    170                    y1: y1.get(),
    171                    x2: x2.get(),
    172                    y2: y2.get(),
    173                }
    174            },
    175            GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
    176            GenericTimingFunction::LinearFunction(function) => {
    177                GenericTimingFunction::LinearFunction(function.clone())
    178            },
    179        }
    180    }
    181 }
    182 
    183 impl ToComputedValue for TimingFunction {
    184    type ComputedValue = ComputedTimingFunction;
    185    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
    186        self.to_computed_value_without_context()
    187    }
    188 
    189    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
    190        match &computed {
    191            ComputedTimingFunction::Steps(steps, pos) => Self::Steps(Integer::new(*steps), *pos),
    192            ComputedTimingFunction::CubicBezier { x1, y1, x2, y2 } => Self::CubicBezier {
    193                x1: Number::new(*x1),
    194                y1: Number::new(*y1),
    195                x2: Number::new(*x2),
    196                y2: Number::new(*y2),
    197            },
    198            ComputedTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
    199            ComputedTimingFunction::LinearFunction(function) => {
    200                GenericTimingFunction::LinearFunction(function.clone())
    201            },
    202        }
    203    }
    204 }