mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
parent
0a00ea3db3
commit
183f15d5aa
16 changed files with 241 additions and 96 deletions
|
@ -13,6 +13,7 @@ use crate::dom::{OpaqueNode, TElement, TNode};
|
||||||
use crate::font_metrics::FontMetricsProvider;
|
use crate::font_metrics::FontMetricsProvider;
|
||||||
use crate::properties::animated_properties::AnimationValue;
|
use crate::properties::animated_properties::AnimationValue;
|
||||||
use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
|
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::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
|
||||||
use crate::properties::LonghandIdSet;
|
use crate::properties::LonghandIdSet;
|
||||||
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
|
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
|
||||||
|
@ -184,6 +185,9 @@ pub struct Animation {
|
||||||
/// The delay of the animation.
|
/// The delay of the animation.
|
||||||
pub delay: f64,
|
pub delay: f64,
|
||||||
|
|
||||||
|
/// The `animation-fill-mode` property of this animation.
|
||||||
|
pub fill_mode: AnimationFillMode,
|
||||||
|
|
||||||
/// The current iteration state for the animation.
|
/// The current iteration state for the animation.
|
||||||
pub iteration_state: KeyframesIterationState,
|
pub iteration_state: KeyframesIterationState,
|
||||||
|
|
||||||
|
@ -324,9 +328,6 @@ impl Animation {
|
||||||
(&mut Paused(ref mut progress), Running) => {
|
(&mut Paused(ref mut progress), Running) => {
|
||||||
*progress = (now - old_started_at) / old_duration
|
*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>(
|
fn update_style<E>(
|
||||||
&self,
|
&self,
|
||||||
context: &SharedStyleContext,
|
context: &SharedStyleContext,
|
||||||
style: &mut ComputedValues,
|
style: &mut Arc<ComputedValues>,
|
||||||
font_metrics_provider: &dyn FontMetricsProvider,
|
font_metrics_provider: &dyn FontMetricsProvider,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
|
@ -370,46 +371,54 @@ impl Animation {
|
||||||
let started_at = self.started_at;
|
let started_at = self.started_at;
|
||||||
|
|
||||||
let now = match self.state {
|
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::Paused(progress) => started_at + duration * progress,
|
||||||
AnimationState::Canceled | AnimationState::Finished => return,
|
AnimationState::Canceled => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(!self.keyframes_animation.steps.is_empty());
|
debug_assert!(!self.keyframes_animation.steps.is_empty());
|
||||||
|
|
||||||
let mut total_progress = (now - started_at) / duration;
|
let mut total_progress = (now - started_at) / duration;
|
||||||
if total_progress < 0. {
|
if total_progress < 0. &&
|
||||||
warn!("Negative progress found for animation {:?}", self.name);
|
self.fill_mode != AnimationFillMode::Backwards &&
|
||||||
|
self.fill_mode != AnimationFillMode::Both
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if total_progress > 1. {
|
|
||||||
total_progress = 1.;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the target and the last keyframe position.
|
if total_progress > 1. &&
|
||||||
let last_keyframe_position;
|
self.fill_mode != AnimationFillMode::Forwards &&
|
||||||
let target_keyframe_position;
|
self.fill_mode != AnimationFillMode::Both
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
total_progress = total_progress.min(1.0).max(0.0);
|
||||||
|
|
||||||
|
// 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 {
|
match self.current_direction {
|
||||||
AnimationDirection::Normal => {
|
AnimationDirection::Normal => {
|
||||||
target_keyframe_position = self
|
next_keyframe_index = self
|
||||||
.keyframes_animation
|
.keyframes_animation
|
||||||
.steps
|
.steps
|
||||||
.iter()
|
.iter()
|
||||||
.position(|step| total_progress as f32 <= step.start_percentage.0);
|
.position(|step| total_progress as f32 <= step.start_percentage.0);
|
||||||
|
prev_keyframe_index = next_keyframe_index
|
||||||
last_keyframe_position = target_keyframe_position
|
|
||||||
.and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
|
.and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
},
|
},
|
||||||
AnimationDirection::Reverse => {
|
AnimationDirection::Reverse => {
|
||||||
target_keyframe_position = self
|
next_keyframe_index = self
|
||||||
.keyframes_animation
|
.keyframes_animation
|
||||||
.steps
|
.steps
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.position(|step| total_progress as f32 <= 1. - step.start_percentage.0)
|
.position(|step| total_progress as f32 <= 1. - step.start_percentage.0)
|
||||||
.map(|pos| self.keyframes_animation.steps.len() - pos - 1);
|
.map(|pos| self.keyframes_animation.steps.len() - pos - 1);
|
||||||
|
prev_keyframe_index = next_keyframe_index
|
||||||
last_keyframe_position = target_keyframe_position
|
|
||||||
.and_then(|pos| {
|
.and_then(|pos| {
|
||||||
if pos != self.keyframes_animation.steps.len() - 1 {
|
if pos != self.keyframes_animation.steps.len() - 1 {
|
||||||
Some(pos + 1)
|
Some(pos + 1)
|
||||||
|
@ -417,52 +426,83 @@ impl Animation {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or(self.keyframes_animation.steps.len() - 1);
|
.unwrap_or(self.keyframes_animation.steps.len() - 1)
|
||||||
},
|
},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Animation::update_style: keyframe from {:?} to {:?}",
|
"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],
|
Some(target) => &self.keyframes_animation.steps[target],
|
||||||
None => return,
|
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 =
|
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 relative_duration = relative_timespan as f64 * duration;
|
||||||
let last_keyframe_ended_at = match self.current_direction {
|
let last_keyframe_ended_at = match self.current_direction {
|
||||||
AnimationDirection::Normal => {
|
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 => {
|
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!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
|
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
|
// NB: The spec says that the timing function can be overwritten
|
||||||
// from the keyframe style.
|
// 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
|
// NB: animation_timing_function can never be empty, always has
|
||||||
// at least the default value (`ease`).
|
// 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 {
|
} else {
|
||||||
// TODO(mrobinson): It isn't optimal to have to walk this list every
|
// TODO(mrobinson): It isn't optimal to have to walk this list every
|
||||||
// time. Perhaps this should be stored in the animation.
|
// time. Perhaps this should be stored in the animation.
|
||||||
|
@ -477,18 +517,10 @@ impl Animation {
|
||||||
style.get_box().animation_timing_function_mod(index)
|
style.get_box().animation_timing_function_mod(index)
|
||||||
};
|
};
|
||||||
|
|
||||||
let target_style = compute_style_for_animation_step::<E>(
|
let mut new_style = (**style).clone();
|
||||||
context,
|
|
||||||
target_keyframe,
|
|
||||||
&from_style,
|
|
||||||
&self.cascade_style,
|
|
||||||
font_metrics_provider,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut new_style = (*style).clone();
|
|
||||||
let mut update_style_for_longhand = |longhand| {
|
let mut update_style_for_longhand = |longhand| {
|
||||||
let from = AnimationValue::from_computed_values(longhand, &from_style)?;
|
let from = AnimationValue::from_computed_values(longhand, &prev_keyframe_style)?;
|
||||||
let to = AnimationValue::from_computed_values(longhand, &target_style)?;
|
let to = AnimationValue::from_computed_values(longhand, &next_keyframe_style)?;
|
||||||
PropertyAnimation {
|
PropertyAnimation {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
|
@ -503,7 +535,7 @@ impl Animation {
|
||||||
update_style_for_longhand(property);
|
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`.
|
/// 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.
|
// Never apply canceled transitions to a style.
|
||||||
if self.state == AnimationState::Canceled {
|
if self.state == AnimationState::Canceled {
|
||||||
return;
|
return;
|
||||||
|
@ -568,7 +600,8 @@ impl Transition {
|
||||||
|
|
||||||
let progress = self.progress(context.current_time_for_animations);
|
let progress = self.progress(context.current_time_for_animations);
|
||||||
if progress >= 0.0 {
|
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
|
) where
|
||||||
E: TElement,
|
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 {
|
for animation in &self.animations {
|
||||||
animation.update_style::<E>(context, style, font_metrics);
|
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`.
|
/// Clear all canceled animations and transitions from this `ElementAnimationSet`.
|
||||||
pub fn clear_canceled_animations(&mut self) {
|
pub fn clear_canceled_animations(&mut self) {
|
||||||
self.animations
|
self.animations
|
||||||
|
@ -933,6 +951,7 @@ pub fn maybe_start_animations<E>(
|
||||||
keyframes_animation: anim.clone(),
|
keyframes_animation: anim.clone(),
|
||||||
started_at: animation_start,
|
started_at: animation_start,
|
||||||
duration: duration as f64,
|
duration: duration as f64,
|
||||||
|
fill_mode: box_style.animation_fill_mode_mod(i),
|
||||||
delay: delay as f64,
|
delay: delay as f64,
|
||||||
iteration_state,
|
iteration_state,
|
||||||
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.
|
// 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() {
|
for existing_animation in animation_state.animations.iter_mut() {
|
||||||
if existing_animation.state != AnimationState::Running {
|
if existing_animation.state == AnimationState::Canceled {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use crate::animation::AnimationState;
|
||||||
use crate::computed_value_flags::ComputedValueFlags;
|
use crate::computed_value_flags::ComputedValueFlags;
|
||||||
use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
|
use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
|
||||||
use crate::context::{SharedStyleContext, StyleContext};
|
use crate::context::{SharedStyleContext, StyleContext};
|
||||||
|
@ -458,9 +459,14 @@ trait PrivateMatchMethods: TElement {
|
||||||
&context.thread_local.font_metrics_provider,
|
&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
|
// If the ElementAnimationSet is empty, and don't store it in order to
|
||||||
// save memory and to avoid extra processing later.
|
// save memory and to avoid extra processing later.
|
||||||
animation_state.clear_finished_animations();
|
|
||||||
if !animation_state.is_empty() {
|
if !animation_state.is_empty() {
|
||||||
animation_states.insert(this_opaque, animation_state);
|
animation_states.insert(this_opaque, animation_state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,7 +312,7 @@ ${helpers.single_keyword(
|
||||||
${helpers.single_keyword(
|
${helpers.single_keyword(
|
||||||
"animation-fill-mode",
|
"animation-fill-mode",
|
||||||
"none forwards backwards both",
|
"none forwards backwards both",
|
||||||
engines="gecko servo-2013",
|
engines="gecko servo-2013 servo-2020",
|
||||||
need_index=True,
|
need_index=True,
|
||||||
animation_value_type="none",
|
animation_value_type="none",
|
||||||
vector=True,
|
vector=True,
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[animation-delay-010.html]
|
[animation-delay-010.html]
|
||||||
bug: https://github.com/servo/servo/issues/17335
|
bug: https://github.com/servo/servo/issues/17335
|
||||||
expected: TIMEOUT
|
expected: FAIL
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
[variable-animation-substitute-into-keyframe-shorthand.html]
|
[variable-animation-substitute-into-keyframe-shorthand.html]
|
||||||
bug: https://github.com/servo/servo/issues/21564
|
bug: https://github.com/servo/servo/issues/21564
|
||||||
[Verify border-bottom-color before animation]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
[variable-animation-substitute-into-keyframe.html]
|
[variable-animation-substitute-into-keyframe.html]
|
||||||
bug: https://github.com/servo/servo/issues/21564
|
bug: https://github.com/servo/servo/issues/21564
|
||||||
[Verify color before animation]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
[variable-animation-substitute-within-keyframe-fallback.html]
|
[variable-animation-substitute-within-keyframe-fallback.html]
|
||||||
bug: https://github.com/servo/servo/issues/21564
|
bug: https://github.com/servo/servo/issues/21564
|
||||||
[Verify color before animation]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Verify color after animation]
|
[Verify color after animation]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
[variable-animation-substitute-within-keyframe-multiple.html]
|
[variable-animation-substitute-within-keyframe-multiple.html]
|
||||||
bug: https://github.com/servo/servo/issues/21564
|
bug: https://github.com/servo/servo/issues/21564
|
||||||
[Verify color before animation]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Verify color after animation]
|
[Verify color after animation]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
[variable-animation-substitute-within-keyframe.html]
|
[variable-animation-substitute-within-keyframe.html]
|
||||||
bug: https://github.com/servo/servo/issues/21564
|
bug: https://github.com/servo/servo/issues/21564
|
||||||
[Verify color before animation]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Verify color after animation]
|
[Verify color after animation]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
prefs: ["layout.animations.test.enabled:true",
|
||||||
|
"dom.testbinding.enabled:true"]
|
|
@ -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
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[basic-transition.html]
|
|
||||||
expected: ERROR
|
|
||||||
[Transition test]
|
|
||||||
expected: TIMEOUT
|
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
[transition-raf.html]
|
[transition-raf.html]
|
||||||
expected: ERROR
|
expected: TIMEOUT
|
||||||
|
[Transitions should work during RAF loop]
|
||||||
|
expected: TIMEOUT
|
||||||
|
|
||||||
|
|
|
@ -12849,6 +12849,13 @@
|
||||||
},
|
},
|
||||||
"css": {
|
"css": {
|
||||||
"animations": {
|
"animations": {
|
||||||
|
"animation-fill-mode.html": [
|
||||||
|
"4cfaab9fbce0adccd83f592935e63fa8ff58a1cf",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"basic-linear-width.html": [
|
"basic-linear-width.html": [
|
||||||
"634b09dca5924b8bea58ac8532d9d46c20d8a0ad",
|
"634b09dca5924b8bea58ac8532d9d46c20d8a0ad",
|
||||||
[
|
[
|
||||||
|
|
116
tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
Normal file
116
tests/wpt/mozilla/tests/css/animations/animation-fill-mode.html
Normal file
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue