script/dom/animations/
animationeffect.rs1use cssparser::Parser;
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 script_bindings::str::DOMString;
18use style::parser::Parse;
19use style::stylesheets::CssRuleType;
20use style::values::generics::easing::TimingKeyword;
21use style::values::specified::TimingFunction;
22use style_traits::{ParsingMode, ToCss};
23
24use crate::css::css::parser_context_for_document;
25use crate::dom::Window;
26use crate::dom::bindings::codegen::UnionTypes::UnrestrictedDoubleOrString;
27
28#[dom_struct]
30pub(crate) struct AnimationEffect {
31 reflector: Reflector,
32
33 window: Dom<Window>,
35
36 specified_timing_properties: DomRefCell<SpecifiedTimingProperties>,
37}
38
39#[derive(Clone, JSTraceable, MallocSizeOf)]
40struct SpecifiedTimingProperties {
41 start_delay: Finite<f64>,
43
44 end_delay: Finite<f64>,
46
47 fill_mode: FillMode,
49
50 iteration_count: f64,
52
53 iteration_start: Finite<f64>,
55
56 iteration_duration: IterationDurationOrAuto,
58
59 playback_direction: PlaybackDirection,
61
62 #[no_trace]
64 easing_function: TimingFunction,
65}
66
67impl AnimationEffect {
68 pub(crate) fn new_inherited(window: &Window) -> Self {
69 Self {
70 reflector: Reflector::new(),
71 window: Dom::from_ref(window),
72
73 specified_timing_properties: DomRefCell::new(SpecifiedTimingProperties {
77 start_delay: Default::default(),
78 end_delay: Default::default(),
79 fill_mode: FillMode::None,
80 iteration_count: Default::default(),
81 iteration_start: Default::default(),
82 iteration_duration: IterationDurationOrAuto::Auto,
83 playback_direction: PlaybackDirection::Normal,
84 easing_function: TimingFunction::Keyword(TimingKeyword::Linear),
85 }),
86 }
87 }
88
89 pub(crate) fn window(&self) -> &Window {
90 &self.window
91 }
92
93 pub(crate) fn update_the_timing_properties(
95 &self,
96 input: &OptionalEffectTiming,
97 ) -> Fallible<()> {
98 if input
101 .iterationStart
102 .is_some_and(|iteration_start| *iteration_start < 0.0)
103 {
104 return Err(Error::Type(
105 c"Negative values for iterationStart are not allowed".to_owned(),
106 ));
107 }
108
109 if input
112 .iterations
113 .is_some_and(|iterations| iterations < 0.0 || iterations.is_nan())
114 {
115 return Err(Error::Type(
116 c"\"iterations\" must be a positive number".to_owned(),
117 ));
118 }
119
120 let Ok(duration) = input
123 .duration
124 .as_ref()
125 .map(|duration| match duration {
126 UnrestrictedDoubleOrString::UnrestrictedDouble(double) => {
127 if *double < 0.0 || double.is_nan() {
128 Err(())
129 } else {
130 Ok(IterationDurationOrAuto::Duration(*double))
131 }
132 },
133 UnrestrictedDoubleOrString::String(string) => {
134 if string == "auto" {
135 Ok(IterationDurationOrAuto::Auto)
136 } else {
137 Err(())
138 }
139 },
140 })
141 .transpose()
142 else {
143 return Err(Error::Type(
144 c"\"duration\" must be a positive number".to_owned(),
145 ));
146 };
147
148 let Ok(easing) = input
151 .easing
152 .as_ref()
153 .map(|easing| {
154 let easing = easing.str();
155 let mut parser = Parser::new(&easing);
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 => {
313 UnrestrictedDoubleOrString::String(DOMString::from_static("auto"))
314 },
315 IterationDurationOrAuto::Duration(double) => {
316 UnrestrictedDoubleOrString::UnrestrictedDouble(double)
317 },
318 }
319 }
320}