Auto merge of #26244 - mrobinson:animation-cancel, r=jdm

Add support for canceling CSS transitions

This change adds support for canceling CSS transitions when a property is
no longer transitionable. Support for canceling and replacing CSS
transitions when the end value changes is still pending. This change
also takes advantage of updating the constellation message to fix a bug
where transition events could be sent for closed pipelines.

Fixes #15079.

<!-- 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] These changes fix #15079.

<!-- Either: -->
- [x] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- 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. -->
This commit is contained in:
bors-servo 2020-04-22 15:34:06 -04:00 committed by GitHub
commit 610991bdfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 351 additions and 153 deletions

View file

@ -15,7 +15,7 @@ use crate::font_metrics::FontMetricsProvider;
use crate::properties::animated_properties::{AnimatedProperty, TransitionPropertyIteration};
use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId, LonghandIdSet};
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
use crate::stylesheets::Origin;
use crate::timer::Timer;
@ -245,6 +245,17 @@ impl Animation {
Animation::Keyframes(..) => false,
}
}
/// Whether this animation has the same end value as another one.
#[inline]
pub fn is_transition_with_same_end_value(&self, other_animation: &PropertyAnimation) -> bool {
match *self {
Animation::Transition(_, _, ref animation) => {
animation.has_the_same_end_value_as(other_animation)
},
Animation::Keyframes(..) => false,
}
}
}
/// Represents an animation for a given property.
@ -261,6 +272,11 @@ pub struct PropertyAnimation {
}
impl PropertyAnimation {
/// Returns the given property longhand id.
pub fn property_id(&self) -> LonghandId {
self.property.id()
}
/// Returns the given property name.
pub fn property_name(&self) -> &'static str {
self.property.name()
@ -351,20 +367,86 @@ impl PropertyAnimation {
}
}
/// Inserts transitions into the queue of running animations as applicable for
/// the given style difference. This is called from the layout worker threads.
/// Returns true if any animations were kicked off and false otherwise.
pub fn start_transitions_if_applicable(
/// Start any new transitions for this node and ensure that any existing transitions
/// that are cancelled are marked as cancelled in the SharedStyleContext. This is
/// at the end of calculating style for a single node.
#[cfg(feature = "servo")]
pub fn update_transitions(
context: &SharedStyleContext,
new_animations_sender: &Sender<Animation>,
opaque_node: OpaqueNode,
old_style: &ComputedValues,
new_style: &mut Arc<ComputedValues>,
timer: &Timer,
running_and_expired_transitions: &[PropertyAnimation],
) -> bool {
let mut had_animations = false;
expired_transitions: &[PropertyAnimation],
) {
let mut all_running_animations = context.running_animations.write();
let previously_running_animations = all_running_animations
.remove(&opaque_node)
.unwrap_or_else(Vec::new);
let properties_that_transition = start_transitions_if_applicable(
context,
new_animations_sender,
opaque_node,
old_style,
new_style,
expired_transitions,
&previously_running_animations,
);
let mut all_cancelled_animations = context.cancelled_animations.write();
let mut cancelled_animations = all_cancelled_animations
.remove(&opaque_node)
.unwrap_or_else(Vec::new);
let mut running_animations = vec![];
// For every animation that was running before this style change, we cancel it
// if the property no longer transitions.
for running_animation in previously_running_animations.into_iter() {
if let Animation::Transition(_, _, ref property_animation) = running_animation {
if !properties_that_transition.contains(property_animation.property_id()) {
cancelled_animations.push(running_animation);
continue;
}
}
running_animations.push(running_animation);
}
if !cancelled_animations.is_empty() {
all_cancelled_animations.insert(opaque_node, cancelled_animations);
}
if !running_animations.is_empty() {
all_running_animations.insert(opaque_node, running_animations);
}
}
/// Kick off any new transitions for this node and return all of the properties that are
/// transitioning. This is at the end of calculating style for a single node.
#[cfg(feature = "servo")]
pub fn start_transitions_if_applicable(
context: &SharedStyleContext,
new_animations_sender: &Sender<Animation>,
opaque_node: OpaqueNode,
old_style: &ComputedValues,
new_style: &mut Arc<ComputedValues>,
expired_transitions: &[PropertyAnimation],
running_animations: &[Animation],
) -> LonghandIdSet {
// If the style of this element is display:none, then we don't start any transitions
// and we cancel any currently running transitions by returning an empty LonghandIdSet.
if new_style.get_box().clone_display().is_none() {
return LonghandIdSet::new();
}
let mut properties_that_transition = LonghandIdSet::new();
let transitions: Vec<TransitionPropertyIteration> = new_style.transition_properties().collect();
for transition in &transitions {
if properties_that_transition.contains(transition.longhand_id) {
continue;
} else {
properties_that_transition.insert(transition.longhand_id);
}
let property_animation = match PropertyAnimation::from_longhand(
transition.longhand_id,
new_style
@ -387,20 +469,27 @@ pub fn start_transitions_if_applicable(
property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0);
// Per [1], don't trigger a new transition if the end state for that
// transition is the same as that of a transition that's already
// running on the same node.
//
// transition is the same as that of a transition that's expired.
// [1]: https://drafts.csswg.org/css-transitions/#starting
debug!(
"checking {:?} for matching end value",
running_and_expired_transitions
);
if running_and_expired_transitions
debug!("checking {:?} for matching end value", expired_transitions);
if expired_transitions
.iter()
.any(|animation| animation.has_the_same_end_value_as(&property_animation))
{
debug!(
"Not initiating transition for {}, other transition \
"Not initiating transition for {}, expired transition \
found with the same end value",
property_animation.property_name()
);
continue;
}
if running_animations
.iter()
.any(|animation| animation.is_transition_with_same_end_value(&property_animation))
{
debug!(
"Not initiating transition for {}, running transition \
found with the same end value",
property_animation.property_name()
);
@ -410,7 +499,7 @@ pub fn start_transitions_if_applicable(
// Kick off the animation.
debug!("Kicking off transition of {:?}", property_animation);
let box_style = new_style.get_box();
let now = timer.seconds();
let now = context.timer.seconds();
let start_time = now + (box_style.transition_delay_mod(transition.index).seconds() as f64);
new_animations_sender
.send(Animation::Transition(
@ -419,11 +508,9 @@ pub fn start_transitions_if_applicable(
property_animation,
))
.unwrap();
had_animations = true;
}
had_animations
properties_that_transition
}
fn compute_style_for_animation_step<E>(
@ -608,12 +695,6 @@ where
if progress >= 0.0 {
property_animation.update(Arc::make_mut(style), progress);
}
// FIXME(emilio): Should check before updating the style that the
// transition_property still transitions this, or bail out if not.
//
// Or doing it in process_animations, only if transition_property
// changed somehow (even better).
AnimationUpdate::Regular
},
Animation::Keyframes(_, ref animation, ref name, ref state) => {

View file

@ -194,6 +194,10 @@ pub struct SharedStyleContext<'a> {
#[cfg(feature = "servo")]
pub expired_animations: Arc<RwLock<FxHashMap<OpaqueNode, Vec<Animation>>>>,
/// The list of animations that have expired since the last style recalculation.
#[cfg(feature = "servo")]
pub cancelled_animations: Arc<RwLock<FxHashMap<OpaqueNode, Vec<Animation>>>>,
/// Paint worklets
#[cfg(feature = "servo")]
pub registered_speculative_painters: &'a dyn RegisteredSpeculativePainters,

View file

@ -440,7 +440,7 @@ trait PrivateMatchMethods: TElement {
use crate::animation;
let this_opaque = self.as_node().opaque();
let mut running_and_expired_transitions = vec![];
let mut expired_transitions = vec![];
let shared_context = context.shared;
if let Some(ref mut old_values) = *old_values {
// We apply the expired transitions and animations to the old style
@ -454,14 +454,13 @@ trait PrivateMatchMethods: TElement {
shared_context,
this_opaque,
old_values,
&mut running_and_expired_transitions,
&mut expired_transitions,
);
Self::update_style_for_animations_and_collect_running_transitions(
Self::update_style_for_animations(
shared_context,
this_opaque,
old_values,
&mut running_and_expired_transitions,
&context.thread_local.font_metrics_provider,
);
}
@ -479,13 +478,13 @@ trait PrivateMatchMethods: TElement {
// Trigger transitions if necessary. This will set `new_values` to
// the starting value of the transition if it did trigger a transition.
if let Some(ref values) = old_values {
animation::start_transitions_if_applicable(
animation::update_transitions(
&shared_context,
new_animations_sender,
this_opaque,
&values,
new_values,
&shared_context.timer,
&running_and_expired_transitions,
&expired_transitions,
);
}
}
@ -627,33 +626,30 @@ trait PrivateMatchMethods: TElement {
}
#[cfg(feature = "servo")]
fn update_style_for_animations_and_collect_running_transitions(
fn update_style_for_animations(
context: &SharedStyleContext,
node: OpaqueNode,
style: &mut Arc<ComputedValues>,
running_transitions: &mut Vec<crate::animation::PropertyAnimation>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) {
use crate::animation::{self, Animation, AnimationUpdate};
let had_running_animations = context.running_animations.read().get(&node).is_some();
if !had_running_animations {
return;
}
let mut all_running_animations = context.running_animations.write();
for mut running_animation in all_running_animations.get_mut(&node).unwrap() {
if let Animation::Transition(_, _, ref property_animation) = *running_animation {
running_transitions.push(property_animation.clone());
continue;
}
let running_animations = match all_running_animations.get_mut(&node) {
Some(running_animations) => running_animations,
None => return,
};
let update = animation::update_style_for_animation::<Self>(
context,
&mut running_animation,
style,
font_metrics,
);
for running_animation in running_animations.iter_mut() {
let update = match *running_animation {
Animation::Transition(..) => continue,
Animation::Keyframes(..) => animation::update_style_for_animation::<Self>(
context,
running_animation,
style,
font_metrics,
),
};
match *running_animation {
Animation::Transition(..) => unreachable!(),