Bug 1848170: Use fully-computed piecewise linear function for both computed and specified value. r=emilio

Later spec change specifies that the serialized value always has one input and
one output entries for each linear stop entry, so specified and computed values
no longer have any difference.

Get rid of prefs for WPT too, since they're default-enabled on on channels.

Differential Revision: https://phabricator.services.mozilla.com/D186021
This commit is contained in:
David Shin 2023-08-15 13:26:25 +00:00
parent 833ce835ea
commit 19eba0b7a1
5 changed files with 188 additions and 224 deletions

View File

@ -24,6 +24,7 @@ type ValueType = CSSFloat;
PartialEq,
SpecifiedValueInfo,
ToResolvedValue,
ToShmem,
Serialize,
Deserialize,
)]
@ -54,6 +55,7 @@ impl ToCss for PiecewiseLinearFunctionEntry {
SpecifiedValueInfo,
ToResolvedValue,
ToCss,
ToShmem,
Serialize,
Deserialize,
)]
@ -61,7 +63,9 @@ impl ToCss for PiecewiseLinearFunctionEntry {
#[css(comma)]
pub struct PiecewiseLinearFunction {
#[css(iterable)]
entries: crate::OwnedSlice<PiecewiseLinearFunctionEntry>,
#[ignore_malloc_size_of = "Arc"]
#[shmem(field_bound)]
entries: crate::ArcSlice<PiecewiseLinearFunctionEntry>,
}
/// Parameters to define one linear stop.
@ -133,18 +137,6 @@ impl PiecewiseLinearFunction {
unreachable!("Input is supposed to be within the entries' min & max!");
}
/// Create the piecewise linear function from an iterator that generates the parameter tuple.
pub fn from_iter<Iter>(iter: Iter) -> Self
where
Iter: Iterator<Item = PiecewiseLinearFunctionBuildParameters> + ExactSizeIterator,
{
let mut builder = PiecewiseLinearFunctionBuilder::with_capacity(iter.len());
for (y, x_start) in iter {
builder = builder.push(y, x_start);
}
builder.build()
}
#[allow(missing_docs)]
pub fn iter(&self) -> Iter<PiecewiseLinearFunctionEntry> {
self.entries.iter()
@ -167,11 +159,6 @@ pub struct PiecewiseLinearFunctionBuilder {
}
impl PiecewiseLinearFunctionBuilder {
#[allow(missing_docs)]
pub fn new() -> Self {
PiecewiseLinearFunctionBuilder::default()
}
/// Create a builder for a known amount of linear stop entries.
pub fn with_capacity(len: usize) -> Self {
PiecewiseLinearFunctionBuilder {
@ -208,9 +195,8 @@ impl PiecewiseLinearFunctionBuilder {
/// the x value is calculated later. If the end x value is specified, a flat segment
/// is generated. If start x value is not specified but end x is, it is treated as
/// start x.
pub fn push(mut self, y: CSSFloat, x_start: Option<CSSFloat>) -> Self {
self.create_entry(y, x_start);
self
pub fn push(&mut self, y: CSSFloat, x_start: Option<CSSFloat>) {
self.create_entry(y, x_start)
}
/// Finish building the piecewise linear function by resolving all undefined x values,
@ -222,10 +208,10 @@ impl PiecewiseLinearFunctionBuilder {
if self.entries.len() == 1 {
// Don't bother resolving anything.
return PiecewiseLinearFunction {
entries: crate::OwnedSlice::from_slice(&[PiecewiseLinearFunctionEntry {
entries: crate::ArcSlice::from_iter(std::iter::once(PiecewiseLinearFunctionEntry {
x: 0.,
y: self.entries[0].y,
}]),
})),
};
}
// Guaranteed at least two elements.
@ -287,7 +273,7 @@ impl PiecewiseLinearFunctionBuilder {
"Should've mapped one-to-one!"
);
PiecewiseLinearFunction {
entries: result.into(),
entries: crate::ArcSlice::from_iter(result.into_iter()),
}
}
}

View File

@ -4,7 +4,7 @@
//! Specified types for CSS Easing functions.
use crate::parser::{Parse, ParserContext};
use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuildParameters};
use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuilder};
use crate::values::computed::easing::TimingFunction as ComputedTimingFunction;
use crate::values::computed::{Context, ToComputedValue};
use crate::values::generics::easing::TimingFunction as GenericTimingFunction;
@ -12,35 +12,10 @@ use crate::values::generics::easing::{StepPosition, TimingKeyword};
use crate::values::specified::{Integer, Number, Percentage};
use cssparser::{Delimiter, Parser, Token};
use selectors::parser::SelectorParseErrorKind;
use std::iter::FromIterator;
use style_traits::{ParseError, StyleParseErrorKind};
/// An entry for linear easing function.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub struct LinearStop {
/// Output of the function at the given point.
pub output: Number,
/// Playback progress at which this output is given.
#[css(skip_if = "Option::is_none")]
pub input: Option<Percentage>,
}
/// A list of specified linear stops.
#[derive(Clone, Default, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
#[css(comma)]
pub struct LinearStops {
#[css(iterable)]
entries: crate::OwnedSlice<LinearStop>,
}
impl LinearStops {
fn new(list: crate::OwnedSlice<LinearStop>) -> Self {
LinearStops { entries: list }
}
}
/// A specified timing function.
pub type TimingFunction = GenericTimingFunction<Integer, Number, LinearStops>;
pub type TimingFunction = GenericTimingFunction<Integer, Number, PiecewiseLinearFunction>;
#[cfg(feature = "gecko")]
fn linear_timing_function_enabled() -> bool {
@ -136,10 +111,12 @@ impl TimingFunction {
if !linear_timing_function_enabled() {
return Err(input.new_custom_error(StyleParseErrorKind::ExperimentalProperty));
}
let mut result = vec![];
let mut builder = PiecewiseLinearFunctionBuilder::default();
let mut num_specified_stops = 0;
// Closely follows `parse_comma_separated`, but can generate multiple entries for one comma-separated entry.
loop {
input.parse_until_before(Delimiter::Comma, |i| {
let builder = &mut builder;
let mut input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
let mut input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
@ -149,19 +126,14 @@ impl TimingFunction {
input_start = i.try_parse(|i| Percentage::parse(context, i)).ok();
input_end = i.try_parse(|i| Percentage::parse(context, i)).ok();
}
result.push(LinearStop {
output,
input: input_start.into(),
});
builder.push(output.into(), input_start.map(|v| v.get()).into());
num_specified_stops += 1;
if input_end.is_some() {
debug_assert!(
input_start.is_some(),
"Input end valid but not input start?"
);
result.push(LinearStop {
output,
input: input_end.into(),
});
builder.push(output.into(), input_end.map(|v| v.get()).into());
}
Ok(())
@ -173,22 +145,13 @@ impl TimingFunction {
Ok(_) => unreachable!(),
}
}
if result.len() < 2 {
// By spec, specifying only a single stop makes the function invalid, even if that single entry may generate
// two entries.
if num_specified_stops < 2 {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(GenericTimingFunction::LinearFunction(LinearStops::new(
crate::OwnedSlice::from(result),
)))
}
}
impl LinearStop {
/// Convert this type to entries that can be used to build PiecewiseLinearFunction.
pub fn to_piecewise_linear_build_parameters(
x: &LinearStop,
) -> PiecewiseLinearFunctionBuildParameters {
(x.output.get(), x.input.map(|x| x.get()))
Ok(GenericTimingFunction::LinearFunction(builder.build()))
}
}
@ -211,13 +174,8 @@ impl TimingFunction {
}
},
GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
GenericTimingFunction::LinearFunction(steps) => {
GenericTimingFunction::LinearFunction(PiecewiseLinearFunction::from_iter(
steps
.entries
.iter()
.map(|e| LinearStop::to_piecewise_linear_build_parameters(e)),
))
GenericTimingFunction::LinearFunction(function) => {
GenericTimingFunction::LinearFunction(function.clone())
},
}
}
@ -240,12 +198,7 @@ impl ToComputedValue for TimingFunction {
},
ComputedTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword),
ComputedTimingFunction::LinearFunction(function) => {
GenericTimingFunction::LinearFunction(LinearStops {
entries: crate::OwnedSlice::from_iter(function.iter().map(|e| LinearStop {
output: Number::new(e.y),
input: Some(Percentage::new(e.x)).into(),
})),
})
GenericTimingFunction::LinearFunction(function.clone())
},
}
}

View File

@ -6,7 +6,7 @@ use euclid::approxeq::ApproxEq;
use style::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuilder};
fn get_linear_keyword_equivalent() -> PiecewiseLinearFunction {
PiecewiseLinearFunctionBuilder::new().build()
PiecewiseLinearFunctionBuilder::default().build()
}
#[test]
fn linear_keyword_equivalent_in_bound() {
@ -24,9 +24,9 @@ fn linear_keyword_equivalent_out_of_bounds() {
}
fn get_const_function() -> PiecewiseLinearFunction {
PiecewiseLinearFunctionBuilder::new()
.push(CONST_VALUE as f32, None)
.build()
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(CONST_VALUE as f32, None);
builder.build()
}
const CONST_VALUE: f32 = 0.2;
@ -48,14 +48,19 @@ fn const_function_out_of_bounds() {
#[test]
fn implied_input_spacing() {
let explicit_spacing = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(1.0, Some(1.0))
.build();
let implied_spacing = PiecewiseLinearFunctionBuilder::new()
.push(0.0, None)
.push(1.0, None)
.build();
let explicit_spacing = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(1.0, Some(1.0));
builder.build()
};
let implied_spacing = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, None);
builder.push(1.0, None);
builder.build()
};
assert!(implied_spacing.at(0.).approx_eq(&explicit_spacing.at(0.)));
assert!(implied_spacing.at(0.5).approx_eq(&explicit_spacing.at(0.5)));
assert!(implied_spacing.at(1.0).approx_eq(&explicit_spacing.at(1.0)));
@ -63,11 +68,13 @@ fn implied_input_spacing() {
#[test]
fn interpolation() {
let interpolate = PiecewiseLinearFunctionBuilder::new()
.push(0.0, None)
.push(0.7, None)
.push(1.0, None)
.build();
let interpolate = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, None);
builder.push(0.7, None);
builder.push(1.0, None);
builder.build()
};
assert!(interpolate.at(0.1).approx_eq(&0.14));
assert!(interpolate.at(0.25).approx_eq(&0.35));
assert!(interpolate.at(0.45).approx_eq(&0.63));
@ -78,16 +85,18 @@ fn interpolation() {
#[test]
fn implied_multiple_input_spacing() {
let multiple_implied = PiecewiseLinearFunctionBuilder::new()
.push(0.0, None)
.push(0.8, None)
.push(0.6, None)
.push(0.4, None)
.push(0.5, Some(0.4))
.push(0.1, None)
.push(0.9, None)
.push(1.0, None)
.build();
let multiple_implied = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, None);
builder.push(0.8, None);
builder.push(0.6, None);
builder.push(0.4, None);
builder.push(0.5, Some(0.4));
builder.push(0.1, None);
builder.push(0.9, None);
builder.push(1.0, None);
builder.build()
};
assert!(multiple_implied.at(0.1).approx_eq(&0.8));
assert!(multiple_implied.at(0.2).approx_eq(&0.6));
assert!(multiple_implied.at(0.3).approx_eq(&0.4));
@ -99,10 +108,12 @@ fn implied_multiple_input_spacing() {
#[test]
fn nonzero_edge_values() {
let nonzero_edges = PiecewiseLinearFunctionBuilder::new()
.push(0.1, Some(0.0))
.push(0.7, Some(1.0))
.build();
let nonzero_edges = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.1, Some(0.0));
builder.push(0.7, Some(1.0));
builder.build()
};
assert!(nonzero_edges.at(0.).approx_eq(&0.1));
assert!(nonzero_edges.at(0.5).approx_eq(&0.4));
assert!(nonzero_edges.at(1.0).approx_eq(&0.7));
@ -111,11 +122,13 @@ fn nonzero_edge_values() {
#[test]
fn out_of_bounds_extrapolate() {
// General case: extrapolate from the edges' slope
let oob_extend = PiecewiseLinearFunctionBuilder::new()
.push(0.0, None)
.push(0.7, None)
.push(1.0, None)
.build();
let oob_extend = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, None);
builder.push(0.7, None);
builder.push(1.0, None);
builder.build()
};
assert!(oob_extend.at(-0.25).approx_eq(&-0.35));
assert!(oob_extend.at(1.25).approx_eq(&1.15));
}
@ -123,25 +136,29 @@ fn out_of_bounds_extrapolate() {
#[test]
fn out_of_bounds_flat() {
// Repeated endpoints: flat extrapolation out-of-bounds
let oob_flat = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.0, Some(0.0))
.push(0.7, None)
.push(1.0, Some(1.0))
.push(1.0, Some(1.0))
.build();
let oob_flat = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.0, Some(0.0));
builder.push(0.7, None);
builder.push(1.0, Some(1.0));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(oob_flat.at(0.0).approx_eq(&oob_flat.at(-0.25)));
assert!(oob_flat.at(1.0).approx_eq(&oob_flat.at(1.25)));
}
#[test]
fn flat_region() {
let flat = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.5, Some(0.25))
.push(0.5, Some(0.7))
.push(1.0, Some(1.0))
.build();
let flat = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.5, Some(0.25));
builder.push(0.5, Some(0.7));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(flat.at(0.125).approx_eq(&0.25));
assert!(flat.at(0.5).approx_eq(&0.5));
assert!(flat.at(0.85).approx_eq(&0.75));
@ -149,12 +166,14 @@ fn flat_region() {
#[test]
fn step() {
let step = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.0, Some(0.5))
.push(1.0, Some(0.5))
.push(1.0, Some(1.0))
.build();
let step = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.0, Some(0.5));
builder.push(1.0, Some(0.5));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(step.at(0.25).approx_eq(&0.0));
// At the discontinuity, take the right hand side value
assert!(step.at(0.5).approx_eq(&1.0));
@ -163,14 +182,16 @@ fn step() {
#[test]
fn step_multiple_conflicting() {
let step = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.0, Some(0.5))
.push(0.75, Some(0.5))
.push(0.75, Some(0.5))
.push(1.0, Some(0.5))
.push(1.0, Some(1.0))
.build();
let step = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.0, Some(0.5));
builder.push(0.75, Some(0.5));
builder.push(0.75, Some(0.5));
builder.push(1.0, Some(0.5));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(step.at(0.25).approx_eq(&0.0));
assert!(step.at(0.5).approx_eq(&1.0));
assert!(step.at(0.75).approx_eq(&1.0));
@ -178,12 +199,14 @@ fn step_multiple_conflicting() {
#[test]
fn always_monotonic() {
let monotonic = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.3, Some(0.5))
.push(0.4, Some(0.4))
.push(1.0, Some(1.0))
.build();
let monotonic = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.3, Some(0.5));
builder.push(0.4, Some(0.4));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(monotonic.at(0.25).approx_eq(&0.15));
// A discontinuity at x = 0.5 from y = 0.3 to 0.4
assert!(monotonic.at(0.5).approx_eq(&0.4));
@ -192,13 +215,15 @@ fn always_monotonic() {
#[test]
fn always_monotonic_flat() {
let monotonic = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.2, Some(0.2))
.push(0.4, Some(0.1))
.push(0.4, Some(0.15))
.push(1.0, Some(1.0))
.build();
let monotonic = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.2, Some(0.2));
builder.push(0.4, Some(0.1));
builder.push(0.4, Some(0.15));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(monotonic.at(0.2).approx_eq(&0.4));
// A discontinuity at x = 0.2 from y = 0.2 to 0.4
assert!(monotonic.at(0.3).approx_eq(&0.475));
@ -206,13 +231,15 @@ fn always_monotonic_flat() {
#[test]
fn always_monotonic_flat_backwards() {
let monotonic = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.2, Some(0.2))
.push(0.3, Some(0.3))
.push(0.3, Some(0.2))
.push(1.0, Some(1.0))
.build();
let monotonic = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.2, Some(0.2));
builder.push(0.3, Some(0.3));
builder.push(0.3, Some(0.2));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(monotonic.at(0.2).approx_eq(&0.2));
assert!(monotonic.at(0.3).approx_eq(&0.3));
assert!(monotonic.at(0.4).approx_eq(&0.4));
@ -220,10 +247,12 @@ fn always_monotonic_flat_backwards() {
#[test]
fn input_out_of_bounds() {
let oob = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(-0.5))
.push(1.0, Some(1.5))
.build();
let oob = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(-0.5));
builder.push(1.0, Some(1.5));
builder.build()
};
assert!(oob.at(-0.5).approx_eq(&0.0));
assert!(oob.at(0.0).approx_eq(&0.25));
assert!(oob.at(0.5).approx_eq(&0.5));
@ -233,16 +262,20 @@ fn input_out_of_bounds() {
#[test]
fn invalid_builder_input() {
let built_from_invalid = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(f32::NEG_INFINITY))
.push(0.7, Some(f32::NAN))
.push(1.0, Some(f32::INFINITY))
.build();
let equivalent = PiecewiseLinearFunctionBuilder::new()
.push(0.0, None)
.push(0.7, None)
.push(1.0, None)
.build();
let built_from_invalid = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(f32::NEG_INFINITY));
builder.push(0.7, Some(f32::NAN));
builder.push(1.0, Some(f32::INFINITY));
builder.build()
};
let equivalent = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, None);
builder.push(0.7, None);
builder.push(1.0, None);
builder.build()
};
assert!(built_from_invalid.at(0.0).approx_eq(&equivalent.at(0.0)));
assert!(built_from_invalid.at(0.25).approx_eq(&equivalent.at(0.25)));
@ -253,10 +286,12 @@ fn invalid_builder_input() {
#[test]
fn input_domain_not_complete() {
let not_covered = PiecewiseLinearFunctionBuilder::new()
.push(0.2, Some(0.2))
.push(0.8, Some(0.8))
.build();
let not_covered = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.2, Some(0.2));
builder.push(0.8, Some(0.8));
builder.build()
};
assert!(not_covered.at(0.0).approx_eq(&0.0));
assert!(not_covered.at(0.5).approx_eq(&0.5));
assert!(not_covered.at(1.0).approx_eq(&1.0));
@ -264,22 +299,26 @@ fn input_domain_not_complete() {
#[test]
fn input_second_negative() {
let function = PiecewiseLinearFunctionBuilder::new()
.push(0.0, None)
.push(0.0, Some(-0.1))
.push(0.3, Some(-0.05))
.push(0.5, None)
.push(0.2, Some(0.6))
.push(1.0, None)
.build();
let equivalent = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(0.0, Some(0.0))
.push(0.3, Some(0.0))
.push(0.5, Some(0.3))
.push(0.2, Some(0.6))
.push(1.0, Some(1.0))
.build();
let function = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, None);
builder.push(0.0, Some(-0.1));
builder.push(0.3, Some(-0.05));
builder.push(0.5, None);
builder.push(0.2, Some(0.6));
builder.push(1.0, None);
builder.build()
};
let equivalent = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(0.0, Some(0.0));
builder.push(0.3, Some(0.0));
builder.push(0.5, Some(0.3));
builder.push(0.2, Some(0.6));
builder.push(1.0, Some(1.0));
builder.build()
};
assert!(function.at(-0.1).approx_eq(&equivalent.at(-0.1)));
assert!(function.at(0.0).approx_eq(&equivalent.at(0.0)));
assert!(function.at(0.3).approx_eq(&equivalent.at(0.3)));
@ -289,11 +328,13 @@ fn input_second_negative() {
#[test]
fn input_second_last_above_1() {
let function = PiecewiseLinearFunctionBuilder::new()
.push(0.0, Some(0.0))
.push(1.0, Some(2.0))
.push(1.0, None)
.build();
let function = {
let mut builder = PiecewiseLinearFunctionBuilder::default();
builder.push(0.0, Some(0.0));
builder.push(1.0, Some(2.0));
builder.push(1.0, None);
builder.build()
};
assert!(function.at(-0.5).approx_eq(&-0.25));
assert!(function.at(0.0).approx_eq(&0.0));
assert!(function.at(0.5).approx_eq(&0.25));

View File

@ -1 +0,0 @@
prefs: [layout.css.linear-easing-function.enabled:true]

View File

@ -1,15 +0,0 @@
prefs: [layout.css.linear-easing-function.enabled:true]
[linear-timing-functions-syntax.tentative.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[e.style['animation-timing-function'\] = "linear(0, 0.5 25% 75%, 1 100% 100%)" should set the property value]
expected: FAIL
[e.style['animation-timing-function'\] = "linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 1.004, 0.998, 1 100% 100%)" should set the property value]
expected: FAIL
[e.style['animation-timing-function'\] = "linear(0 0% 100%)" should not set the property value]
expected: FAIL
[e.style['animation-timing-function'\] = "linear(0% 100% 0)" should not set the property value]
expected: FAIL