aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <servo-ops@mozilla.com>2020-05-17 15:09:49 -0400
committerGitHub <noreply@github.com>2020-05-17 15:09:49 -0400
commit50bd5c3e0f9166a9fa049b95f23251ad7626f8de (patch)
tree1c817c288530e4115609daa87ede4f85cfe825b0
parent619e0bceaf750ec862169a6a02cfbf82ba9be0a8 (diff)
parent183f15d5aacd290aa2bd2c3a2397cd456d170161 (diff)
downloadservo-50bd5c3e0f9166a9fa049b95f23251ad7626f8de.tar.gz
servo-50bd5c3e0f9166a9fa049b95f23251ad7626f8de.zip
Auto merge of #26519 - mrobinson:animation-animation-fill-mode, r=jdm
Implement `animation-fill-mode` Fixes #26460. <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] There are tests for these changes <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
-rw-r--r--components/style/animation.rs153
-rw-r--r--components/style/matching.rs8
-rw-r--r--components/style/properties/longhands/box.mako.rs2
-rw-r--r--tests/wpt/metadata/css/css-animations/animation-delay-010.html.ini2
-rw-r--r--tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini3
-rw-r--r--tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini3
-rw-r--r--tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini3
-rw-r--r--tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini3
-rw-r--r--tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini3
-rw-r--r--tests/wpt/metadata/css/cssom/getComputedStyle-animations-replaced-into-ib-split.html.ini4
-rw-r--r--tests/wpt/mozilla/meta-layout-2020/css/animations/__dir__.ini2
-rw-r--r--tests/wpt/mozilla/meta-layout-2020/css/animations/animation-fill-mode.html.ini16
-rw-r--r--tests/wpt/mozilla/meta-layout-2020/css/animations/basic-transition.html.ini5
-rw-r--r--tests/wpt/mozilla/meta-layout-2020/css/animations/transition-raf.html.ini5
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json7
-rw-r--r--tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html116
16 files changed, 240 insertions, 95 deletions
diff --git a/components/style/animation.rs b/components/style/animation.rs
index f52e9b42a51..b30ff303165 100644
--- a/components/style/animation.rs
+++ b/components/style/animation.rs
@@ -13,6 +13,7 @@ use crate::dom::{OpaqueNode, TElement, TNode};
use crate::font_metrics::FontMetricsProvider;
use crate::properties::animated_properties::AnimationValue;
use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
+use crate::properties::longhands::animation_fill_mode::computed_value::single_value::T as AnimationFillMode;
use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
use crate::properties::LonghandIdSet;
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
@@ -184,6 +185,9 @@ pub struct Animation {
/// The delay of the animation.
pub delay: f64,
+ /// The `animation-fill-mode` property of this animation.
+ pub fill_mode: AnimationFillMode,
+
/// The current iteration state for the animation.
pub iteration_state: KeyframesIterationState,
@@ -324,9 +328,6 @@ impl Animation {
(&mut Paused(ref mut progress), Running) => {
*progress = (now - old_started_at) / old_duration
},
- // TODO(mrobinson): We should handle the case where a new animation replaces
- // a finished one.
- (_, Finished) | (Finished, _) => unreachable!("Did not expect Finished animation."),
_ => {},
}
@@ -361,7 +362,7 @@ impl Animation {
fn update_style<E>(
&self,
context: &SharedStyleContext,
- style: &mut ComputedValues,
+ style: &mut Arc<ComputedValues>,
font_metrics_provider: &dyn FontMetricsProvider,
) where
E: TElement,
@@ -370,46 +371,54 @@ impl Animation {
let started_at = self.started_at;
let now = match self.state {
- AnimationState::Running => context.current_time_for_animations,
+ AnimationState::Running | AnimationState::Finished => {
+ context.current_time_for_animations
+ },
AnimationState::Paused(progress) => started_at + duration * progress,
- AnimationState::Canceled | AnimationState::Finished => return,
+ AnimationState::Canceled => return,
};
debug_assert!(!self.keyframes_animation.steps.is_empty());
+
let mut total_progress = (now - started_at) / duration;
- if total_progress < 0. {
- warn!("Negative progress found for animation {:?}", self.name);
+ if total_progress < 0. &&
+ self.fill_mode != AnimationFillMode::Backwards &&
+ self.fill_mode != AnimationFillMode::Both
+ {
return;
}
- if total_progress > 1. {
- total_progress = 1.;
+
+ if total_progress > 1. &&
+ self.fill_mode != AnimationFillMode::Forwards &&
+ self.fill_mode != AnimationFillMode::Both
+ {
+ return;
}
+ total_progress = total_progress.min(1.0).max(0.0);
- // Get the target and the last keyframe position.
- let last_keyframe_position;
- let target_keyframe_position;
+ // Get the indices of the previous (from) keyframe and the next (to) keyframe.
+ let next_keyframe_index;
+ let prev_keyframe_index;
match self.current_direction {
AnimationDirection::Normal => {
- target_keyframe_position = self
+ next_keyframe_index = self
.keyframes_animation
.steps
.iter()
.position(|step| total_progress as f32 <= step.start_percentage.0);
-
- last_keyframe_position = target_keyframe_position
+ prev_keyframe_index = next_keyframe_index
.and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
.unwrap_or(0);
},
AnimationDirection::Reverse => {
- target_keyframe_position = self
+ next_keyframe_index = self
.keyframes_animation
.steps
.iter()
.rev()
.position(|step| total_progress as f32 <= 1. - step.start_percentage.0)
.map(|pos| self.keyframes_animation.steps.len() - pos - 1);
-
- last_keyframe_position = target_keyframe_position
+ prev_keyframe_index = next_keyframe_index
.and_then(|pos| {
if pos != self.keyframes_animation.steps.len() - 1 {
Some(pos + 1)
@@ -417,52 +426,83 @@ impl Animation {
None
}
})
- .unwrap_or(self.keyframes_animation.steps.len() - 1);
+ .unwrap_or(self.keyframes_animation.steps.len() - 1)
},
_ => unreachable!(),
}
debug!(
"Animation::update_style: keyframe from {:?} to {:?}",
- last_keyframe_position, target_keyframe_position
+ prev_keyframe_index, next_keyframe_index
);
- let target_keyframe = match target_keyframe_position {
+ let prev_keyframe = &self.keyframes_animation.steps[prev_keyframe_index];
+ let next_keyframe = match next_keyframe_index {
Some(target) => &self.keyframes_animation.steps[target],
None => return,
};
- let last_keyframe = &self.keyframes_animation.steps[last_keyframe_position];
+ let update_with_single_keyframe_style = |style, computed_style: &Arc<ComputedValues>| {
+ let mutable_style = Arc::make_mut(style);
+ for property in self
+ .keyframes_animation
+ .properties_changed
+ .iter()
+ .filter_map(|longhand| {
+ AnimationValue::from_computed_values(longhand, &**computed_style)
+ })
+ {
+ property.set_in_style_for_servo(mutable_style);
+ }
+ };
+
+ // TODO: How could we optimise it? Is it such a big deal?
+ let prev_keyframe_style = compute_style_for_animation_step::<E>(
+ context,
+ prev_keyframe,
+ style,
+ &self.cascade_style,
+ font_metrics_provider,
+ );
+ if total_progress <= 0.0 {
+ update_with_single_keyframe_style(style, &prev_keyframe_style);
+ return;
+ }
+
+ let next_keyframe_style = compute_style_for_animation_step::<E>(
+ context,
+ next_keyframe,
+ &prev_keyframe_style,
+ &self.cascade_style,
+ font_metrics_provider,
+ );
+ if total_progress >= 1.0 {
+ update_with_single_keyframe_style(style, &next_keyframe_style);
+ return;
+ }
let relative_timespan =
- (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs();
+ (next_keyframe.start_percentage.0 - prev_keyframe.start_percentage.0).abs();
let relative_duration = relative_timespan as f64 * duration;
let last_keyframe_ended_at = match self.current_direction {
AnimationDirection::Normal => {
- self.started_at + (duration * last_keyframe.start_percentage.0 as f64)
+ self.started_at + (duration * prev_keyframe.start_percentage.0 as f64)
},
AnimationDirection::Reverse => {
- self.started_at + (duration * (1. - last_keyframe.start_percentage.0 as f64))
+ self.started_at + (duration * (1. - prev_keyframe.start_percentage.0 as f64))
},
_ => unreachable!(),
};
let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
- // TODO: How could we optimise it? Is it such a big deal?
- let from_style = compute_style_for_animation_step::<E>(
- context,
- last_keyframe,
- style,
- &self.cascade_style,
- font_metrics_provider,
- );
-
// NB: The spec says that the timing function can be overwritten
// from the keyframe style.
- let timing_function = if last_keyframe.declared_timing_function {
+ let timing_function = if prev_keyframe.declared_timing_function {
// NB: animation_timing_function can never be empty, always has
// at least the default value (`ease`).
- from_style.get_box().animation_timing_function_at(0)
+ prev_keyframe_style
+ .get_box()
+ .animation_timing_function_at(0)
} else {
// TODO(mrobinson): It isn't optimal to have to walk this list every
// time. Perhaps this should be stored in the animation.
@@ -477,18 +517,10 @@ impl Animation {
style.get_box().animation_timing_function_mod(index)
};
- let target_style = compute_style_for_animation_step::<E>(
- context,
- target_keyframe,
- &from_style,
- &self.cascade_style,
- font_metrics_provider,
- );
-
- let mut new_style = (*style).clone();
+ let mut new_style = (**style).clone();
let mut update_style_for_longhand = |longhand| {
- let from = AnimationValue::from_computed_values(longhand, &from_style)?;
- let to = AnimationValue::from_computed_values(longhand, &target_style)?;
+ let from = AnimationValue::from_computed_values(longhand, &prev_keyframe_style)?;
+ let to = AnimationValue::from_computed_values(longhand, &next_keyframe_style)?;
PropertyAnimation {
from,
to,
@@ -503,7 +535,7 @@ impl Animation {
update_style_for_longhand(property);
}
- *style = new_style;
+ *Arc::make_mut(style) = new_style;
}
}
@@ -560,7 +592,7 @@ impl Transition {
}
/// Update a style to the value specified by this `Transition` given a `SharedStyleContext`.
- fn update_style(&self, context: &SharedStyleContext, style: &mut ComputedValues) {
+ fn update_style(&self, context: &SharedStyleContext, style: &mut Arc<ComputedValues>) {
// Never apply canceled transitions to a style.
if self.state == AnimationState::Canceled {
return;
@@ -568,7 +600,8 @@ impl Transition {
let progress = self.progress(context.current_time_for_animations);
if progress >= 0.0 {
- self.property_animation.update(style, progress);
+ self.property_animation
+ .update(Arc::make_mut(style), progress);
}
}
}
@@ -603,12 +636,6 @@ impl ElementAnimationSet {
) where
E: TElement,
{
- // Return early to avoid potentially copying the style.
- if self.animations.is_empty() && self.transitions.is_empty() {
- return;
- }
-
- let style = Arc::make_mut(style);
for animation in &self.animations {
animation.update_style::<E>(context, style, font_metrics);
}
@@ -618,15 +645,6 @@ impl ElementAnimationSet {
}
}
- pub(crate) fn clear_finished_animations(&mut self) {
- // TODO(mrobinson): This should probably not clear finished animations
- // because of `animation-fill-mode`.
- self.animations
- .retain(|animation| animation.state != AnimationState::Finished);
- self.transitions
- .retain(|animation| animation.state != AnimationState::Finished);
- }
-
/// Clear all canceled animations and transitions from this `ElementAnimationSet`.
pub fn clear_canceled_animations(&mut self) {
self.animations
@@ -933,6 +951,7 @@ pub fn maybe_start_animations<E>(
keyframes_animation: anim.clone(),
started_at: animation_start,
duration: duration as f64,
+ fill_mode: box_style.animation_fill_mode_mod(i),
delay: delay as f64,
iteration_state,
state,
@@ -944,7 +963,7 @@ pub fn maybe_start_animations<E>(
// If the animation was already present in the list for the node, just update its state.
for existing_animation in animation_state.animations.iter_mut() {
- if existing_animation.state != AnimationState::Running {
+ if existing_animation.state == AnimationState::Canceled {
continue;
}
diff --git a/components/style/matching.rs b/components/style/matching.rs
index e01adda303e..677f024acfa 100644
--- a/components/style/matching.rs
+++ b/components/style/matching.rs
@@ -7,6 +7,7 @@
#![allow(unsafe_code)]
#![deny(missing_docs)]
+use crate::animation::AnimationState;
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
use crate::context::{SharedStyleContext, StyleContext};
@@ -458,9 +459,14 @@ trait PrivateMatchMethods: TElement {
&context.thread_local.font_metrics_provider,
);
+ // We clear away any finished transitions, but retain animations, because they
+ // might still be used for proper calculation of `animation-fill-mode`.
+ animation_state
+ .transitions
+ .retain(|transition| transition.state != AnimationState::Finished);
+
// If the ElementAnimationSet is empty, and don't store it in order to
// save memory and to avoid extra processing later.
- animation_state.clear_finished_animations();
if !animation_state.is_empty() {
animation_states.insert(this_opaque, animation_state);
}
diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs
index a4f2cc670f5..243acb76ea3 100644
--- a/components/style/properties/longhands/box.mako.rs
+++ b/components/style/properties/longhands/box.mako.rs
@@ -312,7 +312,7 @@ ${helpers.single_keyword(
${helpers.single_keyword(
"animation-fill-mode",
"none forwards backwards both",
- engines="gecko servo-2013",
+ engines="gecko servo-2013 servo-2020",
need_index=True,
animation_value_type="none",
vector=True,
diff --git a/tests/wpt/metadata/css/css-animations/animation-delay-010.html.ini b/tests/wpt/metadata/css/css-animations/animation-delay-010.html.ini
index 8f275c978f4..284bbbac898 100644
--- a/tests/wpt/metadata/css/css-animations/animation-delay-010.html.ini
+++ b/tests/wpt/metadata/css/css-animations/animation-delay-010.html.ini
@@ -1,3 +1,3 @@
[animation-delay-010.html]
bug: https://github.com/servo/servo/issues/17335
- expected: TIMEOUT
+ expected: FAIL
diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini
index 454e1d2dec0..e61911d3325 100644
--- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini
+++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini
@@ -1,5 +1,2 @@
[variable-animation-substitute-into-keyframe-shorthand.html]
bug: https://github.com/servo/servo/issues/21564
- [Verify border-bottom-color before animation]
- expected: FAIL
-
diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini
index f4720d8cf96..ccac15c6181 100644
--- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini
+++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini
@@ -1,5 +1,2 @@
[variable-animation-substitute-into-keyframe.html]
bug: https://github.com/servo/servo/issues/21564
- [Verify color before animation]
- expected: FAIL
-
diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini
index a0f8055196a..8fe87ce4aa4 100644
--- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini
+++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini
@@ -1,8 +1,5 @@
[variable-animation-substitute-within-keyframe-fallback.html]
bug: https://github.com/servo/servo/issues/21564
- [Verify color before animation]
- expected: FAIL
-
[Verify color after animation]
expected: FAIL
diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini
index baa8d60195e..7046292ccd7 100644
--- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini
+++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini
@@ -1,8 +1,5 @@
[variable-animation-substitute-within-keyframe-multiple.html]
bug: https://github.com/servo/servo/issues/21564
- [Verify color before animation]
- expected: FAIL
-
[Verify color after animation]
expected: FAIL
diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini
index e1e48b487e4..705cf7ed37a 100644
--- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini
+++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini
@@ -1,8 +1,5 @@
[variable-animation-substitute-within-keyframe.html]
bug: https://github.com/servo/servo/issues/21564
- [Verify color before animation]
- expected: FAIL
-
[Verify color after animation]
expected: FAIL
diff --git a/tests/wpt/metadata/css/cssom/getComputedStyle-animations-replaced-into-ib-split.html.ini b/tests/wpt/metadata/css/cssom/getComputedStyle-animations-replaced-into-ib-split.html.ini
deleted file mode 100644
index ed664c1c84d..00000000000
--- a/tests/wpt/metadata/css/cssom/getComputedStyle-animations-replaced-into-ib-split.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[getComputedStyle-animations-replaced-into-ib-split.html]
- [getComputedStyle() should return animation styles for nodes just inserted into the document, even if they're in an IB-split]
- expected: FAIL
-
diff --git a/tests/wpt/mozilla/meta-layout-2020/css/animations/__dir__.ini b/tests/wpt/mozilla/meta-layout-2020/css/animations/__dir__.ini
new file mode 100644
index 00000000000..696581b4a4a
--- /dev/null
+++ b/tests/wpt/mozilla/meta-layout-2020/css/animations/__dir__.ini
@@ -0,0 +1,2 @@
+prefs: ["layout.animations.test.enabled:true",
+ "dom.testbinding.enabled:true"]
diff --git a/tests/wpt/mozilla/meta-layout-2020/css/animations/animation-fill-mode.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/animations/animation-fill-mode.html.ini
new file mode 100644
index 00000000000..9d52b8c51db
--- /dev/null
+++ b/tests/wpt/mozilla/meta-layout-2020/css/animations/animation-fill-mode.html.ini
@@ -0,0 +1,16 @@
+[animation-fill-mode.html]
+ [animation-fill-mode: both should function correctly]
+ expected: FAIL
+
+ [animation-fill-mode: both on animation with multiple iterations]
+ expected: FAIL
+
+ [animation-fill-mode: forwards should function correctly]
+ expected: FAIL
+
+ [animation-fill-mode: both on reversed animation]
+ expected: FAIL
+
+ [animation-fill-mode: backwards should function correctly]
+ expected: FAIL
+
diff --git a/tests/wpt/mozilla/meta-layout-2020/css/animations/basic-transition.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/animations/basic-transition.html.ini
deleted file mode 100644
index aad401f6822..00000000000
--- a/tests/wpt/mozilla/meta-layout-2020/css/animations/basic-transition.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[basic-transition.html]
- expected: ERROR
- [Transition test]
- expected: TIMEOUT
-
diff --git a/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-raf.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-raf.html.ini
index 9c4652f5544..cd03f205cae 100644
--- a/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-raf.html.ini
+++ b/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-raf.html.ini
@@ -1,2 +1,5 @@
[transition-raf.html]
- expected: ERROR
+ expected: TIMEOUT
+ [Transitions should work during RAF loop]
+ expected: TIMEOUT
+
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 8de2df10dec..fcd1e3da6e3 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -12849,6 +12849,13 @@
},
"css": {
"animations": {
+ "animation-fill-mode.html": [
+ "4cfaab9fbce0adccd83f592935e63fa8ff58a1cf",
+ [
+ null,
+ {}
+ ]
+ ],
"basic-linear-width.html": [
"634b09dca5924b8bea58ac8532d9d46c20d8a0ad",
[
diff --git a/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html b/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
new file mode 100644
index 00000000000..4cfaab9fbce
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
@@ -0,0 +1,116 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Animation test: Automated test for animation-fill-mode.</title>
+<style>
+ .target {
+ width: 50px;
+ height: 50px;
+ background: red;
+ }
+
+ @keyframes width-animation {
+ from { width: 0px; }
+ to { width: 500px; }
+ }
+
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body></body>
+
+<script>
+function setAndTriggerAnimationOnElement(fillMode, direction = "normal", iterationCount = "1") {
+ let element = document.createElement("div");
+ element.className = "target";
+
+ element.style.animationDelay = "1s";
+ element.style.animationDirection = direction;
+ element.style.animationDuration = "1s";
+ element.style.animationFillMode = fillMode;
+ element.style.animationIterationCount = iterationCount;
+ element.style.animationName = "width-animation";
+ element.style.animationTimingFunction = "linear";
+
+ document.body.appendChild(element);
+ return element;
+}
+
+function runThroughAnimation(testBinding, element) {
+ testBinding.advanceClock(1000);
+ assert_equals(getComputedStyle(element).getPropertyValue("width"), "0px");
+
+ testBinding.advanceClock(500);
+ assert_equals(getComputedStyle(element).getPropertyValue("width"), "250px");
+
+ testBinding.advanceClock(500);
+ assert_equals(getComputedStyle(element).getPropertyValue("width"), "500px");
+}
+
+test(function() {
+ let testBinding = new window.TestBinding();
+ let div = setAndTriggerAnimationOnElement("both");
+
+ // The style should reflect the first and last keyframe of the animation
+ // before and after the animation runs.
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "0px");
+ runThroughAnimation(testBinding, div);
+ testBinding.advanceClock(2000);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "500px");
+}, "animation-fill-mode: both should function correctly");
+
+test(function() {
+ let testBinding = new window.TestBinding();
+ let div = setAndTriggerAnimationOnElement("forwards");
+
+ // The style should reflect the last keyframe of the animation after the animation runs.
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "50px");
+ runThroughAnimation(testBinding, div);
+ testBinding.advanceClock(2000);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "500px");
+}, "animation-fill-mode: forwards should function correctly");
+
+test(function() {
+ let testBinding = new window.TestBinding();
+ let div = setAndTriggerAnimationOnElement("backwards");
+
+ // The style should reflect the first keyframe of the animation before the animation runs.
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "0px");
+ runThroughAnimation(testBinding, div);
+ testBinding.advanceClock(2000);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "50px");
+}, "animation-fill-mode: backwards should function correctly");
+
+test(function() {
+ let testBinding = new window.TestBinding();
+ let div = setAndTriggerAnimationOnElement("both", "reverse");
+
+ // The style should reflect the first keyframe of the animation before the animation runs.
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "500px");
+
+ testBinding.advanceClock(1000);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "500px");
+
+ testBinding.advanceClock(500);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "250px");
+
+ testBinding.advanceClock(500);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "0px");
+
+ testBinding.advanceClock(2000);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "0px");
+}, "animation-fill-mode: both on reversed animation");
+
+test(function() {
+ let testBinding = new window.TestBinding();
+ let div = setAndTriggerAnimationOnElement("both", "normal", "3");
+
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "0px");
+ runThroughAnimation(testBinding, div);
+ runThroughAnimation(testBinding, div);
+ runThroughAnimation(testBinding, div);
+ testBinding.advanceClock(1000);
+ assert_equals(getComputedStyle(div).getPropertyValue("width"), "500px");
+}, "animation-fill-mode: both on animation with multiple iterations");
+
+</script>