mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Use a restyle for animation ticks
This change corrects synchronization issues with animations, by reworking the animation processing model to do a quick restyle and incremental layout when ticking animations. While this change adds overhead to animation ticks, the idea is that this will be the fallback when synchronous behavior is required to fulfill specification requirements. In the optimistic case, many animations could be updated and applied off-the-main-thread and then resynchronized when style information is queried by script. Fixes #13865.
This commit is contained in:
parent
5e44327325
commit
b585ce5b1f
39 changed files with 1285 additions and 1662 deletions
|
@ -4,7 +4,6 @@
|
|||
|
||||
//! CSS transitions and animations.
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::items::OpaqueNode;
|
||||
use crate::flow::{Flow, GetBaseFlow};
|
||||
use crate::opaque_node::OpaqueNodeMethods;
|
||||
|
@ -16,54 +15,27 @@ use script_traits::{
|
|||
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg,
|
||||
TransitionOrAnimationEventType,
|
||||
};
|
||||
use servo_arc::Arc;
|
||||
use style::animation::{update_style_for_animation, Animation, ElementAnimationState};
|
||||
use style::dom::TElement;
|
||||
use style::font_metrics::ServoMetricsProvider;
|
||||
use style::selector_parser::RestyleDamage;
|
||||
use style::timer::Timer;
|
||||
use style::animation::{Animation, ElementAnimationState};
|
||||
|
||||
/// Collect newly animating nodes, which is used by the script process during
|
||||
/// forced, synchronous reflows to root DOM nodes for the duration of their
|
||||
/// animations or transitions.
|
||||
pub fn collect_newly_animating_nodes(
|
||||
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
|
||||
mut out: Option<&mut Vec<UntrustedNodeAddress>>,
|
||||
) {
|
||||
// This extends the output vector with an iterator that contains a copy of the node
|
||||
// address for every new animation. This is a bit goofy, but the script thread
|
||||
// currently stores a rooted node for every property that is transitioning.
|
||||
if let Some(ref mut out) = out {
|
||||
out.extend(animation_states.iter().flat_map(|(node, state)| {
|
||||
std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len())
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes any new animations that were discovered after style recalculation. Also
|
||||
/// finish any animations that have completed, inserting them into `finished_animations`.
|
||||
pub fn update_animation_states(
|
||||
/// Processes any new animations that were discovered after style recalculation and
|
||||
/// remove animations for any disconnected nodes. Send messages that trigger events
|
||||
/// for any events that changed state.
|
||||
pub fn do_post_style_animations_update(
|
||||
constellation_chan: &IpcSender<ConstellationMsg>,
|
||||
script_chan: &IpcSender<ConstellationControlMsg>,
|
||||
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>,
|
||||
invalid_nodes: FxHashSet<OpaqueNode>,
|
||||
pipeline_id: PipelineId,
|
||||
timer: &Timer,
|
||||
now: f64,
|
||||
out: &mut Vec<UntrustedNodeAddress>,
|
||||
root_flow: &mut dyn Flow,
|
||||
) {
|
||||
let had_running_animations = animation_states
|
||||
.values()
|
||||
.any(|state| !state.running_animations.is_empty());
|
||||
|
||||
// Cancel all animations on any invalid nodes. These entries will later
|
||||
// be removed from the list of states, because their states will become
|
||||
// empty.
|
||||
for node in &invalid_nodes {
|
||||
if let Some(mut state) = animation_states.remove(node) {
|
||||
state.cancel_all_animations();
|
||||
}
|
||||
}
|
||||
cancel_animations_for_disconnected_nodes(animation_states, root_flow);
|
||||
collect_newly_animating_nodes(animation_states, out);
|
||||
|
||||
let now = timer.seconds();
|
||||
let mut have_running_animations = false;
|
||||
for (node, animation_state) in animation_states.iter_mut() {
|
||||
update_animation_state(script_chan, animation_state, pipeline_id, now, *node);
|
||||
|
@ -89,7 +61,50 @@ pub fn update_animation_states(
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn update_animation_state(
|
||||
/// Collect newly animating nodes, which is used by the script process during
|
||||
/// forced, synchronous reflows to root DOM nodes for the duration of their
|
||||
/// animations or transitions.
|
||||
pub fn collect_newly_animating_nodes(
|
||||
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
|
||||
out: &mut Vec<UntrustedNodeAddress>,
|
||||
) {
|
||||
// This extends the output vector with an iterator that contains a copy of the node
|
||||
// address for every new animation. This is a bit goofy, but the script thread
|
||||
// currently stores a rooted node for every property that is transitioning.
|
||||
out.extend(animation_states.iter().flat_map(|(node, state)| {
|
||||
std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Cancel animations for any nodes which have been removed from the DOM or are display:none.
|
||||
/// We detect this by looking for nodes that are used in the flow tree.
|
||||
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
|
||||
/// This also doesn't yet handles nodes that have been reparented.
|
||||
pub fn cancel_animations_for_disconnected_nodes(
|
||||
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>,
|
||||
root_flow: &mut dyn Flow,
|
||||
) {
|
||||
// Assume all nodes have been removed until proven otherwise.
|
||||
let mut invalid_nodes: FxHashSet<OpaqueNode> = animation_states.keys().cloned().collect();
|
||||
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<OpaqueNode>) {
|
||||
flow.mutate_fragments(&mut |fragment| {
|
||||
invalid_nodes.remove(&fragment.node);
|
||||
});
|
||||
for kid in flow.mut_base().children.iter_mut() {
|
||||
traverse_flow(kid, invalid_nodes)
|
||||
}
|
||||
}
|
||||
traverse_flow(root_flow, &mut invalid_nodes);
|
||||
|
||||
// Cancel animations for any nodes that are no longer in the flow tree.
|
||||
for node in &invalid_nodes {
|
||||
if let Some(state) = animation_states.get_mut(node) {
|
||||
state.cancel_all_animations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_animation_state(
|
||||
script_channel: &IpcSender<ConstellationControlMsg>,
|
||||
animation_state: &mut ElementAnimationState,
|
||||
pipeline_id: PipelineId,
|
||||
|
@ -122,36 +137,28 @@ pub fn update_animation_state(
|
|||
|
||||
/// Walk through the list of running animations and remove all of the ones that
|
||||
/// have ended.
|
||||
pub fn handle_running_animations(
|
||||
fn handle_running_animations(
|
||||
animation_state: &mut ElementAnimationState,
|
||||
now: f64,
|
||||
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
|
||||
) {
|
||||
if animation_state.running_animations.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut running_animations =
|
||||
std::mem::replace(&mut animation_state.running_animations, Vec::new());
|
||||
for mut running_animation in running_animations.drain(..) {
|
||||
let still_running = match running_animation {
|
||||
Animation::Transition(_, started_at, ref property_animation) => {
|
||||
now < started_at + (property_animation.duration)
|
||||
},
|
||||
Animation::Keyframes(_, _, _, ref mut state) => {
|
||||
// This animation is still running, or we need to keep
|
||||
// iterating.
|
||||
now < state.started_at + state.duration || state.tick()
|
||||
},
|
||||
};
|
||||
|
||||
for running_animation in running_animations.drain(..) {
|
||||
// If the animation is still running, add it back to the list of running animations.
|
||||
if still_running {
|
||||
if !running_animation.has_ended(now) {
|
||||
animation_state.running_animations.push(running_animation);
|
||||
} else {
|
||||
debug!("Finishing transition: {:?}", running_animation);
|
||||
let (event_type, elapsed_time) = match running_animation {
|
||||
Animation::Transition(_, _, ref property_animation) => (
|
||||
TransitionOrAnimationEventType::TransitionEnd,
|
||||
property_animation.duration,
|
||||
),
|
||||
Animation::Keyframes(_, _, _, ref mut state) => (
|
||||
Animation::Keyframes(_, _, _, ref state) => (
|
||||
TransitionOrAnimationEventType::AnimationEnd,
|
||||
state.active_duration(),
|
||||
),
|
||||
|
@ -166,7 +173,7 @@ pub fn handle_running_animations(
|
|||
/// Send events for cancelled animations. Currently this only handles cancelled
|
||||
/// transitions, but eventually this should handle cancelled CSS animations as
|
||||
/// well.
|
||||
pub fn handle_cancelled_animations(
|
||||
fn handle_cancelled_animations(
|
||||
animation_state: &mut ElementAnimationState,
|
||||
now: f64,
|
||||
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
|
||||
|
@ -188,7 +195,7 @@ pub fn handle_cancelled_animations(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_new_animations(
|
||||
fn handle_new_animations(
|
||||
animation_state: &mut ElementAnimationState,
|
||||
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
|
||||
) {
|
||||
|
@ -208,55 +215,3 @@ pub fn handle_new_animations(
|
|||
animation_state.running_animations.push(animation);
|
||||
}
|
||||
}
|
||||
|
||||
/// Recalculates style for a set of animations. This does *not* run with the DOM
|
||||
/// lock held. Returns a set of nodes associated with animations that are no longer
|
||||
/// valid.
|
||||
pub fn recalc_style_for_animations<E>(
|
||||
context: &LayoutContext,
|
||||
flow: &mut dyn Flow,
|
||||
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
|
||||
) -> FxHashSet<OpaqueNode>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
let mut invalid_nodes = animation_states.keys().cloned().collect();
|
||||
do_recalc_style_for_animations::<E>(context, flow, animation_states, &mut invalid_nodes);
|
||||
invalid_nodes
|
||||
}
|
||||
|
||||
fn do_recalc_style_for_animations<E>(
|
||||
context: &LayoutContext,
|
||||
flow: &mut dyn Flow,
|
||||
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
|
||||
invalid_nodes: &mut FxHashSet<OpaqueNode>,
|
||||
) where
|
||||
E: TElement,
|
||||
{
|
||||
let mut damage = RestyleDamage::empty();
|
||||
flow.mutate_fragments(&mut |fragment| {
|
||||
let animations = match animation_states.get(&fragment.node) {
|
||||
Some(state) => &state.running_animations,
|
||||
None => return,
|
||||
};
|
||||
|
||||
invalid_nodes.remove(&fragment.node);
|
||||
for animation in animations.iter() {
|
||||
let old_style = fragment.style.clone();
|
||||
update_style_for_animation::<E>(
|
||||
&context.style_context,
|
||||
animation,
|
||||
Arc::make_mut(&mut fragment.style),
|
||||
&ServoMetricsProvider,
|
||||
);
|
||||
let difference = RestyleDamage::compute_style_difference(&old_style, &fragment.style);
|
||||
damage |= difference.damage;
|
||||
}
|
||||
});
|
||||
|
||||
let base = flow.mut_base();
|
||||
base.restyle_damage.insert(damage);
|
||||
for kid in base.children.iter_mut() {
|
||||
do_recalc_style_for_animations::<E>(context, kid, animation_states, invalid_nodes)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue