aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/script/animations.rs12
-rw-r--r--components/style/animation.rs150
-rw-r--r--tests/wpt/metadata-layout-2020/css/css-animations/animation-delay-011.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-blur.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-brightness.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-combined-001.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-contrast.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-grayscale.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-invert.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-opacity.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-saturate.html.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-sepia.html.ini2
-rw-r--r--tests/wpt/metadata/MANIFEST.json7
-rw-r--r--tests/wpt/metadata/css/css-ui/outline-017.html.ini10
-rw-r--r--tests/wpt/metadata/css/css-ui/outline-018.html.ini4
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json4
-rw-r--r--tests/wpt/mozilla/tests/css/animations/animation-delay.html6
-rw-r--r--tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html3
-rw-r--r--tests/wpt/web-platform-tests/css/css-animations/animation-iteration-count-009.html46
19 files changed, 162 insertions, 100 deletions
diff --git a/components/script/animations.rs b/components/script/animations.rs
index bddbac14966..854a06a1e12 100644
--- a/components/script/animations.rs
+++ b/components/script/animations.rs
@@ -365,7 +365,7 @@ impl Animations {
now: f64,
pipeline_id: PipelineId,
) {
- let num_iterations = match animation.iteration_state {
+ let iteration_index = match animation.iteration_state {
KeyframesIterationState::Finite(current, _) |
KeyframesIterationState::Infinite(current) => current,
};
@@ -381,10 +381,14 @@ impl Animations {
TransitionOrAnimationEventType::AnimationStart => {
(-animation.delay).max(0.).min(active_duration)
},
- TransitionOrAnimationEventType::AnimationIteration |
- TransitionOrAnimationEventType::AnimationEnd => num_iterations * animation.duration,
+ TransitionOrAnimationEventType::AnimationIteration => {
+ iteration_index * animation.duration
+ },
+ TransitionOrAnimationEventType::AnimationEnd => {
+ (iteration_index * animation.duration) + animation.current_iteration_duration()
+ },
TransitionOrAnimationEventType::AnimationCancel => {
- (num_iterations * animation.duration) + (now - animation.started_at).max(0.)
+ (iteration_index * animation.duration) + (now - animation.started_at).max(0.)
},
_ => unreachable!(),
}
diff --git a/components/style/animation.rs b/components/style/animation.rs
index 197ba5be0cf..6632ce856b7 100644
--- a/components/style/animation.rs
+++ b/components/style/animation.rs
@@ -470,19 +470,27 @@ impl Animation {
return false;
}
+ if self.on_last_iteration() {
+ return false;
+ }
+
+ self.iterate();
+ true
+ }
+
+ fn iterate(&mut self) {
+ debug_assert!(!self.on_last_iteration());
+
if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state {
- // If we are already on the final iteration, just exit now. This prevents
- // us from updating the direction, which might be needed for the correct
- // handling of animation-fill-mode and also firing animationiteration events
- // at the end of animations.
*current = (*current + 1.).min(max);
- if *current == max {
- return false;
- }
+ }
+
+ if let AnimationState::Paused(ref mut progress) = self.state {
+ debug_assert!(*progress > 1.);
+ *progress -= 1.;
}
// Update the next iteration direction if applicable.
- // TODO(mrobinson): The duration might now be wrong for floating point iteration counts.
self.started_at += self.duration;
match self.direction {
AnimationDirection::Alternate | AnimationDirection::AlternateReverse => {
@@ -494,36 +502,55 @@ impl Animation {
},
_ => {},
}
+ }
- true
+ /// A number (> 0 and <= 1) which represents the fraction of a full iteration
+ /// that the current iteration of the animation lasts. This will be less than 1
+ /// if the current iteration is the fractional remainder of a non-integral
+ /// iteration count.
+ pub fn current_iteration_end_progress(&self) -> f64 {
+ match self.iteration_state {
+ KeyframesIterationState::Finite(current, max) => (max - current).min(1.),
+ KeyframesIterationState::Infinite(_) => 1.,
+ }
}
+ /// The duration of the current iteration of this animation which may be less
+ /// than the animation duration if it has a non-integral iteration count.
+ pub fn current_iteration_duration(&self) -> f64 {
+ self.current_iteration_end_progress() * self.duration
+ }
+
+ /// Whether or not the current iteration is over. Note that this method assumes that
+ /// the animation is still running.
fn iteration_over(&self, time: f64) -> bool {
- time > (self.started_at + self.duration)
+ time > (self.started_at + self.current_iteration_duration())
+ }
+
+ /// Assuming this animation is running, whether or not it is on the last iteration.
+ fn on_last_iteration(&self) -> bool {
+ match self.iteration_state {
+ KeyframesIterationState::Finite(current, max) => current >= (max - 1.),
+ KeyframesIterationState::Infinite(_) => false,
+ }
}
/// Whether or not this animation has finished at the provided time. This does
/// not take into account canceling i.e. when an animation or transition is
/// canceled due to changes in the style.
pub fn has_ended(&self, time: f64) -> bool {
- match self.state {
- AnimationState::Running => {},
- AnimationState::Finished => return true,
- AnimationState::Pending | AnimationState::Canceled | AnimationState::Paused(_) => {
- return false
- },
- }
-
- if !self.iteration_over(time) {
+ if !self.on_last_iteration() {
return false;
}
- // If we have a limited number of iterations and we cannot advance to another
- // iteration, then we have ended.
- return match self.iteration_state {
- KeyframesIterationState::Finite(current, max) => max == current,
- KeyframesIterationState::Infinite(..) => false,
+ let progress = match self.state {
+ AnimationState::Finished => return true,
+ AnimationState::Paused(progress) => progress,
+ AnimationState::Running => (time - self.started_at) / self.duration,
+ AnimationState::Pending | AnimationState::Canceled => return false,
};
+
+ progress >= self.current_iteration_end_progress()
}
/// Updates the appropiate state from other animation.
@@ -601,38 +628,36 @@ impl Animation {
/// Fill in an `AnimationValueMap` with values calculated from this animation at
/// the given time value.
fn get_property_declaration_at_time(&self, now: f64, map: &mut AnimationValueMap) {
- let duration = self.duration;
- let started_at = self.started_at;
+ debug_assert!(!self.computed_steps.is_empty());
- let now = match self.state {
- AnimationState::Running | AnimationState::Pending | AnimationState::Finished => now,
- AnimationState::Paused(progress) => started_at + duration * progress,
+ let total_progress = match self.state {
+ AnimationState::Running | AnimationState::Pending | AnimationState::Finished => {
+ (now - self.started_at) / self.duration
+ },
+ AnimationState::Paused(progress) => progress,
AnimationState::Canceled => return,
};
- debug_assert!(!self.computed_steps.is_empty());
-
- let mut total_progress = (now - started_at) / duration;
if total_progress < 0. &&
self.fill_mode != AnimationFillMode::Backwards &&
self.fill_mode != AnimationFillMode::Both
{
return;
}
-
- if total_progress > 1. &&
+ if self.has_ended(now) &&
self.fill_mode != AnimationFillMode::Forwards &&
self.fill_mode != AnimationFillMode::Both
{
return;
}
- total_progress = total_progress.min(1.0).max(0.0);
+ let total_progress = total_progress
+ .min(self.current_iteration_end_progress())
+ .max(0.0);
// Get the indices of the previous (from) keyframe and the next (to) keyframe.
let next_keyframe_index;
let prev_keyframe_index;
let num_steps = self.computed_steps.len();
- debug_assert!(num_steps > 0);
match self.current_direction {
AnimationDirection::Normal => {
next_keyframe_index = self
@@ -674,45 +699,43 @@ impl Animation {
None => return,
};
+ // If we only need to take into account one keyframe, then exit early
+ // in order to avoid doing more work.
let mut add_declarations_to_map = |keyframe: &ComputedKeyframe| {
for value in keyframe.values.iter() {
map.insert(value.id(), value.clone());
}
};
-
if total_progress <= 0.0 {
add_declarations_to_map(&prev_keyframe);
return;
}
-
if total_progress >= 1.0 {
add_declarations_to_map(&next_keyframe);
return;
}
- let relative_timespan =
- (next_keyframe.start_percentage - prev_keyframe.start_percentage).abs();
- let relative_duration = relative_timespan as f64 * duration;
- let last_keyframe_ended_at = match self.current_direction {
- AnimationDirection::Normal => {
- self.started_at + (duration * prev_keyframe.start_percentage as f64)
- },
- AnimationDirection::Reverse => {
- self.started_at + (duration * (1. - prev_keyframe.start_percentage as f64))
- },
+ let percentage_between_keyframes =
+ (next_keyframe.start_percentage - prev_keyframe.start_percentage).abs() as f64;
+ let duration_between_keyframes = percentage_between_keyframes * self.duration;
+ let direction_aware_prev_keyframe_start_percentage = match self.current_direction {
+ AnimationDirection::Normal => prev_keyframe.start_percentage as f64,
+ AnimationDirection::Reverse => 1. - prev_keyframe.start_percentage as f64,
_ => unreachable!(),
};
+ let progress_between_keyframes = (total_progress -
+ direction_aware_prev_keyframe_start_percentage) /
+ percentage_between_keyframes;
- let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
for (from, to) in prev_keyframe.values.iter().zip(next_keyframe.values.iter()) {
let animation = PropertyAnimation {
from: from.clone(),
to: to.clone(),
timing_function: prev_keyframe.timing_function,
- duration: relative_duration as f64,
+ duration: duration_between_keyframes as f64,
};
- if let Ok(value) = animation.calculate_value(relative_progress) {
+ if let Ok(value) = animation.calculate_value(progress_between_keyframes) {
map.insert(value.id(), value);
}
}
@@ -1319,7 +1342,7 @@ pub fn maybe_start_animations<E>(
};
debug!("maybe_start_animations: name={}", name);
- let duration = box_style.animation_duration_mod(i).seconds();
+ let duration = box_style.animation_duration_mod(i).seconds() as f64;
if duration == 0. {
continue;
}
@@ -1339,8 +1362,11 @@ pub fn maybe_start_animations<E>(
continue;
}
+ // NB: This delay may be negative, meaning that the animation may be created
+ // in a state where we have advanced one or more iterations or even that the
+ // animation begins in a finished state.
let delay = box_style.animation_delay_mod(i).seconds();
- let animation_start = context.current_time_for_animations + delay as f64;
+
let iteration_state = match box_style.animation_iteration_count_mod(i) {
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0),
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
@@ -1357,8 +1383,11 @@ pub fn maybe_start_animations<E>(
},
};
+ let now = context.current_time_for_animations;
+ let started_at = now + delay as f64;
+ let mut starting_progress = (now - started_at) / duration;
let state = match box_style.animation_play_state_mod(i) {
- AnimationPlayState::Paused => AnimationState::Paused(0.),
+ AnimationPlayState::Paused => AnimationState::Paused(starting_progress),
AnimationPlayState::Running => AnimationState::Pending,
};
@@ -1371,12 +1400,12 @@ pub fn maybe_start_animations<E>(
resolver,
);
- let new_animation = Animation {
+ let mut new_animation = Animation {
name: name.clone(),
properties_changed: keyframe_animation.properties_changed,
computed_steps,
- started_at: animation_start,
- duration: duration as f64,
+ started_at,
+ duration,
fill_mode: box_style.animation_fill_mode_mod(i),
delay: delay as f64,
iteration_state,
@@ -1387,6 +1416,13 @@ pub fn maybe_start_animations<E>(
is_new: true,
};
+ // If we started with a negative delay, make sure we iterate the animation if
+ // the delay moves us past the first iteration.
+ while starting_progress > 1. && !new_animation.on_last_iteration() {
+ new_animation.iterate();
+ starting_progress -= 1.;
+ }
+
animation_state.dirty = true;
// If the animation was already present in the list for the node, just update its state.
diff --git a/tests/wpt/metadata-layout-2020/css/css-animations/animation-delay-011.html.ini b/tests/wpt/metadata-layout-2020/css/css-animations/animation-delay-011.html.ini
deleted file mode 100644
index 78b88159561..00000000000
--- a/tests/wpt/metadata-layout-2020/css/css-animations/animation-delay-011.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[animation-delay-011.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-blur.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-blur.html.ini
deleted file mode 100644
index 800cc4b478d..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-blur.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-blur.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-brightness.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-brightness.html.ini
deleted file mode 100644
index f11997b584a..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-brightness.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-brightness.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-combined-001.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-combined-001.html.ini
deleted file mode 100644
index 0344b35229b..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-combined-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-combined-001.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-contrast.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-contrast.html.ini
deleted file mode 100644
index 2695f0f7f9c..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-contrast.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-contrast.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-grayscale.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-grayscale.html.ini
deleted file mode 100644
index 8f2f124651c..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-grayscale.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-grayscale.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-invert.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-invert.html.ini
deleted file mode 100644
index ff2c841595d..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-invert.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-invert.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-opacity.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-opacity.html.ini
deleted file mode 100644
index 7fe7b9058bd..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-opacity.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-opacity.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-saturate.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-saturate.html.ini
deleted file mode 100644
index 6134590bfd9..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-saturate.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-saturate.html]
- expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-sepia.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-sepia.html.ini
deleted file mode 100644
index e827ddafe8a..00000000000
--- a/tests/wpt/metadata-layout-2020/css/filter-effects/css-filters-animation-sepia.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-filters-animation-sepia.html]
- expected: FAIL
diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json
index 5d2c1f32822..b1c526b2fa2 100644
--- a/tests/wpt/metadata/MANIFEST.json
+++ b/tests/wpt/metadata/MANIFEST.json
@@ -385595,6 +385595,13 @@
{}
]
],
+ "animation-iteration-count-009.html": [
+ "da86c81b9337a99841977acd8e2ffe8b8e858190",
+ [
+ null,
+ {}
+ ]
+ ],
"animation-iteration-count-calc.html": [
"44e1e96a589a4e1c5b98e919e7246d05097b0604",
[
diff --git a/tests/wpt/metadata/css/css-ui/outline-017.html.ini b/tests/wpt/metadata/css/css-ui/outline-017.html.ini
deleted file mode 100644
index 6ef00c57d8b..00000000000
--- a/tests/wpt/metadata/css/css-ui/outline-017.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[outline-017.html]
- [outline-color is animated as a color]
- expected: FAIL
-
- [outline-width is animated as a length]
- expected: FAIL
-
- [outline-offset is animated as a length]
- expected: FAIL
-
diff --git a/tests/wpt/metadata/css/css-ui/outline-018.html.ini b/tests/wpt/metadata/css/css-ui/outline-018.html.ini
deleted file mode 100644
index a2fea6c00e1..00000000000
--- a/tests/wpt/metadata/css/css-ui/outline-018.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[outline-018.html]
- [outline-style is animated as a discrete type]
- expected: FAIL
-
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 7923f209acb..777a77d3388 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -12867,7 +12867,7 @@
"css": {
"animations": {
"animation-delay.html": [
- "0d2053a9134d8ff0ade7b5dc37ecfce305557c44",
+ "f54cf2b9bca93e82b177243b9d754e4ae3bf15fa",
[
null,
{}
@@ -12883,7 +12883,7 @@
]
],
"animation-fill-mode.html": [
- "9602ec9f0e0eb1f6efcc2e7bd95181ef65339bae",
+ "ac3062879af9836768890d653f4b29b6165b6a45",
[
null,
{}
diff --git a/tests/wpt/mozilla/tests/css/animations/animation-delay.html b/tests/wpt/mozilla/tests/css/animations/animation-delay.html
index 0d2053a9134..f54cf2b9bca 100644
--- a/tests/wpt/mozilla/tests/css/animations/animation-delay.html
+++ b/tests/wpt/mozilla/tests/css/animations/animation-delay.html
@@ -31,6 +31,7 @@ test(function() {
element.style.animationIterationCount = 1;
element.style.animationName = "width-animation";
element.style.animationTimingFunction = "linear";
+ element.style.animationFillMode = "forwards";
document.body.appendChild(element);
assert_equals(getComputedStyle(element).getPropertyValue("width"), "50px");
@@ -48,7 +49,7 @@ test(function() {
assert_equals(getComputedStyle(element).getPropertyValue("width"), "500px");
testBinding.advanceClock(500);
- assert_equals(getComputedStyle(element).getPropertyValue("width"), "50px");
+ assert_equals(getComputedStyle(element).getPropertyValue("width"), "500px");
}, "animation-delay should function correctly");
test(function() {
@@ -61,6 +62,7 @@ test(function() {
element.style.animationIterationCount = 2;
element.style.animationName = "width-animation";
element.style.animationTimingFunction = "linear";
+ element.style.animationFillMode = "forwards";
document.body.appendChild(element);
assert_equals(getComputedStyle(element).getPropertyValue("width"), "50px");
@@ -85,7 +87,7 @@ test(function() {
assert_equals(getComputedStyle(element).getPropertyValue("width"), "500px");
testBinding.advanceClock(500);
- assert_equals(getComputedStyle(element).getPropertyValue("width"), "50px");
+ assert_equals(getComputedStyle(element).getPropertyValue("width"), "500px");
}, "animation-delay should function correctly with multiple iterations");
</script>
diff --git a/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html b/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
index 9602ec9f0e0..ac3062879af 100644
--- a/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
+++ b/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
@@ -48,8 +48,9 @@ function runThroughAnimation(testBinding, element, waitForDelay = true) {
testBinding.advanceClock(500);
assert_equals(getComputedStyle(element).getPropertyValue("width"), "250px");
+ // After advancing another 500 milliseconds the animation should finished and
+ // width value will depend on the value of `animation-fill-mode`.
testBinding.advanceClock(500);
- assert_equals(getComputedStyle(element).getPropertyValue("width"), "500px");
}
test(function() {
diff --git a/tests/wpt/web-platform-tests/css/css-animations/animation-iteration-count-009.html b/tests/wpt/web-platform-tests/css/css-animations/animation-iteration-count-009.html
new file mode 100644
index 00000000000..da86c81b933
--- /dev/null
+++ b/tests/wpt/web-platform-tests/css/css-animations/animation-iteration-count-009.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Animation Test: fractional animation-iteration-count</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#animation-iteration-count">
+<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes margin-animation {
+ from {
+ margin-left: 0px;
+ }
+ to {
+ margin-left: 100px;
+ }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 1.5 normal forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '50px');
+}, 'Basic floating point iteration count');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 3.25 normal forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '25px');
+}, 'Floating point iteration count after multiple iterations');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 0.75 normal forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '75px');
+}, 'Floating point iteration count during first iteration');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 1.75 alternate forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '25px');
+}, 'Floating point iteration count with alternating directions');
+</script>