script/dom/animations/
animationeffect.rs1use cssparser::{Parser, ParserInput};
6use dom_struct::dom_struct;
7use script_bindings::cell::DomRefCell;
8use script_bindings::codegen::GenericBindings::AnimationEffectBinding::{
9 AnimationEffectMethods, ComputedEffectTiming, EffectTiming, FillMode, OptionalEffectTiming,
10 PlaybackDirection,
11};
12use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
13use script_bindings::error::{Error, Fallible};
14use script_bindings::num::Finite;
15use script_bindings::reflector::Reflector;
16use script_bindings::root::Dom;
17use style::parser::Parse;
18use style::stylesheets::CssRuleType;
19use style::values::generics::easing::TimingKeyword;
20use style::values::specified::TimingFunction;
21use style_traits::{ParsingMode, ToCss};
22
23use crate::css::parser_context_for_document;
24use crate::dom::Window;
25use crate::dom::bindings::codegen::UnionTypes::UnrestrictedDoubleOrString;
26
27#[dom_struct]
29pub(crate) struct AnimationEffect {
30 reflector: Reflector,
31
32 window: Dom<Window>,
34
35 specified_timing_properties: DomRefCell<SpecifiedTimingProperties>,
36}
37
38#[derive(Clone, JSTraceable, MallocSizeOf)]
39struct SpecifiedTimingProperties {
40 start_delay: Finite<f64>,
42
43 end_delay: Finite<f64>,
45
46 fill_mode: FillMode,
48
49 iteration_count: f64,
51
52 iteration_start: Finite<f64>,
54
55 iteration_duration: IterationDurationOrAuto,
57
58 playback_direction: PlaybackDirection,
60
61 #[no_trace]
63 easing_function: TimingFunction,
64}
65
66impl AnimationEffect {
67 pub(crate) fn new_inherited(window: &Window) -> Self {
68 Self {
69 reflector: Reflector::new(),
70 window: Dom::from_ref(window),
71
72 specified_timing_properties: DomRefCell::new(SpecifiedTimingProperties {
76 start_delay: Default::default(),
77 end_delay: Default::default(),
78 fill_mode: FillMode::None,
79 iteration_count: Default::default(),
80 iteration_start: Default::default(),
81 iteration_duration: IterationDurationOrAuto::Auto,
82 playback_direction: PlaybackDirection::Normal,
83 easing_function: TimingFunction::Keyword(TimingKeyword::Linear),
84 }),
85 }
86 }
87
88 pub(crate) fn window(&self) -> &Window {
89 &self.window
90 }
91
92 pub(crate) fn update_the_timing_properties(
94 &self,
95 input: &OptionalEffectTiming,
96 ) -> Fallible<()> {
97 if input
100 .iterationStart
101 .is_some_and(|iteration_start| *iteration_start < 0.0)
102 {
103 return Err(Error::Type(
104 c"Negative values for iterationStart are not allowed".to_owned(),
105 ));
106 }
107
108 if input
111 .iterations
112 .is_some_and(|iterations| iterations < 0.0 || iterations.is_nan())
113 {
114 return Err(Error::Type(
115 c"\"iterations\" must be a positive number".to_owned(),
116 ));
117 }
118
119 let Ok(duration) = input
122 .duration
123 .as_ref()
124 .map(|duration| match duration {
125 UnrestrictedDoubleOrString::UnrestrictedDouble(double) => {
126 if *double < 0.0 || double.is_nan() {
127 Err(())
128 } else {
129 Ok(IterationDurationOrAuto::Duration(*double))
130 }
131 },
132 UnrestrictedDoubleOrString::String(string) => {
133 if string == "auto" {
134 Ok(IterationDurationOrAuto::Auto)
135 } else {
136 Err(())
137 }
138 },
139 })
140 .transpose()
141 else {
142 return Err(Error::Type(
143 c"\"duration\" must be a positive number".to_owned(),
144 ));
145 };
146
147 let Ok(easing) = input
150 .easing
151 .as_ref()
152 .map(|easing| {
153 let easing = easing.str();
154 let mut parser_input = ParserInput::new(&easing);
155 let mut parser = Parser::new(&mut parser_input);
156
157 let document = self.window.Document();
159 let urlextradata = document.url().into_url().into();
160 let parser_context = parser_context_for_document(
161 &document,
162 CssRuleType::Style,
163 ParsingMode::DEFAULT,
164 &urlextradata,
165 );
166 TimingFunction::parse(&parser_context, &mut parser).map_err(|_| ())
167 })
168 .transpose()
169 else {
170 return Err(Error::Type(
171 c"\"easing\" is not a valid timing function".to_owned(),
172 ));
173 };
174
175 let mut specified_timing_properties = self.specified_timing_properties.borrow_mut();
178 if let Some(start_delay) = input.delay {
179 specified_timing_properties.start_delay = start_delay;
180 }
181
182 if let Some(end_delay) = input.endDelay {
184 specified_timing_properties.end_delay = end_delay;
185 }
186
187 if let Some(fill) = input.fill {
189 specified_timing_properties.fill_mode = fill;
190 }
191
192 if let Some(iteration_start) = input.iterationStart {
194 specified_timing_properties.iteration_start = iteration_start;
195 }
196
197 if let Some(iterations) = input.iterations {
199 specified_timing_properties.iteration_count = iterations;
200 }
201
202 if let Some(duration) = duration {
204 specified_timing_properties.iteration_duration = duration;
205 }
206
207 if let Some(direction) = input.direction {
209 specified_timing_properties.playback_direction = direction;
210 }
211
212 if let Some(easing) = easing {
214 specified_timing_properties.easing_function = easing;
215 }
216 Ok(())
217 }
218}
219
220impl AnimationEffectMethods<crate::DomTypeHolder> for AnimationEffect {
221 fn GetTiming(&self) -> EffectTiming {
223 let specified_timing_properties = self.specified_timing_properties.borrow();
225 EffectTiming {
226 delay: specified_timing_properties.start_delay,
227 direction: specified_timing_properties.playback_direction,
228 duration: specified_timing_properties.iteration_duration.into(),
229 easing: specified_timing_properties
230 .easing_function
231 .to_css_string()
232 .into(),
233 endDelay: specified_timing_properties.end_delay,
234 fill: specified_timing_properties.fill_mode,
235 iterationStart: specified_timing_properties.iteration_start,
236 iterations: specified_timing_properties.iteration_count,
237 }
238 }
239
240 fn GetComputedTiming(&self) -> ComputedEffectTiming {
242 let specified_timing_properties = self.specified_timing_properties.borrow();
244
245 let computed_duration = specified_timing_properties
250 .iteration_duration
251 .computed_value();
252
253 let computed_fill_mode = if specified_timing_properties.fill_mode == FillMode::Auto {
258 FillMode::None
259 } else {
260 specified_timing_properties.fill_mode
261 };
262
263 ComputedEffectTiming {
264 parent: EffectTiming {
265 delay: specified_timing_properties.start_delay,
266 direction: specified_timing_properties.playback_direction,
267 duration: UnrestrictedDoubleOrString::UnrestrictedDouble(computed_duration),
268 easing: specified_timing_properties
269 .easing_function
270 .to_css_string()
271 .into(),
272 endDelay: specified_timing_properties.end_delay,
273 fill: computed_fill_mode,
274 iterationStart: specified_timing_properties.iteration_start,
275 iterations: specified_timing_properties.iteration_count,
276 },
277 endTime: None,
279 activeDuration: None,
280 localTime: None,
281 progress: None,
282 currentIteration: None,
283 }
284 }
285
286 fn UpdateTiming(&self, timing: &OptionalEffectTiming) -> Fallible<()> {
288 self.update_the_timing_properties(timing)
291 }
292}
293
294#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
295enum IterationDurationOrAuto {
296 Duration(f64),
297 Auto,
298}
299
300impl IterationDurationOrAuto {
301 fn computed_value(&self) -> f64 {
302 match self {
303 IterationDurationOrAuto::Auto => 0.0,
304 IterationDurationOrAuto::Duration(double) => *double,
305 }
306 }
307}
308
309impl From<IterationDurationOrAuto> for UnrestrictedDoubleOrString {
310 fn from(value: IterationDurationOrAuto) -> Self {
311 match value {
312 IterationDurationOrAuto::Auto => UnrestrictedDoubleOrString::String("auto".into()),
313 IterationDurationOrAuto::Duration(double) => {
314 UnrestrictedDoubleOrString::UnrestrictedDouble(double)
315 },
316 }
317 }
318}