From 47642e0eee1db349d5b933c0cafc4088818db9a0 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Sat, 23 May 2020 14:22:43 +0200 Subject: [PATCH] Have Animations struct handle rooting nodes Instead of having `ScriptThread` handle rooting nodes, do this in `Animations`. This makes it easier to know when it is appropriate to root and unroot nodes instead of relying on a certain order of events. This also allows reducing quite a bit the amount of unsafe code. --- components/script/animations.rs | 404 ++++++++++++++++++----------- components/script/dom/document.rs | 20 +- components/script/dom/window.rs | 17 +- components/script/script_thread.rs | 165 +----------- components/style/animation.rs | 9 - 5 files changed, 277 insertions(+), 338 deletions(-) diff --git a/components/script/animations.rs b/components/script/animations.rs index d8b80338851b..f9de0fc0a321 100644 --- a/components/script/animations.rs +++ b/components/script/animations.rs @@ -6,107 +6,125 @@ //! The set of animations for a document. +use crate::dom::animationevent::AnimationEvent; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; +use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::node::{from_untrusted_node_address, window_from_node, Node, NodeDamage}; +use crate::dom::transitionevent::TransitionEvent; use crate::dom::window::Window; use fxhash::FxHashMap; use libc::c_void; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use msg::constellation_msg::PipelineId; use parking_lot::RwLock; use script_traits::{AnimationState as AnimationsPresentState, ScriptMsg, UntrustedNodeAddress}; use servo_arc::Arc; -use style::animation::{AnimationState, ElementAnimationSet}; +use std::cell::Cell; +use style::animation::{ + Animation, AnimationState, ElementAnimationSet, KeyframesIterationState, Transition, +}; use style::dom::OpaqueNode; /// The set of animations for a document. -/// -/// Make sure to update the MallocSizeOf implementation when changing the -/// contents of this struct. -#[derive(Clone, Debug, Default, JSTraceable)] +#[derive(Default, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub(crate) struct Animations { + /// The map of nodes to their animation states. + #[ignore_malloc_size_of = "Arc is hard"] pub sets: Arc>>, - have_running_animations: bool, + + /// Whether or not we have animations that are running. + have_running_animations: Cell, + + /// A list of nodes with in-progress CSS transitions or pending events. + rooted_nodes: DomRefCell>>, + + /// A list of pending animation-related events. + pending_events: DomRefCell>, } impl Animations { pub(crate) fn new() -> Self { Animations { sets: Default::default(), - have_running_animations: false, + have_running_animations: Cell::new(false), + rooted_nodes: Default::default(), + pending_events: Default::default(), } } - pub(crate) fn update_for_new_timeline_value( - &mut self, - window: &Window, - now: f64, - ) -> AnimationsUpdate { - let mut update = AnimationsUpdate::new(window.pipeline_id()); + pub(crate) fn clear(&self) { + self.sets.write().clear(); + self.rooted_nodes.borrow_mut().clear(); + self.pending_events.borrow_mut().clear(); + } + + pub(crate) fn mark_animating_nodes_as_dirty(&self) { + let sets = self.sets.read(); + let rooted_nodes = self.rooted_nodes.borrow(); + for node in sets.keys().filter_map(|node| rooted_nodes.get(&node)) { + node.dirty(NodeDamage::NodeStyleDamaged); + } + } + + pub(crate) fn update_for_new_timeline_value(&self, window: &Window, now: f64) { + let pipeline_id = window.pipeline_id(); let mut sets = self.sets.write(); for set in sets.values_mut() { // When necessary, iterate our running animations to the next iteration. for animation in set.animations.iter_mut() { if animation.iterate_if_necessary(now) { - update.add_event( - animation.node, - animation.name.to_string(), + self.add_animation_event( + animation, TransitionOrAnimationEventType::AnimationIteration, - animation.active_duration(), + pipeline_id, ); } } - Self::finish_running_animations(set, now, &mut update); + self.finish_running_animations(set, now, pipeline_id); } - update + + self.unroot_unused_nodes(&sets); } /// Processes any new animations that were discovered after reflow. Collect messages /// that trigger events for any animations that changed state. /// TODO(mrobinson): The specification dictates that this should happen before reflow. - pub(crate) fn do_post_reflow_update(&mut self, window: &Window, now: f64) -> AnimationsUpdate { - let mut update = AnimationsUpdate::new(window.pipeline_id()); - - { - let mut sets = self.sets.write(); - update.collect_newly_animating_nodes(&sets); - - for set in sets.values_mut() { - Self::handle_canceled_animations(set, now, &mut update); - Self::handle_new_animations(set, &mut update); - } + pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) { + let pipeline_id = window.pipeline_id(); + let mut sets = self.sets.write(); + self.root_newly_animating_dom_nodes(&sets, window); - // 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. - sets.retain(|_, state| !state.is_empty()); + for set in sets.values_mut() { + self.handle_canceled_animations(set, now, pipeline_id); + self.handle_new_animations(set, now, pipeline_id); } - self.update_running_animations_presence(window); - - update - } + // 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. + sets.retain(|_, state| !state.is_empty()); + let have_running_animations = sets.values().any(|state| state.needs_animation_ticks()); - pub(crate) fn running_animation_count(&self) -> usize { - self.sets - .read() - .values() - .map(|state| state.running_animation_and_transition_count()) - .sum() + self.update_running_animations_presence(window, have_running_animations); } - fn update_running_animations_presence(&mut self, window: &Window) { - let have_running_animations = self - .sets - .read() - .values() - .any(|state| state.needs_animation_ticks()); - if have_running_animations == self.have_running_animations { + fn update_running_animations_presence(&self, window: &Window, new_value: bool) { + let have_running_animations = self.have_running_animations.get(); + if new_value == have_running_animations { return; } - self.have_running_animations = have_running_animations; - let state = match have_running_animations { + self.have_running_animations.set(new_value); + let state = match new_value { true => AnimationsPresentState::AnimationsPresent, false => AnimationsPresentState::NoAnimationsPresent, }; @@ -114,21 +132,29 @@ impl Animations { window.send_to_constellation(ScriptMsg::ChangeRunningAnimationsState(state)); } + pub(crate) fn running_animation_count(&self) -> usize { + self.sets + .read() + .values() + .map(|state| state.running_animation_and_transition_count()) + .sum() + } + /// Walk through the list of running animations and remove all of the ones that /// have ended. fn finish_running_animations( + &self, set: &mut ElementAnimationSet, now: f64, - update: &mut AnimationsUpdate, + pipeline_id: PipelineId, ) { for animation in set.animations.iter_mut() { if animation.state == AnimationState::Running && animation.has_ended(now) { animation.state = AnimationState::Finished; - update.add_event( - animation.node, - animation.name.to_string(), + self.add_animation_event( + animation, TransitionOrAnimationEventType::AnimationEnd, - animation.active_duration(), + pipeline_id, ); } } @@ -136,11 +162,11 @@ impl Animations { for transition in set.transitions.iter_mut() { if transition.state == AnimationState::Running && transition.has_ended(now) { transition.state = AnimationState::Finished; - update.add_event( - transition.node, - transition.property_animation.property_id().name().into(), + self.add_transition_event( + transition, TransitionOrAnimationEventType::TransitionEnd, - transition.property_animation.duration, + now, + pipeline_id, ); } } @@ -150,19 +176,18 @@ impl Animations { /// transitions, but eventually this should handle canceled CSS animations as /// well. fn handle_canceled_animations( + &self, set: &mut ElementAnimationSet, now: f64, - update: &mut AnimationsUpdate, + pipeline_id: PipelineId, ) { for transition in &set.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 - update.add_event( - transition.node, - transition.property_animation.property_id().name().into(), + self.add_transition_event( + transition, TransitionOrAnimationEventType::TransitionCancel, - (now - transition.start_time).max(0.), + now, + pipeline_id, ); } } @@ -171,103 +196,187 @@ impl Animations { set.clear_canceled_animations(); } - fn handle_new_animations(set: &mut ElementAnimationSet, update: &mut AnimationsUpdate) { + fn handle_new_animations( + &self, + set: &mut ElementAnimationSet, + now: f64, + pipeline_id: PipelineId, + ) { for animation in set.animations.iter_mut() { animation.is_new = false; } for transition in set.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 - update.add_event( - transition.node, - transition.property_animation.property_id().name().into(), + self.add_transition_event( + transition, TransitionOrAnimationEventType::TransitionRun, - 0., + now, + pipeline_id, ); transition.is_new = false; } } } -} - -impl MallocSizeOf for Animations { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.sets.read().size_of(ops) + self.have_running_animations.size_of(ops) - } -} -pub(crate) struct AnimationsUpdate { - pub pipeline_id: PipelineId, - pub events: Vec, - pub newly_animating_nodes: Vec, -} + /// Ensure that all nodes with new animations are rooted. This should be called + /// immediately after a restyle, to ensure that these addresses are still valid. + #[allow(unsafe_code)] + fn root_newly_animating_dom_nodes( + &self, + sets: &FxHashMap, + window: &Window, + ) { + let js_runtime = window.get_js_runtime().as_ref().unwrap().rt(); + let mut rooted_nodes = self.rooted_nodes.borrow_mut(); + for (opaque_node, set) in sets.iter() { + if rooted_nodes.contains_key(opaque_node) { + continue; + } -impl AnimationsUpdate { - fn new(pipeline_id: PipelineId) -> Self { - AnimationsUpdate { - pipeline_id, - events: Default::default(), - newly_animating_nodes: Default::default(), + if set.animations.iter().any(|animation| animation.is_new) || + set.transitions.iter().any(|transition| transition.is_new) + { + let address = UntrustedNodeAddress(opaque_node.0 as *const c_void); + unsafe { + rooted_nodes.insert( + opaque_node.clone(), + Dom::from_ref(&*from_untrusted_node_address(js_runtime, address)), + ) + }; + } } } - fn add_event( - &mut self, - node: OpaqueNode, - property_or_animation_name: String, - event_type: TransitionOrAnimationEventType, - elapsed_time: f64, - ) { - let node = UntrustedNodeAddress(node.0 as *const c_void); - self.events.push(TransitionOrAnimationEvent { - pipeline_id: self.pipeline_id, - event_type, - node, - property_or_animation_name, - elapsed_time, + // Unroot any nodes that we have rooted but are no longer tracking animations for. + fn unroot_unused_nodes(&self, sets: &FxHashMap) { + let pending_events = self.pending_events.borrow(); + self.rooted_nodes.borrow_mut().retain(|key, _| { + sets.contains_key(key) || pending_events.iter().any(|event| event.node == *key) }); } - pub(crate) fn is_empty(&self) -> bool { - self.events.is_empty() && self.newly_animating_nodes.is_empty() + fn add_transition_event( + &self, + transition: &Transition, + event_type: TransitionOrAnimationEventType, + now: f64, + pipeline_id: PipelineId, + ) { + let elapsed_time = match event_type { + TransitionOrAnimationEventType::TransitionRun | + TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration, + TransitionOrAnimationEventType::TransitionCancel => { + (now - transition.start_time).max(0.) + }, + _ => unreachable!(), + }; + + self.pending_events + .borrow_mut() + .push(TransitionOrAnimationEvent { + pipeline_id, + event_type, + node: transition.node.clone(), + property_or_animation_name: transition + .property_animation + .property_id() + .name() + .into(), + elapsed_time, + }); } - /// 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. - /// TODO(mrobinson): Look into handling the rooting inside this class. - fn collect_newly_animating_nodes( - &mut self, - animation_states: &FxHashMap, + fn add_animation_event( + &self, + animation: &Animation, + event_type: TransitionOrAnimationEventType, + pipeline_id: PipelineId, ) { - // 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. - self.newly_animating_nodes - .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(); - - let node = UntrustedNodeAddress(node.0 as *const c_void); - std::iter::repeat(node).take(num_new_animations) - })); + let num_iterations = match animation.iteration_state { + KeyframesIterationState::Finite(current, _) | + KeyframesIterationState::Infinite(current) => current, + }; + + let elapsed_time = match event_type { + TransitionOrAnimationEventType::AnimationIteration | + TransitionOrAnimationEventType::AnimationEnd => num_iterations * animation.duration, + _ => unreachable!(), + }; + + self.pending_events + .borrow_mut() + .push(TransitionOrAnimationEvent { + pipeline_id, + event_type, + node: animation.node.clone(), + property_or_animation_name: animation.name.to_string(), + elapsed_time, + }); + } + + pub(crate) fn send_pending_events(&self) { + // Take all of the events here, in case sending one of these events + // triggers adding new events by forcing a layout. + let events = std::mem::replace(&mut *self.pending_events.borrow_mut(), Vec::new()); + + for event in events.into_iter() { + // We root the node here to ensure that sending this event doesn't + // unroot it as a side-effect. + let node = match self.rooted_nodes.borrow().get(&event.node) { + Some(node) => DomRoot::from_ref(&**node), + None => { + warn!("Tried to send an event for an unrooted node"); + continue; + }, + }; + + let event_atom = match event.event_type { + TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"), + TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"), + TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"), + TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"), + TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"), + }; + let parent = EventInit { + bubbles: true, + cancelable: false, + }; + + // TODO: Handle pseudo-elements properly + let property_or_animation_name = + DOMString::from(event.property_or_animation_name.clone()); + let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap(); + let window = window_from_node(&*node); + + if event.event_type.is_transition_event() { + let event_init = TransitionEventInit { + parent, + propertyName: property_or_animation_name, + elapsedTime: elapsed_time, + pseudoElement: DOMString::new(), + }; + TransitionEvent::new(&window, event_atom, &event_init) + .upcast::() + .fire(node.upcast()); + } else { + let event_init = AnimationEventInit { + parent, + animationName: property_or_animation_name, + elapsedTime: elapsed_time, + pseudoElement: DOMString::new(), + }; + AnimationEvent::new(&window, event_atom, &event_init) + .upcast::() + .fire(node.upcast()); + } + } } } /// The type of transition event to trigger. These are defined by /// CSS Transitions § 6.1 and CSS Animations § 4.2 -#[derive(Clone, Debug, Deserialize, JSTraceable, Serialize)] +#[derive(Clone, Debug, Deserialize, JSTraceable, MallocSizeOf, Serialize)] pub enum TransitionOrAnimationEventType { /// "The transitionrun event occurs when a transition is created (i.e., when it /// is added to the set of running transitions)." @@ -278,23 +387,14 @@ pub enum TransitionOrAnimationEventType { TransitionEnd, /// "The transitioncancel event occurs when a transition is canceled." TransitionCancel, - /// "The animationend event occurs when the animation finishes" - AnimationEnd, /// "The animationiteration event occurs at the end of each iteration of an /// animation, except when an animationend event would fire at the same time." AnimationIteration, + /// "The animationend event occurs when the animation finishes" + AnimationEnd, } impl TransitionOrAnimationEventType { - /// Whether or not this event finalizes the animation or transition. During finalization - /// the DOM object associated with this transition or animation is unrooted. - pub fn finalizes_transition_or_animation(&self) -> bool { - match *self { - Self::TransitionEnd | Self::TransitionCancel | Self::AnimationEnd => true, - Self::TransitionRun | Self::AnimationIteration => false, - } - } - /// Whether or not this event is a transition-related event. pub fn is_transition_event(&self) -> bool { match *self { @@ -304,7 +404,7 @@ impl TransitionOrAnimationEventType { } } -#[derive(Deserialize, JSTraceable, Serialize)] +#[derive(Deserialize, JSTraceable, MallocSizeOf, Serialize)] /// A transition or animation event. pub struct TransitionOrAnimationEvent { /// The pipeline id of the layout task that sent this message. @@ -312,7 +412,7 @@ pub struct TransitionOrAnimationEvent { /// The type of transition event this should trigger. pub event_type: TransitionOrAnimationEventType, /// The address of the node which owns this transition. - pub node: UntrustedNodeAddress, + pub node: OpaqueNode, /// The name of the property that is transitioning (in the case of a transition) /// or the name of the animation (in the case of an animation). pub property_or_animation_name: String, diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index b0271e159de2..b9af35b8cac7 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::animation_timeline::AnimationTimeline; -use crate::animations::{Animations, AnimationsUpdate}; +use crate::animations::Animations; use crate::document_loader::{DocumentLoader, LoadType}; use crate::dom::attr::Attr; use crate::dom::beforeunloadevent::BeforeUnloadEvent; @@ -3750,15 +3750,15 @@ impl Document { .collect() } - pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) -> AnimationsUpdate { + pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) { self.animation_timeline.borrow_mut().advance_specific(delta); let current_timeline_value = self.current_animation_timeline_value(); self.animations - .borrow_mut() - .update_for_new_timeline_value(&self.window, current_timeline_value) + .borrow() + .update_for_new_timeline_value(&self.window, current_timeline_value); } - pub(crate) fn update_animation_timeline(&self) -> AnimationsUpdate { + pub(crate) fn update_animation_timeline(&self) { // Only update the time if it isn't being managed by a test. if !pref!(layout.animations.test.enabled) { self.animation_timeline.borrow_mut().update(); @@ -3768,8 +3768,8 @@ impl Document { // value might have been advanced previously via the TestBinding. let current_timeline_value = self.current_animation_timeline_value(); self.animations - .borrow_mut() - .update_for_new_timeline_value(&self.window, current_timeline_value) + .borrow() + .update_for_new_timeline_value(&self.window, current_timeline_value); } pub(crate) fn current_animation_timeline_value(&self) -> f64 { @@ -3780,10 +3780,10 @@ impl Document { self.animations.borrow() } - pub(crate) fn update_animations_post_reflow(&self) -> AnimationsUpdate { + pub(crate) fn update_animations_post_reflow(&self) { self.animations - .borrow_mut() - .do_post_reflow_update(&self.window, self.current_animation_timeline_value()) + .borrow() + .do_post_reflow_update(&self.window, self.current_animation_timeline_value()); } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index a6965155588a..16126dcf1b9c 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -2,7 +2,7 @@ * 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/. */ -use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::cell::{DomRefCell, Ref}; use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ DocumentMethods, DocumentReadyState, }; @@ -410,6 +410,10 @@ impl Window { unsafe { JSContext::from_ptr(self.js_runtime.borrow().as_ref().unwrap().cx()) } } + pub fn get_js_runtime(&self) -> Ref>> { + self.js_runtime.borrow() + } + pub fn main_thread_script_chan(&self) -> &Sender { &self.script_chan.0 } @@ -1581,12 +1585,8 @@ impl Window { #[allow(unsafe_code)] pub fn advance_animation_clock(&self, delta_ms: i32) { let pipeline_id = self.upcast::().pipeline_id(); - let update = self - .Document() + self.Document() .advance_animation_timeline_for_testing(delta_ms as f64 / 1000.); - unsafe { - ScriptThread::process_animations_update(update); - } ScriptThread::handle_tick_all_animations_for_testing(pipeline_id); } @@ -1752,10 +1752,7 @@ impl Window { } } - let update = document.update_animations_post_reflow(); - unsafe { - ScriptThread::process_animations_update(update); - } + document.update_animations_post_reflow(); true } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 294027582fa4..56a69ca10739 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -17,26 +17,18 @@ //! a page runs its course and the script thread returns to processing events in the main event //! loop. -use crate::animations::{ - AnimationsUpdate, TransitionOrAnimationEvent, TransitionOrAnimationEventType, -}; use crate::devtools; use crate::document_loader::DocumentLoader; -use crate::dom::animationevent::AnimationEvent; use crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit; use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ DocumentMethods, DocumentReadyState, }; -use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; -use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::conversions::{ ConversionResult, FromJSValConvertible, StringificationBehavior, }; use crate::dom::bindings::inheritance::Castable; -use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::ThreadLocalStackRoots; @@ -57,14 +49,11 @@ use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmliframeelement::{HTMLIFrameElement, NavigationType}; use crate::dom::identityhub::Identities; use crate::dom::mutationobserver::MutationObserver; -use crate::dom::node::{ - from_untrusted_node_address, window_from_node, Node, NodeDamage, ShadowIncluding, -}; +use crate::dom::node::{window_from_node, Node, ShadowIncluding}; use crate::dom::performanceentry::PerformanceEntry; use crate::dom::performancepainttiming::PerformancePaintTiming; use crate::dom::serviceworker::TrustedServiceWorkerAddress; use crate::dom::servoparser::{ParserContext, ServoParser}; -use crate::dom::transitionevent::TransitionEvent; use crate::dom::uievent::UIEvent; use crate::dom::window::{ReflowReason, Window}; use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy}; @@ -636,13 +625,6 @@ pub struct ScriptThread { /// resources during a turn of the event loop. docs_with_no_blocking_loads: DomRefCell>>, - /// A list of nodes with in-progress CSS transitions, which roots them for the duration - /// of the transition. - animating_nodes: DomRefCell>>>, - - /// Animations events that are pending to be sent. - animation_events: RefCell>, - /// custom_element_reaction_stack: CustomElementReactionStack, @@ -831,40 +813,6 @@ impl ScriptThread { }) } - /// Consume the list of pointer addresses corresponding to DOM nodes that are animating - /// and root them in a per-pipeline list of nodes. - /// - /// Unsafety: any pointer to invalid memory (ie. a GCed node) will trigger a crash. - /// TODO: ensure caller uses rooted nodes instead of unsafe node addresses. - pub(crate) unsafe fn process_animations_update(mut update: AnimationsUpdate) { - if update.is_empty() { - return; - } - - SCRIPT_THREAD_ROOT.with(|root| { - let script_thread = &*root.get().unwrap(); - - if !update.events.is_empty() { - script_thread - .animation_events - .borrow_mut() - .append(&mut update.events); - } - - let js_runtime = script_thread.js_runtime.rt(); - let new_nodes = update - .newly_animating_nodes - .into_iter() - .map(|n| Dom::from_ref(&*from_untrusted_node_address(js_runtime, n))); - script_thread - .animating_nodes - .borrow_mut() - .entry(update.pipeline_id) - .or_insert_with(Vec::new) - .extend(new_nodes); - }) - } - pub fn set_mutation_observer_microtask_queued(value: bool) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; @@ -1363,9 +1311,6 @@ impl ScriptThread { docs_with_no_blocking_loads: Default::default(), - animating_nodes: Default::default(), - animation_events: Default::default(), - custom_element_reaction_stack: CustomElementReactionStack::new(), webrender_document: state.webrender_document, @@ -1644,19 +1589,13 @@ impl ScriptThread { } // Perform step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops. - // TODO(mrobinson): This should also update the current animations to conform to - // the HTML specification. fn update_animations_and_send_events(&self) { - // We remove the events because handling these events might trigger - // a reflow which might want to add more events to the queue. - let events = self.animation_events.replace(Vec::new()); - for event in events.into_iter() { - self.handle_transition_or_animation_event(&event); + for (_, document) in self.documents.borrow().iter() { + document.animations().send_pending_events(); } for (_, document) in self.documents.borrow().iter() { - let update = document.update_animation_timeline(); - unsafe { ScriptThread::process_animations_update(update) }; + document.update_animation_timeline(); } } @@ -2859,11 +2798,11 @@ impl ScriptThread { .send((id, ScriptMsg::PipelineExited)) .ok(); - // Remove any rooted nodes for active animations and transitions. - self.animating_nodes.borrow_mut().remove(&id); - // Now that layout is shut down, it's OK to remove the document. if let Some(document) = document { + // Clear any active animations and unroot all of the associated DOM objects. + document.animations().clear(); + // We don't want to dispatch `mouseout` event pointing to non-existing element if let Some(target) = self.topmost_mouse_over_target.get() { if target.upcast::().owner_doc() == document { @@ -2939,99 +2878,11 @@ impl ScriptThread { document.run_the_animation_frame_callbacks(); } if tick_type.contains(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS) { - match self.animating_nodes.borrow().get(&id) { - Some(nodes) => { - for node in nodes.iter() { - node.dirty(NodeDamage::NodeStyleDamaged); - } - }, - None => return, - } - + document.animations().mark_animating_nodes_as_dirty(); document.window().add_pending_reflow(); } } - /// Handles firing of transition-related events. - /// - /// TODO(mrobinson): Add support for more events. - fn handle_transition_or_animation_event(&self, event: &TransitionOrAnimationEvent) { - // We limit the scope of the borrow here so that we aren't holding it when - // sending events. Event handlers may trigger another layout, resulting in - // a double mutable borrow of `animating_nodes`. - let node = { - let mut animating_nodes = self.animating_nodes.borrow_mut(); - let nodes = match animating_nodes.get_mut(&event.pipeline_id) { - Some(nodes) => nodes, - None => { - return warn!( - "Ignoring transition event for pipeline without animating nodes." - ); - }, - }; - - let node_index = nodes - .iter() - .position(|n| n.to_untrusted_node_address() == event.node); - let node_index = match node_index { - Some(node_index) => node_index, - None => { - // If no index is found, we can't know whether this node is safe to use. - // It's better not to fire a DOM event than crash. - warn!("Ignoring transition event for unknown node."); - return; - }, - }; - - // We need to root the node now, because if we remove it from the map - // a garbage collection might clean it up while we are sending events. - let node = DomRoot::from_ref(&*nodes[node_index]); - if event.event_type.finalizes_transition_or_animation() { - nodes.remove(node_index); - } - node - }; - - let event_atom = match event.event_type { - TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"), - TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"), - TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"), - TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"), - TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"), - }; - let parent = EventInit { - bubbles: true, - cancelable: false, - }; - - // TODO: Handle pseudo-elements properly - let property_or_animation_name = DOMString::from(event.property_or_animation_name.clone()); - let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap(); - let window = window_from_node(&*node); - - if event.event_type.is_transition_event() { - let event_init = TransitionEventInit { - parent, - propertyName: property_or_animation_name, - elapsedTime: elapsed_time, - pseudoElement: DOMString::new(), - }; - TransitionEvent::new(&window, event_atom, &event_init) - .upcast::() - .fire(node.upcast()); - } else { - let event_init = AnimationEventInit { - parent, - animationName: property_or_animation_name, - elapsedTime: elapsed_time, - pseudoElement: DOMString::new(), - }; - AnimationEvent::new(&window, event_atom, &event_init) - .upcast::() - .fire(node.upcast()); - } - } - /// Handles a Web font being loaded. Does nothing if the page no longer exists. fn handle_web_font_loaded(&self, pipeline_id: PipelineId) { let document = self.documents.borrow().find_document(pipeline_id); diff --git a/components/style/animation.rs b/components/style/animation.rs index f792d852a9cb..2330f41f448e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -346,15 +346,6 @@ impl Animation { self.started_at = new_started_at; } - /// Calculate the active-duration of this animation according to - /// https://drafts.csswg.org/css-animations/#active-duration. - pub fn active_duration(&self) -> f64 { - match self.iteration_state { - KeyframesIterationState::Finite(current, _) | - KeyframesIterationState::Infinite(current) => self.duration * current, - } - } - /// Update the given style to reflect the values specified by this `Animation` /// at the time provided by the given `SharedStyleContext`. fn update_style(