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
131 .duration
132 .as_ref()
133 .map(|duration| match duration {
134 UnrestrictedDoubleOrString::UnrestrictedDouble(double) => {
135 if *double < 0.0 || double.is_nan() {
136 Err(())
137 } else {
138 Ok(IterationDurationOrAuto::Duration(*double))
139 }
140 },
141 UnrestrictedDoubleOrString::String(string) => {
142 if string == "auto" {
143 Ok(IterationDurationOrAuto::Auto)
144 } else {
145 Err(())
146 }
147 },
148 })
149 .transpose()
150 else {
151 return Err(Error::Type(
152 c"\"duration\" must be a positive number".to_owned(),
153 ));
154 };
155
156 let Ok(easing) = input
159 .easing
160 .as_ref()
161 .map(|easing| {
162 let easing = easing.str();
163 let mut parser_input = ParserInput::new(&easing);
164 let mut parser = Parser::new(&mut parser_input);
165
166 let document = self.window.Document();
168 let urlextradata = document.url().into_url().into();
169 let parser_context = parser_context_for_document(
170 &document,
171 CssRuleType::Style,
172 ParsingMode::DEFAULT,
173 &urlextradata,
174 );
175 TimingFunction::parse(&parser_context, &mut parser).map_err(|_| ())
176 })
177 .transpose()
178 else {
179 return Err(Error::Type(
180 c"\"easing\" is not a valid timing function".to_owned(),
181 ));
182 };
183
184 let mut specified_timing_properties = self.specified_timing_properties.borrow_mut();
187 if let Some(start_delay) = input.delay {
188 specified_timing_properties.start_delay = start_delay;
189 }
190
191 if let Some(end_delay) = input.endDelay {
193 specified_timing_properties.end_delay = end_delay;
194 }
195
196 if let Some(fill) = input.fill {
198 specified_timing_properties.fill_mode = fill;
199 }
200
201 if let Some(iteration_start) = input.iterationStart {
203 specified_timing_properties.iteration_start = iteration_start;
204 }
205
206 if let Some(iterations) = input.iterations {
208 specified_timing_properties.iteration_count = iterations;
209 }
210
211 if let Some(duration) = duration {
213 specified_timing_properties.iteration_duration = duration;
214 }
215
216 if let Some(direction) = input.direction {
218 specified_timing_properties.playback_direction = direction;
219 }
220
221 if let Some(easing) = easing {
223 specified_timing_properties.easing_function = easing;
224 }
225 Ok(())
226 }
227}
228
229impl AnimationEffectMethods<crate::DomTypeHolder> for AnimationEffect {
230 fn GetTiming(&self) -> EffectTiming {
232 let specified_timing_properties = self.specified_timing_properties.borrow();
234 EffectTiming {
235 delay: specified_timing_properties.start_delay,
236 direction: specified_timing_properties.playback_direction,
237 duration: specified_timing_properties.iteration_duration.into(),
238 easing: specified_timing_properties
239 .easing_function
240 .to_css_string()
241 .into(),
242 endDelay: specified_timing_properties.end_delay,
243 fill: specified_timing_properties.fill_mode,
244 iterationStart: specified_timing_properties.iteration_start,
245 iterations: specified_timing_properties.iteration_count,
246 }
247 }
248
249 fn GetComputedTiming(&self) -> ComputedEffectTiming {
251 let specified_timing_properties = self.specified_timing_properties.borrow();
253
254 let computed_duration = specified_timing_properties
259 .iteration_duration
260 .computed_value();
261
262 let computed_fill_mode = if specified_timing_properties.fill_mode == FillMode::Auto {
267 FillMode::None
268 } else {
269 specified_timing_properties.fill_mode
270 };
271
272 ComputedEffectTiming {
273 parent: EffectTiming {
274 delay: specified_timing_properties.start_delay,
275 direction: specified_timing_properties.playback_direction,
276 duration: UnrestrictedDoubleOrString::UnrestrictedDouble(computed_duration),
277 easing: specified_timing_properties
278 .easing_function
279 .to_css_string()
280 .into(),
281 endDelay: specified_timing_properties.end_delay,
282 fill: computed_fill_mode,
283 iterationStart: specified_timing_properties.iteration_start,
284 iterations: specified_timing_properties.iteration_count,
285 },
286 endTime: None,
288 activeDuration: None,
289 localTime: None,
290 progress: None,
291 currentIteration: None,
292 }
293 }
294
295 fn UpdateTiming(&self, timing: &OptionalEffectTiming) -> Fallible<()> {
297 self.update_the_timing_properties(timing)
300 }
301}
302
303#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
304enum IterationDurationOrAuto {
305 Duration(f64),
306 Auto,
307}
308
309impl IterationDurationOrAuto {
310 fn computed_value(&self) -> f64 {
311 match self {
312 IterationDurationOrAuto::Auto => 0.0,
313 IterationDurationOrAuto::Duration(double) => *double,
314 }
315 }
316}
317
318impl From<IterationDurationOrAuto> for UnrestrictedDoubleOrString {
319 fn from(value: IterationDurationOrAuto) -> Self {
320 match value {
321 IterationDurationOrAuto::Auto => UnrestrictedDoubleOrString::String("auto".into()),
322 IterationDurationOrAuto::Duration(double) => {
323 UnrestrictedDoubleOrString::UnrestrictedDouble(double)
324 },
325 }
326 }
327}