diff --git a/Cargo.lock b/Cargo.lock index 339ff1aa6a4..827c3d32848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5412,7 +5412,6 @@ dependencies = [ "bindgen", "bitflags", "byteorder", - "crossbeam-channel", "cssparser", "derive_more", "encoding_rs", diff --git a/components/layout/animation.rs b/components/layout/animation.rs index a60c0e484f9..185b6d9c9d1 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -8,7 +8,6 @@ use crate::context::LayoutContext; use crate::display_list::items::OpaqueNode; use crate::flow::{Flow, GetBaseFlow}; use crate::opaque_node::OpaqueNodeMethods; -use crossbeam_channel::Receiver; use fxhash::{FxHashMap, FxHashSet}; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; @@ -16,95 +15,106 @@ use script_traits::UntrustedNodeAddress; use script_traits::{ AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, TransitionEventType, }; -use style::animation::{update_style_for_animation, Animation}; +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; -/// Processes any new animations that were discovered after style recalculation. -/// Also expire any old animations that have completed, inserting them into -/// `expired_animations`. -pub fn update_animation_state( +/// 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( constellation_chan: &IpcSender, script_chan: &IpcSender, - running_animations: &mut FxHashMap>, - expired_animations: &mut FxHashMap>, - cancelled_animations: &mut FxHashMap>, - mut keys_to_remove: FxHashSet, + animation_states: &mut FxHashMap, + invalid_nodes: FxHashSet, mut newly_transitioning_nodes: Option<&mut Vec>, - new_animations_receiver: &Receiver, pipeline_id: PipelineId, timer: &Timer, ) where E: TElement, { - send_events_for_cancelled_animations(script_chan, cancelled_animations, pipeline_id); + let had_running_animations = animation_states + .values() + .any(|state| !state.running_animations.is_empty()); - let mut new_running_animations = vec![]; - while let Ok(animation) = new_animations_receiver.try_recv() { - let mut should_push = true; - if let Animation::Keyframes(ref node, _, ref name, ref state) = animation { - // If the animation was already present in the list for the - // node, just update its state, else push the new animation to - // run. - if let Some(ref mut animations) = running_animations.get_mut(node) { - // TODO: This being linear is probably not optimal. - for anim in animations.iter_mut() { - if let Animation::Keyframes(_, _, ref anim_name, ref mut anim_state) = *anim { - if *name == *anim_name { - debug!("update_animation_state: Found other animation {}", name); - anim_state.update_from_other(&state, timer); - should_push = false; - break; - } - } - } - } + for node in &invalid_nodes { + if let Some(mut state) = animation_states.remove(node) { + state.cancel_all_animations(); + send_events_for_cancelled_animations(script_chan, &mut state, pipeline_id); } - - if should_push { - new_running_animations.push(animation); - } - } - - if running_animations.is_empty() && new_running_animations.is_empty() { - // Nothing to do. Return early so we don't flood the compositor with - // `ChangeRunningAnimationsState` messages. - return; } let now = timer.seconds(); - // Expire old running animations. - // - // TODO: Do not expunge Keyframes animations, since we need that state if - // the animation gets re-triggered. Probably worth splitting in two - // different maps, or at least using a linked list? - for (key, running_animations) in running_animations.iter_mut() { - let mut animations_still_running = vec![]; - for mut running_animation in running_animations.drain(..) { - let still_running = !running_animation.is_expired() && - 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() - }, - }; - - debug!( - "update_animation_state({:?}): {:?}", - still_running, running_animation - ); - - if still_running { - animations_still_running.push(running_animation); - continue; + let mut have_running_animations = false; + for (node, animation_state) in animation_states.iter_mut() { + // TODO(mrobinson): This should really be triggering transitionrun messages + // on the script thread. + if let Some(ref mut newly_transitioning_nodes) = newly_transitioning_nodes { + let number_of_new_transitions = animation_state + .new_animations + .iter() + .filter(|animation| animation.is_transition()) + .count(); + for _ in 0..number_of_new_transitions { + newly_transitioning_nodes.push(node.to_untrusted_node_address()); } + } + update_animation_state::(script_chan, animation_state, pipeline_id, now); + + have_running_animations = + have_running_animations || !animation_state.running_animations.is_empty(); + } + + // 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 present = match (had_running_animations, have_running_animations) { + (true, false) => AnimationState::NoAnimationsPresent, + (false, true) => AnimationState::AnimationsPresent, + _ => return, + }; + constellation_chan + .send(ConstellationMsg::ChangeRunningAnimationsState( + pipeline_id, + present, + )) + .unwrap(); +} + +pub fn update_animation_state( + script_chan: &IpcSender, + animation_state: &mut ElementAnimationState, + pipeline_id: PipelineId, + now: f64, +) where + E: TElement, +{ + send_events_for_cancelled_animations(script_chan, animation_state, pipeline_id); + + 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 = !running_animation.is_expired() && + 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() + }, + }; + + // If the animation is still running, add it back to the list of running animations. + if still_running { + animation_state.running_animations.push(running_animation); + } else { + debug!("Finishing transition: {:?}", running_animation); if let Animation::Transition(node, _, ref property_animation) = running_animation { script_chan .send(ConstellationControlMsg::TransitionEvent { @@ -116,56 +126,13 @@ pub fn update_animation_state( }) .unwrap(); } - - debug!("expiring animation for {:?}", running_animation); - expired_animations - .entry(*key) - .or_insert_with(Vec::new) - .push(running_animation); - } - - if animations_still_running.is_empty() { - keys_to_remove.insert(*key); - } else { - *running_animations = animations_still_running + animation_state.finished_animations.push(running_animation); } } - for key in keys_to_remove { - running_animations.remove(&key).unwrap(); - } - - // Add new running animations. - for new_running_animation in new_running_animations { - if new_running_animation.is_transition() { - match newly_transitioning_nodes { - Some(ref mut nodes) => { - nodes.push(new_running_animation.node().to_untrusted_node_address()); - }, - None => { - warn!("New transition encountered from compositor-initiated layout."); - }, - } - } - - running_animations - .entry(*new_running_animation.node()) - .or_insert_with(Vec::new) - .push(new_running_animation) - } - - let animation_state = if running_animations.is_empty() { - AnimationState::NoAnimationsPresent - } else { - AnimationState::AnimationsPresent - }; - - constellation_chan - .send(ConstellationMsg::ChangeRunningAnimationsState( - pipeline_id, - animation_state, - )) - .unwrap(); + animation_state + .running_animations + .append(&mut animation_state.new_animations); } /// Send events for cancelled animations. Currently this only handles cancelled @@ -173,28 +140,25 @@ pub fn update_animation_state( /// well. pub fn send_events_for_cancelled_animations( script_channel: &IpcSender, - cancelled_animations: &mut FxHashMap>, + animation_state: &mut ElementAnimationState, pipeline_id: PipelineId, ) { - for (node, animations) in cancelled_animations.drain() { - for animation in animations { - match animation { - Animation::Transition(transition_node, _, ref property_animation) => { - debug_assert!(transition_node == node); - script_channel - .send(ConstellationControlMsg::TransitionEvent { - pipeline_id, - event_type: TransitionEventType::TransitionCancel, - node: node.to_untrusted_node_address(), - property_name: property_animation.property_name().into(), - elapsed_time: property_animation.duration, - }) - .unwrap(); - }, - Animation::Keyframes(..) => { - warn!("Got unexpected animation in expired transitions list.") - }, - } + for animation in animation_state.cancelled_animations.drain(..) { + match animation { + Animation::Transition(node, _, ref property_animation) => { + script_channel + .send(ConstellationControlMsg::TransitionEvent { + pipeline_id, + event_type: TransitionEventType::TransitionCancel, + node: node.to_untrusted_node_address(), + property_name: property_animation.property_name().into(), + elapsed_time: property_animation.duration, + }) + .unwrap(); + }, + Animation::Keyframes(..) => { + warn!("Got unexpected animation in finished transitions list.") + }, } } } @@ -205,46 +169,48 @@ pub fn send_events_for_cancelled_animations( pub fn recalc_style_for_animations( context: &LayoutContext, flow: &mut dyn Flow, - animations: &FxHashMap>, + animation_states: &FxHashMap, ) -> FxHashSet where E: TElement, { - let mut invalid_nodes = animations.keys().cloned().collect(); - do_recalc_style_for_animations::(context, flow, animations, &mut invalid_nodes); + let mut invalid_nodes = animation_states.keys().cloned().collect(); + do_recalc_style_for_animations::(context, flow, animation_states, &mut invalid_nodes); invalid_nodes } fn do_recalc_style_for_animations( context: &LayoutContext, flow: &mut dyn Flow, - animations: &FxHashMap>, + animation_states: &FxHashMap, invalid_nodes: &mut FxHashSet, ) where E: TElement, { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { - if let Some(ref animations) = animations.get(&fragment.node) { - invalid_nodes.remove(&fragment.node); - for animation in animations.iter() { - let old_style = fragment.style.clone(); - update_style_for_animation::( - &context.style_context, - animation, - &mut fragment.style, - &ServoMetricsProvider, - ); - let difference = - RestyleDamage::compute_style_difference(&old_style, &fragment.style); - damage |= difference.damage; - } + 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::( + &context.style_context, + animation, + &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::(context, kid, animations, invalid_nodes) + do_recalc_style_for_animations::(context, kid, animation_states, invalid_nodes) } } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 97f0eb15f40..6272147b65e 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -25,7 +25,7 @@ mod dom_wrapper; use crate::dom_wrapper::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use app_units::Au; -use crossbeam_channel::{unbounded, Receiver, Sender}; +use crossbeam_channel::{Receiver, Sender}; use embedder_traits::resources::{self, Resource}; use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D}; use fnv::FnvHashMap; @@ -99,9 +99,9 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; use std::time::Duration; -use style::animation::Animation; +use style::animation::ElementAnimationState; +use style::context::SharedStyleContext; use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; -use style::context::{SharedStyleContext, ThreadLocalStyleContextCreationInfo}; use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode}; use style::driver; use style::error_reporting::RustLogReporter; @@ -185,13 +185,6 @@ pub struct LayoutThread { /// This can be used to easily check for invalid stale data. generation: Cell, - /// A channel on which new animations that have been triggered by style recalculation can be - /// sent. - new_animations_sender: Sender, - - /// Receives newly-discovered animations. - new_animations_receiver: Receiver, - /// The number of Web fonts that have been requested but not yet loaded. outstanding_web_fonts: Arc, @@ -201,14 +194,8 @@ pub struct LayoutThread { /// The document-specific shared lock used for author-origin stylesheets document_shared_lock: Option, - /// The list of currently-running animations. - running_animations: ServoArc>>>, - - /// The list of animations that have expired since the last style recalculation. - expired_animations: ServoArc>>>, - - /// The list of animations that have been cancelled during the last style recalculation. - cancelled_animations: ServoArc>>>, + /// The animation state for all of our nodes. + animation_states: ServoArc>>, /// A counter for epoch messages epoch: Cell, @@ -541,9 +528,6 @@ impl LayoutThread { window_size.device_pixel_ratio, ); - // Create the channel on which new animations can be sent. - let (new_animations_sender, new_animations_receiver) = unbounded(); - // Proxy IPC messages from the pipeline to the layout thread. let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(pipeline_port); @@ -572,14 +556,10 @@ impl LayoutThread { font_cache_sender: ipc_font_cache_sender, parallel_flag: true, generation: Cell::new(0), - new_animations_sender: new_animations_sender, - new_animations_receiver: new_animations_receiver, outstanding_web_fonts: Arc::new(AtomicUsize::new(0)), root_flow: RefCell::new(None), document_shared_lock: None, - running_animations: ServoArc::new(RwLock::new(Default::default())), - expired_animations: ServoArc::new(RwLock::new(Default::default())), - cancelled_animations: ServoArc::new(RwLock::new(Default::default())), + animation_states: ServoArc::new(RwLock::new(Default::default())), // Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR epoch: Cell::new(Epoch(1)), viewport_size: Size2D::new(Au(0), Au(0)), @@ -646,9 +626,6 @@ impl LayoutThread { snapshot_map: &'a SnapshotMap, origin: ImmutableOrigin, ) -> LayoutContext<'a> { - let thread_local_style_context_creation_data = - ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone()); - LayoutContext { id: self.id, origin, @@ -657,11 +634,8 @@ impl LayoutThread { options: GLOBAL_STYLE_DATA.options.clone(), guards, visited_styles_enabled: false, - running_animations: self.running_animations.clone(), - expired_animations: self.expired_animations.clone(), - cancelled_animations: self.cancelled_animations.clone(), + animation_states: self.animation_states.clone(), registered_speculative_painters: &self.registered_painters, - local_context_creation_data: Mutex::new(thread_local_style_context_creation_data), timer: self.timer.clone(), traversal_flags: TraversalFlags::empty(), snapshot_map: snapshot_map, @@ -882,7 +856,13 @@ impl LayoutThread { self.paint_time_metrics.set_navigation_start(time); }, Msg::GetRunningAnimations(sender) => { - let _ = sender.send(self.running_animations.read().len()); + let running_animation_count = self + .animation_states + .read() + .values() + .map(|state| state.running_animations.len()) + .sum(); + let _ = sender.send(running_animation_count); }, } @@ -1737,7 +1717,7 @@ impl LayoutThread { let invalid_nodes = { // Perform an abbreviated style recalc that operates without access to the DOM. - let animations = self.running_animations.read(); + let animation_states = self.animation_states.read(); profile( profile_time::ProfilerCategory::LayoutStyleRecalc, self.profiler_metadata(), @@ -1746,7 +1726,7 @@ impl LayoutThread { animation::recalc_style_for_animations::( &layout_context, FlowRef::deref_mut(&mut root_flow), - &animations, + &animation_states, ) }, ) @@ -1783,15 +1763,12 @@ impl LayoutThread { let newly_transitioning_nodes = newly_transitioning_nodes.as_mut().map(|nodes| &mut **nodes); // Kick off animations if any were triggered, expire completed ones. - animation::update_animation_state::( + animation::update_animation_states::( &self.constellation_chan, &self.script_chan, - &mut *self.running_animations.write(), - &mut *self.expired_animations.write(), - &mut *self.cancelled_animations.write(), + &mut *self.animation_states.write(), invalid_nodes, newly_transitioning_nodes, - &self.new_animations_receiver, self.id, &self.timer, ); diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 136d9d917d0..1a0d99cdb27 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -23,7 +23,7 @@ mod dom_wrapper; use crate::dom_wrapper::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use app_units::Au; -use crossbeam_channel::{unbounded, Receiver, Sender}; +use crossbeam_channel::{Receiver, Sender}; use embedder_traits::resources::{self, Resource}; use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D}; use fnv::FnvHashMap; @@ -81,9 +81,9 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; use std::time::Duration; -use style::animation::Animation; -use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; -use style::context::{SharedStyleContext, ThreadLocalStyleContextCreationInfo}; +use style::context::{ + QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext, +}; use style::dom::{TDocument, TElement, TNode}; use style::driver; use style::error_reporting::RustLogReporter; @@ -159,13 +159,6 @@ pub struct LayoutThread { /// This can be used to easily check for invalid stale data. generation: Cell, - /// A channel on which new animations that have been triggered by style recalculation can be - /// sent. - new_animations_sender: Sender, - - /// Receives newly-discovered animations. - _new_animations_receiver: Receiver, - /// The number of Web fonts that have been requested but not yet loaded. outstanding_web_fonts: Arc, @@ -499,9 +492,6 @@ impl LayoutThread { window_size.device_pixel_ratio, ); - // Create the channel on which new animations can be sent. - let (new_animations_sender, new_animations_receiver) = unbounded(); - // Proxy IPC messages from the pipeline to the layout thread. let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(pipeline_port); @@ -528,8 +518,6 @@ impl LayoutThread { font_cache_receiver: font_cache_receiver, font_cache_sender: ipc_font_cache_sender, generation: Cell::new(0), - new_animations_sender: new_animations_sender, - _new_animations_receiver: new_animations_receiver, outstanding_web_fonts: Arc::new(AtomicUsize::new(0)), box_tree_root: Default::default(), fragment_tree_root: Default::default(), @@ -596,9 +584,6 @@ impl LayoutThread { snapshot_map: &'a SnapshotMap, origin: ImmutableOrigin, ) -> LayoutContext<'a> { - let thread_local_style_context_creation_data = - ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone()); - LayoutContext { id: self.id, origin, @@ -607,11 +592,8 @@ impl LayoutThread { options: GLOBAL_STYLE_DATA.options.clone(), guards, visited_styles_enabled: false, - running_animations: Default::default(), - expired_animations: Default::default(), - cancelled_animations: Default::default(), + animation_states: Default::default(), registered_speculative_painters: &self.registered_painters, - local_context_creation_data: Mutex::new(thread_local_style_context_creation_data), timer: self.timer.clone(), traversal_flags: TraversalFlags::empty(), snapshot_map: snapshot_map, diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 59f231e11b3..f3951107703 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -19,7 +19,7 @@ doctest = false gecko = ["style_traits/gecko", "fallible/known_system_malloc", "bindgen", "regex", "toml"] servo = ["serde", "style_traits/servo", "servo_atoms", "servo_config", "html5ever", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "servo_url", - "string_cache", "crossbeam-channel", "to_shmem/servo", + "string_cache", "to_shmem/servo", "servo_arc/servo"] servo-layout-2013 = [] servo-layout-2020 = [] @@ -34,7 +34,6 @@ atomic_refcell = "0.1" bitflags = "1.0" byteorder = "1.0" cssparser = "0.27" -crossbeam-channel = { version = "0.4", optional = true } derive_more = "0.99" new_debug_unreachable = "1.0" encoding_rs = {version = "0.8", optional = true} diff --git a/components/style/animation.rs b/components/style/animation.rs index f7f639532f8..3658d782dc7 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -5,8 +5,7 @@ //! CSS transitions and animations. // NOTE(emilio): This code isn't really executed in Gecko, but we don't want to -// compile it out so that people remember it exists, thus the cfg'd Sender -// import. +// compile it out so that people remember it exists. use crate::bezier::Bezier; use crate::context::SharedStyleContext; @@ -15,7 +14,6 @@ use crate::font_metrics::FontMetricsProvider; use crate::properties::animated_properties::AnimatedProperty; 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; -#[cfg(feature = "servo")] use crate::properties::LonghandIdSet; use crate::properties::{self, CascadeMode, ComputedValues, LonghandId}; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; @@ -26,12 +24,8 @@ use crate::values::computed::TimingFunction; use crate::values::generics::box_::AnimationIterationCount; use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction}; use crate::Atom; -#[cfg(feature = "servo")] -use crossbeam_channel::Sender; use servo_arc::Arc; use std::fmt; -#[cfg(feature = "gecko")] -use std::sync::mpsc::Sender; /// This structure represents a keyframes animation current iteration state. /// @@ -369,70 +363,168 @@ impl PropertyAnimation { } } -/// 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, - opaque_node: OpaqueNode, - old_style: &ComputedValues, - new_style: &mut Arc, - 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); +/// Holds the animation state for a particular element. +#[derive(Default)] +pub struct ElementAnimationState { + /// The animations running for this element. + pub running_animations: Vec, - let properties_that_transition = start_transitions_if_applicable( - context, - new_animations_sender, - opaque_node, - old_style, - new_style, - expired_transitions, - &previously_running_animations, - ); + /// The animations that have finished for this element, but not canceled. These are cleared + /// upon triggering a DOM event for each finished animation. + pub finished_animations: Vec, - 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![]; + /// The animations that have been cancelled for this element. These are cleared + /// upon triggering a DOM event for each cancelled animation. + pub cancelled_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; + /// New animations created for this element. + pub new_animations: Vec, +} + +impl ElementAnimationState { + /// Cancel all animations in this `ElementAnimationState`. This is typically called + /// when the element has been removed from the DOM. + pub fn cancel_all_animations(&mut self) { + self.cancelled_animations.extend( + self.finished_animations + .drain(..) + .chain(self.running_animations.drain(..)) + .chain(self.new_animations.drain(..)), + ); + } + + pub(crate) fn cancel_transitions_with_nontransitioning_properties( + &mut self, + properties_that_transition: &LonghandIdSet, + ) { + if self.running_animations.is_empty() { + return; + } + + let previously_running_transitions = + std::mem::replace(&mut self.running_animations, Vec::new()); + for running_animation in previously_running_transitions { + if let Animation::Transition(_, _, ref property_animation) = running_animation { + if !properties_that_transition.contains(property_animation.property_id()) { + self.cancelled_animations.push(running_animation); + continue; + } + } + self.running_animations.push(running_animation); + } + } + + fn has_transition_with_same_end_value(&self, property_animation: &PropertyAnimation) -> bool { + if self + .running_animations + .iter() + .any(|animation| animation.is_transition_with_same_end_value(&property_animation)) + { + debug!( + "Running transition found with the same end value for {:?}", + property_animation, + ); + return true; + } + + if self + .finished_animations + .iter() + .any(|animation| animation.is_transition_with_same_end_value(&property_animation)) + { + debug!( + "Expired transition found with the same end value for {:?}", + property_animation, + ); + return true; + } + + false + } + + /// Compute before-change-style given an existing ElementAnimationState, + /// information from the StyleContext, and the values of the previous style + /// computation. + /// + /// TODO(mrobinson): This is not a correct computation of before-change-style. + /// For starters it's unclear why we aren't using the running transitions to + /// transform this style into before-change-style. + pub(crate) fn compute_before_change_style( + &mut self, + context: &SharedStyleContext, + style: &mut Arc, + font_metrics: &dyn crate::font_metrics::FontMetricsProvider, + ) where + E: TElement, + { + for animation in self.finished_animations.iter() { + debug!("Updating style for finished animation {:?}", animation); + // TODO: support animation-fill-mode + if let Animation::Transition(_, _, property_animation) = animation { + property_animation.update(Arc::make_mut(style), 1.0); + } + } + + for running_animation in self.running_animations.iter_mut() { + let update = match *running_animation { + Animation::Transition(..) => continue, + Animation::Keyframes(..) => { + update_style_for_animation::(context, running_animation, style, font_metrics) + }, + }; + + match *running_animation { + Animation::Transition(..) => unreachable!(), + Animation::Keyframes(_, _, _, ref mut state) => match update { + AnimationUpdate::Regular => {}, + AnimationUpdate::AnimationCanceled => { + state.expired = true; + }, + }, } } - running_animations.push(running_animation); } - if !cancelled_animations.is_empty() { - all_cancelled_animations.insert(opaque_node, cancelled_animations); + /// Whether this `ElementAnimationState` is empty, which means it doesn't + /// hold any animations in any state. + pub fn is_empty(&self) -> bool { + self.running_animations.is_empty() && + self.finished_animations.is_empty() && + self.cancelled_animations.is_empty() && + self.new_animations.is_empty() } - if !running_animations.is_empty() { - all_running_animations.insert(opaque_node, running_animations); + + fn add_new_animation(&mut self, animation: Animation) { + self.new_animations.push(animation); + } + + fn add_or_update_new_animation(&mut self, timer: &Timer, new_animation: Animation) { + // If the animation was already present in the list for the node, + // just update its state. + if let Animation::Keyframes(_, _, ref new_name, ref new_state) = new_animation { + for existing_animation in self.running_animations.iter_mut() { + match existing_animation { + Animation::Keyframes(_, _, ref name, ref mut state) if *name == *new_name => { + state.update_from_other(&new_state, timer); + return; + }, + _ => {}, + } + } + } + // Otherwise just add the new running animation. + self.add_new_animation(new_animation); } } /// 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, opaque_node: OpaqueNode, old_style: &ComputedValues, new_style: &mut Arc, - expired_transitions: &[PropertyAnimation], - running_animations: &[Animation], + animation_state: &mut ElementAnimationState, ) -> LonghandIdSet { use crate::properties::animated_properties::TransitionPropertyIteration; @@ -473,30 +565,10 @@ 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 expired. + // transition is the same as that of a transition that's running or + // completed. // [1]: https://drafts.csswg.org/css-transitions/#starting - 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 {}, 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() - ); + if animation_state.has_transition_with_same_end_value(&property_animation) { continue; } @@ -505,13 +577,11 @@ pub fn start_transitions_if_applicable( let box_style = new_style.get_box(); 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( - opaque_node, - start_time, - property_animation, - )) - .unwrap(); + animation_state.add_new_animation(Animation::Transition( + opaque_node, + start_time, + property_animation, + )); } properties_that_transition @@ -575,15 +645,12 @@ where pub fn maybe_start_animations( element: E, context: &SharedStyleContext, - new_animations_sender: &Sender, node: OpaqueNode, new_style: &Arc, -) -> bool -where + animation_state: &mut ElementAnimationState, +) where E: TElement, { - let mut had_animations = false; - let box_style = new_style.get_box(); for (i, name) in box_style.animation_name_iter().enumerate() { let name = match name.as_atom() { @@ -637,8 +704,9 @@ where AnimationPlayState::Running => KeyframesRunningState::Running, }; - new_animations_sender - .send(Animation::Keyframes( + animation_state.add_or_update_new_animation( + &context.timer, + Animation::Keyframes( node, anim.clone(), name.clone(), @@ -653,12 +721,9 @@ where expired: false, cascade_style: new_style.clone(), }, - )) - .unwrap(); - had_animations = true; + ), + ); } - - had_animations } /// Returns the kind of animation update that happened. diff --git a/components/style/context.rs b/components/style/context.rs index a9c7f4125b3..5bcae3e6738 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -5,7 +5,7 @@ //! The context within which style is calculated. #[cfg(feature = "servo")] -use crate::animation::Animation; +use crate::animation::ElementAnimationState; use crate::bloom::StyleBloom; use crate::data::{EagerPseudoStyles, ElementData}; #[cfg(feature = "servo")] @@ -29,8 +29,6 @@ use crate::timer::Timer; use crate::traversal::DomTraversal; use crate::traversal_flags::TraversalFlags; use app_units::Au; -#[cfg(feature = "servo")] -use crossbeam_channel::Sender; use euclid::default::Size2D; use euclid::Scale; use fxhash::FxHashMap; @@ -43,8 +41,6 @@ use servo_arc::Arc; use servo_atoms::Atom; use std::fmt; use std::ops; -#[cfg(feature = "servo")] -use std::sync::Mutex; use style_traits::CSSPixel; use style_traits::DevicePixel; #[cfg(feature = "servo")] @@ -54,22 +50,6 @@ use uluru::{Entry, LRUCache}; pub use selectors::matching::QuirksMode; -/// This structure is used to create a local style context from a shared one. -#[cfg(feature = "servo")] -pub struct ThreadLocalStyleContextCreationInfo { - new_animations_sender: Sender, -} - -#[cfg(feature = "servo")] -impl ThreadLocalStyleContextCreationInfo { - /// Trivially constructs a `ThreadLocalStyleContextCreationInfo`. - pub fn new(animations_sender: Sender) -> Self { - ThreadLocalStyleContextCreationInfo { - new_animations_sender: animations_sender, - } - } -} - /// A global options structure for the style system. We use this instead of /// opts to abstract across Gecko and Servo. #[derive(Clone)] @@ -186,25 +166,13 @@ pub struct SharedStyleContext<'a> { /// A map with our snapshots in order to handle restyle hints. pub snapshot_map: &'a SnapshotMap, - /// The animations that are currently running. + /// The state of all animations for our styled elements. #[cfg(feature = "servo")] - pub running_animations: Arc>>>, - - /// The list of animations that have expired since the last style recalculation. - #[cfg(feature = "servo")] - pub expired_animations: Arc>>>, - - /// The list of animations that have expired since the last style recalculation. - #[cfg(feature = "servo")] - pub cancelled_animations: Arc>>>, + pub animation_states: Arc>>, /// Paint worklets #[cfg(feature = "servo")] pub registered_speculative_painters: &'a dyn RegisteredSpeculativePainters, - - /// Data needed to create the thread-local style context from the shared one. - #[cfg(feature = "servo")] - pub local_context_creation_data: Mutex, } impl<'a> SharedStyleContext<'a> { @@ -741,10 +709,6 @@ pub struct ThreadLocalStyleContext { pub rule_cache: RuleCache, /// The bloom filter used to fast-reject selector-matching. pub bloom_filter: StyleBloom, - /// A channel on which new animations that have been triggered by style - /// recalculation can be sent. - #[cfg(feature = "servo")] - pub new_animations_sender: Sender, /// A set of tasks to be run (on the parent thread) in sequential mode after /// the rest of the styling is complete. This is useful for /// infrequently-needed non-threadsafe operations. @@ -778,12 +742,6 @@ impl ThreadLocalStyleContext { sharing_cache: StyleSharingCache::new(), rule_cache: RuleCache::new(), bloom_filter: StyleBloom::new(), - new_animations_sender: shared - .local_context_creation_data - .lock() - .unwrap() - .new_animations_sender - .clone(), tasks: SequentialTaskList(Vec::new()), selector_flags: SelectorFlagsMap::new(), statistics: PerThreadTraversalStatistics::default(), diff --git a/components/style/lib.rs b/components/style/lib.rs index 5096217d75c..1abb740686d 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -32,8 +32,6 @@ extern crate atomic_refcell; extern crate bitflags; #[allow(unused_extern_crates)] extern crate byteorder; -#[cfg(feature = "servo")] -extern crate crossbeam_channel; #[macro_use] extern crate cssparser; #[macro_use] diff --git a/components/style/matching.rs b/components/style/matching.rs index 15b952d27e3..027f3299fb0 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -7,13 +7,15 @@ #![allow(unsafe_code)] #![deny(missing_docs)] +#[cfg(feature = "servo")] +use crate::animation; use crate::computed_value_flags::ComputedValueFlags; use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::ElementData; use crate::dom::TElement; #[cfg(feature = "servo")] -use crate::dom::{OpaqueNode, TNode}; +use crate::dom::TNode; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::properties::longhands::display::computed_value::T as Display; use crate::properties::ComputedValues; @@ -437,55 +439,48 @@ trait PrivateMatchMethods: TElement { _restyle_hint: RestyleHint, _important_rules_changed: bool, ) { - use crate::animation; - let this_opaque = self.as_node().opaque(); - 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 - // here, because we later compare the old style to the new style in - // `start_transitions_if_applicable`. If the styles differ then it will - // cause the expired transition to restart. - // - // TODO(mrobinson): We should really be following spec behavior and calculate - // after-change-style and before-change-style here. - Self::collect_and_update_style_for_expired_transitions( - shared_context, - this_opaque, - old_values, - &mut expired_transitions, - ); + let mut animation_states = shared_context.animation_states.write(); + let mut animation_state = animation_states.remove(&this_opaque).unwrap_or_default(); - Self::update_style_for_animations( + if let Some(ref mut old_values) = *old_values { + animation_state.compute_before_change_style::( shared_context, - this_opaque, old_values, &context.thread_local.font_metrics_provider, ); } - let new_animations_sender = &context.thread_local.new_animations_sender; // Trigger any present animations if necessary. animation::maybe_start_animations( *self, &shared_context, - new_animations_sender, this_opaque, &new_values, + &mut animation_state, ); // 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::update_transitions( - &shared_context, - new_animations_sender, + if let Some(ref old_values) = old_values { + let transitioning_properties = animation::start_transitions_if_applicable( + shared_context, this_opaque, - &values, + old_values, new_values, - &expired_transitions, + &mut animation_state, ); + animation_state + .cancel_transitions_with_nontransitioning_properties(&transitioning_properties); + } + + animation_state.finished_animations.clear(); + + // If the ElementAnimationState is empty, don't push it to save + // memory and to avoid extra processing later. + if !animation_state.is_empty() { + animation_states.insert(this_opaque, animation_state); } } @@ -601,67 +596,6 @@ trait PrivateMatchMethods: TElement { // properties, we can stop the cascade. ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle } - - #[cfg(feature = "servo")] - fn collect_and_update_style_for_expired_transitions( - context: &SharedStyleContext, - node: OpaqueNode, - style: &mut Arc, - expired_transitions: &mut Vec, - ) { - use crate::animation::Animation; - - let mut all_expired_animations = context.expired_animations.write(); - if let Some(animations) = all_expired_animations.remove(&node) { - debug!("removing expired animations for {:?}", node); - for animation in animations { - debug!("Updating expired animation {:?}", animation); - // TODO: support animation-fill-mode - if let Animation::Transition(_, _, property_animation) = animation { - property_animation.update(Arc::make_mut(style), 1.0); - expired_transitions.push(property_animation); - } - } - } - } - - #[cfg(feature = "servo")] - fn update_style_for_animations( - context: &SharedStyleContext, - node: OpaqueNode, - style: &mut Arc, - font_metrics: &dyn crate::font_metrics::FontMetricsProvider, - ) { - use crate::animation::{self, Animation, AnimationUpdate}; - - let mut all_running_animations = context.running_animations.write(); - let running_animations = match all_running_animations.get_mut(&node) { - Some(running_animations) => running_animations, - None => return, - }; - - for running_animation in running_animations.iter_mut() { - let update = match *running_animation { - Animation::Transition(..) => continue, - Animation::Keyframes(..) => animation::update_style_for_animation::( - context, - running_animation, - style, - font_metrics, - ), - }; - - match *running_animation { - Animation::Transition(..) => unreachable!(), - Animation::Keyframes(_, _, _, ref mut state) => match update { - AnimationUpdate::Regular => {}, - AnimationUpdate::AnimationCanceled => { - state.expired = true; - }, - }, - } - } - } } impl PrivateMatchMethods for E {}