Move most animation processing to script

This is preparation for sharing this code with layout_2020 and
implementing selective off-the-main-thread animations.

We still look for nodes not in the flow tree in the layout thread.
This commit is contained in:
Martin Robinson 2020-05-07 18:37:18 +02:00
parent aa9f16ce45
commit 3b0619aedd
21 changed files with 444 additions and 371 deletions

View file

@ -1,218 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! CSS transitions and animations.
use crate::display_list::items::OpaqueNode;
use crate::flow::{Flow, GetBaseFlow};
use crate::opaque_node::OpaqueNodeMethods;
use fxhash::{FxHashMap, FxHashSet};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{
AnimationState as AnimationsPresentState, ConstellationControlMsg,
LayoutMsg as ConstellationMsg, TransitionOrAnimationEvent, TransitionOrAnimationEventType,
};
use style::animation::{AnimationState, ElementAnimationSet};
/// 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, ElementAnimationSet>,
pipeline_id: PipelineId,
now: f64,
out: &mut Vec<UntrustedNodeAddress>,
root_flow: &mut dyn Flow,
) {
let had_running_animations = animation_states
.values()
.any(|state| state.needs_animation_ticks());
cancel_animations_for_disconnected_nodes(animation_states, root_flow);
collect_newly_animating_nodes(animation_states, out);
for (node, animation_state) in animation_states.iter_mut() {
update_animation_state(script_chan, animation_state, pipeline_id, now, *node);
}
// Remove empty states from our collection of states in order to free
// up space as soon as we are no longer tracking any animations for
// a node.
animation_states.retain(|_, state| !state.is_empty());
let have_running_animations = animation_states
.values()
.any(|state| state.needs_animation_ticks());
let present = match (had_running_animations, have_running_animations) {
(true, false) => AnimationsPresentState::NoAnimationsPresent,
(false, true) => AnimationsPresentState::AnimationsPresent,
_ => return,
};
constellation_chan
.send(ConstellationMsg::ChangeRunningAnimationsState(
pipeline_id,
present,
))
.unwrap();
}
/// 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, ElementAnimationSet>,
out: &mut Vec<UntrustedNodeAddress>,
) {
// This extends the output vector with an iterator that contains a copy of the node
// address for every new animation. The script thread currently stores a rooted node
// for every property that is transitioning. The current strategy of repeating the
// node address is a holdover from when the code here looked different.
out.extend(animation_states.iter().flat_map(|(node, state)| {
let mut num_new_animations = state
.animations
.iter()
.filter(|animation| animation.is_new)
.count();
num_new_animations += state
.transitions
.iter()
.filter(|transition| transition.is_new)
.count();
std::iter::repeat(node.to_untrusted_node_address()).take(num_new_animations)
}));
}
/// 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, ElementAnimationSet>,
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 ElementAnimationSet,
pipeline_id: PipelineId,
now: f64,
node: OpaqueNode,
) {
let send_event = |property_or_animation_name, event_type, elapsed_time| {
script_channel
.send(ConstellationControlMsg::TransitionOrAnimationEvent(
TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: node.to_untrusted_node_address(),
property_or_animation_name,
elapsed_time,
},
))
.unwrap()
};
handle_canceled_animations(animation_state, now, send_event);
finish_running_animations(animation_state, now, send_event);
handle_new_animations(animation_state, send_event);
}
/// Walk through the list of running animations and remove all of the ones that
/// have ended.
fn finish_running_animations(
animation_state: &mut ElementAnimationSet,
now: f64,
mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) {
for animation in animation_state.animations.iter_mut() {
if animation.state == AnimationState::Running && animation.has_ended(now) {
animation.state = AnimationState::Finished;
send_event(
animation.name.to_string(),
TransitionOrAnimationEventType::AnimationEnd,
animation.active_duration(),
);
}
}
for transition in animation_state.transitions.iter_mut() {
if transition.state == AnimationState::Running && transition.has_ended(now) {
transition.state = AnimationState::Finished;
send_event(
transition.property_animation.property_name().into(),
TransitionOrAnimationEventType::TransitionEnd,
transition.property_animation.duration,
);
}
}
}
/// Send events for canceled animations. Currently this only handles canceled
/// transitions, but eventually this should handle canceled CSS animations as
/// well.
fn handle_canceled_animations(
animation_state: &mut ElementAnimationSet,
now: f64,
mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) {
for transition in &animation_state.transitions {
if transition.state == AnimationState::Canceled {
// TODO(mrobinson): We need to properly compute the elapsed_time here
// according to https://drafts.csswg.org/css-transitions/#event-transitionevent
send_event(
transition.property_animation.property_name().into(),
TransitionOrAnimationEventType::TransitionCancel,
(now - transition.start_time).max(0.),
);
}
}
// TODO(mrobinson): We need to send animationcancel events.
animation_state.clear_canceled_animations();
}
fn handle_new_animations(
animation_state: &mut ElementAnimationSet,
mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) {
for animation in animation_state.animations.iter_mut() {
animation.is_new = false;
}
for transition in animation_state.transitions.iter_mut() {
if transition.is_new {
// TODO(mrobinson): We need to properly compute the elapsed_time here
// according to https://drafts.csswg.org/css-transitions/#event-transitionevent
send_event(
transition.property_animation.property_name().into(),
TransitionOrAnimationEventType::TransitionRun,
0.,
);
transition.is_new = false;
}
}
}

View file

@ -17,7 +17,6 @@ use net_traits::image_cache::{
use parking_lot::RwLock;
use script_layout_interface::{PendingImage, PendingImageState};
use script_traits::Painter;
use script_traits::UntrustedNodeAddress;
use servo_atoms::Atom;
use servo_url::{ImmutableOrigin, ServoUrl};
use std::cell::{RefCell, RefMut};
@ -85,9 +84,6 @@ pub struct LayoutContext<'a> {
/// A list of in-progress image loads to be shared with the script thread.
pub pending_images: Mutex<Vec<PendingImage>>,
/// A list of nodes that have just initiated a CSS transition or animation.
pub newly_animating_nodes: Mutex<Vec<UntrustedNodeAddress>>,
}
impl<'a> Drop for LayoutContext<'a> {

View file

@ -20,7 +20,6 @@ extern crate serde;
#[macro_use]
pub mod layout_debug;
pub mod animation;
mod block;
pub mod construct;
pub mod context;