From 47642e0eee1db349d5b933c0cafc4088818db9a0 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Sat, 23 May 2020 14:22:43 +0200 Subject: [PATCH 1/2] 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 | 406 ++++++++++++++++++----------- 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, 278 insertions(+), 339 deletions(-) diff --git a/components/script/animations.rs b/components/script/animations.rs index d8b80338851..f9de0fc0a32 100644 --- a/components/script/animations.rs +++ b/components/script/animations.rs @@ -6,85 +6,130 @@ //! 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()); + 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); - { - 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); - } - - // 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); + // 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()); - update + self.update_running_animations_presence(window, 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.set(new_value); + let state = match new_value { + true => AnimationsPresentState::AnimationsPresent, + false => AnimationsPresentState::NoAnimationsPresent, + }; + + window.send_to_constellation(ScriptMsg::ChangeRunningAnimationsState(state)); } pub(crate) fn running_animation_count(&self) -> usize { @@ -95,40 +140,21 @@ impl Animations { .sum() } - 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 { - return; - } - - self.have_running_animations = have_running_animations; - let state = match have_running_animations { - true => AnimationsPresentState::AnimationsPresent, - false => AnimationsPresentState::NoAnimationsPresent, - }; - - window.send_to_constellation(ScriptMsg::ChangeRunningAnimationsState(state)); - } - /// 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) - } -} + /// 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; + } -pub(crate) struct AnimationsUpdate { - pub pipeline_id: PipelineId, - pub events: Vec, - pub newly_animating_nodes: Vec, -} - -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 num_iterations = match animation.iteration_state { + KeyframesIterationState::Finite(current, _) | + KeyframesIterationState::Infinite(current) => current, + }; - let node = UntrustedNodeAddress(node.0 as *const c_void); - std::iter::repeat(node).take(num_new_animations) - })); + 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 b0271e159de..b9af35b8cac 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 a6965155588..16126dcf1b9 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 294027582fa..56a69ca1073 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 f792d852a9c..2330f41f448 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( From 77aa3721c5221eea9acb064b8c9fd559ee2a6c14 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Tue, 26 May 2020 20:34:58 +0200 Subject: [PATCH 2/2] Add support for remaining animation and transition events Fixes #21564. --- components/atoms/static_atoms.txt | 2 + components/script/animations.rs | 97 ++++++++++- components/style/animation.rs | 102 +++++++---- .../animationevent-types.html.ini | 5 - .../Element-getAnimations.tentative.html.ini | 2 +- .../animationevent-pseudoelement.html.ini | 2 +- .../animationevent-types.html.ini | 6 - .../css/css-transitions/events-006.html.ini | 2 +- .../variable-animation-from-to.html.ini | 2 +- ...ariable-animation-over-transition.html.ini | 2 +- ...ubstitute-into-keyframe-shorthand.html.ini | 2 +- ...ubstitute-into-keyframe-transform.html.ini | 2 +- ...nimation-substitute-into-keyframe.html.ini | 2 +- ...bstitute-within-keyframe-fallback.html.ini | 2 +- ...bstitute-within-keyframe-multiple.html.ini | 2 +- ...mation-substitute-within-keyframe.html.ini | 2 +- .../variable-animation-to-only.html.ini | 2 +- ...ariable-transitions-from-no-value.html.ini | 2 +- .../variable-transitions-to-no-value.html.ini | 2 +- ...on-property-variable-before-value.html.ini | 2 +- ...fore-transition-property-variable.html.ini | 2 +- .../css/animations/animation-events.html.ini | 2 + .../css/animations/transition-events.html.ini | 3 + tests/wpt/mozilla/meta/MANIFEST.json | 16 ++ .../css/animations/animation-events.html.ini | 2 + .../css/animations/transition-events.html.ini | 3 + .../css/animations/animation-events.html | 160 ++++++++++++++++++ .../css/animations/transition-events.html | 131 ++++++++++++++ 28 files changed, 493 insertions(+), 68 deletions(-) delete mode 100644 tests/wpt/metadata-layout-2020/css/css-animations/animationevent-types.html.ini delete mode 100644 tests/wpt/metadata/css/css-animations/animationevent-types.html.ini create mode 100644 tests/wpt/mozilla/meta-layout-2020/css/animations/animation-events.html.ini create mode 100644 tests/wpt/mozilla/meta-layout-2020/css/animations/transition-events.html.ini create mode 100644 tests/wpt/mozilla/meta/css/animations/animation-events.html.ini create mode 100644 tests/wpt/mozilla/meta/css/animations/transition-events.html.ini create mode 100644 tests/wpt/mozilla/tests/css/animations/animation-events.html create mode 100644 tests/wpt/mozilla/tests/css/animations/transition-events.html diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index ebf4a2c27be..d4f2f38da7e 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -2,6 +2,7 @@ DOMContentLoaded abort activate addtrack +animationcancel animationend animationiteration animationstart @@ -132,6 +133,7 @@ track transitioncancel transitionend transitionrun +transitionstart unhandledrejection unload url diff --git a/components/script/animations.rs b/components/script/animations.rs index f9de0fc0a32..d0bfcef6f79 100644 --- a/components/script/animations.rs +++ b/components/script/animations.rs @@ -78,12 +78,15 @@ impl Animations { let mut sets = self.sets.write(); for set in sets.values_mut() { + self.start_pending_animations(set, now, pipeline_id); + // When necessary, iterate our running animations to the next iteration. for animation in set.animations.iter_mut() { if animation.iterate_if_necessary(now) { self.add_animation_event( animation, TransitionOrAnimationEventType::AnimationIteration, + now, pipeline_id, ); } @@ -97,7 +100,6 @@ impl Animations { /// 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(&self, window: &Window, now: f64) { let pipeline_id = window.pipeline_id(); let mut sets = self.sets.write(); @@ -140,6 +142,39 @@ impl Animations { .sum() } + /// Walk through the list of pending animations and start all of the ones that + /// have left the delay phase. + fn start_pending_animations( + &self, + set: &mut ElementAnimationSet, + now: f64, + pipeline_id: PipelineId, + ) { + for animation in set.animations.iter_mut() { + if animation.state == AnimationState::Pending && animation.started_at <= now { + animation.state = AnimationState::Running; + self.add_animation_event( + animation, + TransitionOrAnimationEventType::AnimationStart, + now, + pipeline_id, + ); + } + } + + for transition in set.transitions.iter_mut() { + if transition.state == AnimationState::Pending && transition.start_time <= now { + transition.state = AnimationState::Running; + self.add_transition_event( + transition, + TransitionOrAnimationEventType::TransitionStart, + now, + pipeline_id, + ); + } + } + } + /// Walk through the list of running animations and remove all of the ones that /// have ended. fn finish_running_animations( @@ -154,6 +189,7 @@ impl Animations { self.add_animation_event( animation, TransitionOrAnimationEventType::AnimationEnd, + now, pipeline_id, ); } @@ -192,7 +228,17 @@ impl Animations { } } - // TODO(mrobinson): We need to send animationcancel events. + for animation in &set.animations { + if animation.state == AnimationState::Canceled { + self.add_animation_event( + animation, + TransitionOrAnimationEventType::AnimationCancel, + now, + pipeline_id, + ); + } + } + set.clear_canceled_animations(); } @@ -263,14 +309,21 @@ impl Animations { now: f64, pipeline_id: PipelineId, ) { + // Calculate the `elapsed-time` property of the event and take the absolute + // value to prevent -0 values. let elapsed_time = match event_type { TransitionOrAnimationEventType::TransitionRun | + TransitionOrAnimationEventType::TransitionStart => transition + .property_animation + .duration + .min((-transition.delay).max(0.)), TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration, TransitionOrAnimationEventType::TransitionCancel => { (now - transition.start_time).max(0.) }, _ => unreachable!(), - }; + } + .abs(); self.pending_events .borrow_mut() @@ -291,6 +344,7 @@ impl Animations { &self, animation: &Animation, event_type: TransitionOrAnimationEventType, + now: f64, pipeline_id: PipelineId, ) { let num_iterations = match animation.iteration_state { @@ -298,11 +352,25 @@ impl Animations { KeyframesIterationState::Infinite(current) => current, }; + let active_duration = match animation.iteration_state { + KeyframesIterationState::Finite(_, max) => max * animation.duration, + KeyframesIterationState::Infinite(_) => std::f64::MAX, + }; + + // Calculate the `elapsed-time` property of the event and take the absolute + // value to prevent -0 values. let elapsed_time = match event_type { + TransitionOrAnimationEventType::AnimationStart => { + (-animation.delay).max(0.).min(active_duration) + }, TransitionOrAnimationEventType::AnimationIteration | TransitionOrAnimationEventType::AnimationEnd => num_iterations * animation.duration, + TransitionOrAnimationEventType::AnimationCancel => { + (num_iterations * animation.duration) + (now - animation.started_at).max(0.) + }, _ => unreachable!(), - }; + } + .abs(); self.pending_events .borrow_mut() @@ -333,10 +401,13 @@ impl Animations { let event_atom = match event.event_type { TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"), + TransitionOrAnimationEventType::AnimationStart => atom!("animationstart"), + TransitionOrAnimationEventType::AnimationCancel => atom!("animationcancel"), TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"), TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"), TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"), TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"), + TransitionOrAnimationEventType::TransitionStart => atom!("transitionstart"), }; let parent = EventInit { bubbles: true, @@ -381,25 +452,39 @@ pub enum TransitionOrAnimationEventType { /// "The transitionrun event occurs when a transition is created (i.e., when it /// is added to the set of running transitions)." TransitionRun, + /// "The transitionstart event occurs when a transition’s delay phase ends." + TransitionStart, /// "The transitionend event occurs at the completion of the transition. In the /// case where a transition is removed before completion, such as if the /// transition-property is removed, then the event will not fire." TransitionEnd, /// "The transitioncancel event occurs when a transition is canceled." TransitionCancel, + /// "The animationstart event occurs at the start of the animation. If there is + /// an animation-delay then this event will fire once the delay period has expired." + AnimationStart, /// "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, + /// "The animationcancel event occurs when the animation stops running in a way + /// that does not fire an animationend event..." + AnimationCancel, } impl TransitionOrAnimationEventType { /// Whether or not this event is a transition-related event. pub fn is_transition_event(&self) -> bool { match *self { - Self::TransitionRun | Self::TransitionEnd | Self::TransitionCancel => true, - Self::AnimationEnd | Self::AnimationIteration => false, + Self::TransitionRun | + Self::TransitionEnd | + Self::TransitionCancel | + Self::TransitionStart => true, + Self::AnimationEnd | + Self::AnimationIteration | + Self::AnimationStart | + Self::AnimationCancel => false, } } } diff --git a/components/style/animation.rs b/components/style/animation.rs index 2330f41f448..152692a0b28 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -139,17 +139,27 @@ impl PropertyAnimation { /// This structure represents the state of an animation. #[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub enum AnimationState { + /// The animation has been created, but is not running yet. This state + /// is also used when an animation is still in the first delay phase. + Pending, + /// This animation is currently running. + Running, /// This animation is paused. The inner field is the percentage of progress /// when it was paused, from 0 to 1. Paused(f64), - /// This animation is currently running. - Running, /// This animation has finished. Finished, /// This animation has been canceled. Canceled, } +impl AnimationState { + /// Whether or not this state requires its owning animation to be ticked. + fn needs_to_be_ticked(&self) -> bool { + *self == AnimationState::Running || *self == AnimationState::Pending + } +} + /// This structure represents a keyframes animation current iteration state. /// /// If the iteration count is infinite, there's no other state, otherwise we @@ -174,7 +184,8 @@ pub struct Animation { /// The internal animation from the style system. pub keyframes_animation: KeyframesAnimation, - /// The time this animation started at. + /// The time this animation started at, which is the current value of the animation + /// timeline when this animation was created plus any animation delay. pub started_at: f64, /// The duration of this animation. @@ -273,9 +284,11 @@ impl Animation { /// canceled due to changes in the style. pub fn has_ended(&self, time: f64) -> bool { match self.state { - AnimationState::Canceled | AnimationState::Paused(_) => return false, - AnimationState::Finished => return true, AnimationState::Running => {}, + AnimationState::Finished => return true, + AnimationState::Pending | AnimationState::Canceled | AnimationState::Paused(_) => { + return false + }, } if !self.iteration_over(time) { @@ -312,24 +325,11 @@ impl Animation { let old_direction = self.current_direction; let old_state = self.state.clone(); let old_iteration_state = self.iteration_state.clone(); + *self = other.clone(); - let mut new_started_at = old_started_at; - - // If we're unpausing the animation, fake the start time so we seem to - // restore it. - // - // If the animation keeps paused, keep the old value. - // - // If we're pausing the animation, compute the progress value. - match (&mut self.state, old_state) { - (&mut Running, Paused(progress)) => new_started_at = now - (self.duration * progress), - (&mut Paused(ref mut new), Paused(old)) => *new = old, - (&mut Paused(ref mut progress), Running) => { - *progress = (now - old_started_at) / old_duration - }, - _ => {}, - } + self.started_at = old_started_at; + self.current_direction = old_direction; // Don't update the iteration count, just the iteration limit. // TODO: see how changing the limit affects rendering in other browsers. @@ -342,8 +342,37 @@ impl Animation { _ => {}, } - self.current_direction = old_direction; - self.started_at = new_started_at; + // Don't pause or restart animations that should remain finished. + // We call mem::replace because `has_ended(...)` looks at `Animation::state`. + let new_state = std::mem::replace(&mut self.state, Running); + if old_state == Finished && self.has_ended(now) { + self.state = Finished; + } else { + self.state = new_state; + } + + // If we're unpausing the animation, fake the start time so we seem to + // restore it. + // + // If the animation keeps paused, keep the old value. + // + // If we're pausing the animation, compute the progress value. + match (&mut self.state, &old_state) { + (&mut Pending, &Paused(progress)) => { + self.started_at = now - (self.duration * progress); + }, + (&mut Paused(ref mut new), &Paused(old)) => *new = old, + (&mut Paused(ref mut progress), &Running) => { + *progress = (now - old_started_at) / old_duration + }, + _ => {}, + } + + // Try to detect when we should skip straight to the running phase to + // avoid sending multiple animationstart events. + if self.state == Pending && self.started_at <= now && old_state != Pending { + self.state = Running; + } } /// Update the given style to reflect the values specified by this `Animation` @@ -360,7 +389,7 @@ impl Animation { let started_at = self.started_at; let now = match self.state { - AnimationState::Running | AnimationState::Finished => { + AnimationState::Running | AnimationState::Pending | AnimationState::Finished => { context.current_time_for_animations }, AnimationState::Paused(progress) => started_at + duration * progress, @@ -551,9 +580,12 @@ pub struct Transition { pub node: OpaqueNode, /// The start time of this transition, which is the current value of the animation - /// timeline when this transition created. + /// timeline when this transition was created plus any animation delay. pub start_time: f64, + /// The delay used for this transition. + pub delay: f64, + /// The internal style `PropertyAnimation` for this transition. pub property_animation: PropertyAnimation, @@ -724,26 +756,25 @@ impl ElementAnimationSet { } /// Whether or not this state needs animation ticks for its transitions - /// or animations. New animations don't need ticks until they are no - /// longer marked as new. + /// or animations. pub fn needs_animation_ticks(&self) -> bool { self.animations .iter() - .any(|animation| animation.state == AnimationState::Running && !animation.is_new) || - self.transitions.iter().any(|transition| { - transition.state == AnimationState::Running && !transition.is_new - }) + .any(|animation| animation.state.needs_to_be_ticked()) || + self.transitions + .iter() + .any(|transition| transition.state.needs_to_be_ticked()) } /// The number of running animations and transitions for this `ElementAnimationSet`. pub fn running_animation_and_transition_count(&self) -> usize { self.animations .iter() - .filter(|animation| animation.state == AnimationState::Running) + .filter(|animation| animation.state.needs_to_be_ticked()) .count() + self.transitions .iter() - .filter(|transition| transition.state == AnimationState::Running) + .filter(|transition| transition.state.needs_to_be_ticked()) .count() } @@ -870,8 +901,9 @@ impl ElementAnimationSet { let mut new_transition = Transition { node: opaque_node, start_time: now + delay, + delay, property_animation, - state: AnimationState::Running, + state: AnimationState::Pending, is_new: true, reversing_adjusted_start_value, reversing_shortening_factor: 1.0, @@ -1040,7 +1072,7 @@ pub fn maybe_start_animations( let state = match box_style.animation_play_state_mod(i) { AnimationPlayState::Paused => AnimationState::Paused(0.), - AnimationPlayState::Running => AnimationState::Running, + AnimationPlayState::Running => AnimationState::Pending, }; let new_animation = Animation { diff --git a/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-types.html.ini b/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-types.html.ini deleted file mode 100644 index 256ea2378e5..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-types.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[animationevent-types.html] - expected: TIMEOUT - [animationstart event is instanceof AnimationEvent] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/css/css-animations/Element-getAnimations.tentative.html.ini b/tests/wpt/metadata/css/css-animations/Element-getAnimations.tentative.html.ini index 854c8c6e480..5913cd5b936 100644 --- a/tests/wpt/metadata/css/css-animations/Element-getAnimations.tentative.html.ini +++ b/tests/wpt/metadata/css/css-animations/Element-getAnimations.tentative.html.ini @@ -1,5 +1,5 @@ [Element-getAnimations.tentative.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26626 [getAnimations for CSS Animations with animation-name: none] expected: FAIL diff --git a/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini b/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini index 69cb0d12c27..c7f08fec5f7 100644 --- a/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini +++ b/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini @@ -1,5 +1,5 @@ [animationevent-pseudoelement.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/10316 expected: TIMEOUT [AnimationEvent should have the correct pseudoElement memeber] expected: TIMEOUT diff --git a/tests/wpt/metadata/css/css-animations/animationevent-types.html.ini b/tests/wpt/metadata/css/css-animations/animationevent-types.html.ini deleted file mode 100644 index 944fcee5779..00000000000 --- a/tests/wpt/metadata/css/css-animations/animationevent-types.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[animationevent-types.html] - bug: https://github.com/servo/servo/issues/21564 - expected: TIMEOUT - [animationstart event is instanceof AnimationEvent] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/css/css-transitions/events-006.html.ini b/tests/wpt/metadata/css/css-transitions/events-006.html.ini index a884e8df574..edacd476df2 100644 --- a/tests/wpt/metadata/css/css-transitions/events-006.html.ini +++ b/tests/wpt/metadata/css/css-transitions/events-006.html.ini @@ -1,5 +1,5 @@ [events-006.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/10316 expected: TIMEOUT [transition padding-left on ::after] expected: NOTRUN diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-from-to.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-from-to.html.ini index 79ed388547d..ac3f2998e7f 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-from-to.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-from-to.html.ini @@ -1,5 +1,5 @@ [variable-animation-from-to.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value before animation] expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-over-transition.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-over-transition.html.ini index c6edf5d258b..6544dd3874e 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-over-transition.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-over-transition.html.ini @@ -1,5 +1,5 @@ [variable-animation-over-transition.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value before animation] expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini index e61911d3325..bed87a13159 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-shorthand.html.ini @@ -1,2 +1,2 @@ [variable-animation-substitute-into-keyframe-shorthand.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-transform.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-transform.html.ini index 77ff1af8a7d..4722727d208 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-transform.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe-transform.html.ini @@ -1,5 +1,5 @@ [variable-animation-substitute-into-keyframe-transform.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 [Verify transform before animation] expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini index ccac15c6181..99e8565a805 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-into-keyframe.html.ini @@ -1,2 +1,2 @@ [variable-animation-substitute-into-keyframe.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini index 8fe87ce4aa4..3290f166029 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-fallback.html.ini @@ -1,5 +1,5 @@ [variable-animation-substitute-within-keyframe-fallback.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 [Verify color after animation] expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini index 7046292ccd7..ca4fe4dfc75 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe-multiple.html.ini @@ -1,5 +1,5 @@ [variable-animation-substitute-within-keyframe-multiple.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 [Verify color after animation] expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini index 705cf7ed37a..d347620f3dc 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-substitute-within-keyframe.html.ini @@ -1,5 +1,5 @@ [variable-animation-substitute-within-keyframe.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 [Verify color after animation] expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-animation-to-only.html.ini b/tests/wpt/metadata/css/css-variables/variable-animation-to-only.html.ini index 5e862e864ff..976008b1d68 100644 --- a/tests/wpt/metadata/css/css-variables/variable-animation-to-only.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-animation-to-only.html.ini @@ -1,5 +1,5 @@ [variable-animation-to-only.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value after animation] expected: TIMEOUT diff --git a/tests/wpt/metadata/css/css-variables/variable-transitions-from-no-value.html.ini b/tests/wpt/metadata/css/css-variables/variable-transitions-from-no-value.html.ini index 1d039975c27..b29d8db3da1 100644 --- a/tests/wpt/metadata/css/css-variables/variable-transitions-from-no-value.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-transitions-from-no-value.html.ini @@ -1,5 +1,5 @@ [variable-transitions-from-no-value.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value after transition] expected: NOTRUN diff --git a/tests/wpt/metadata/css/css-variables/variable-transitions-to-no-value.html.ini b/tests/wpt/metadata/css/css-variables/variable-transitions-to-no-value.html.ini index b36005d4be8..a7604084561 100644 --- a/tests/wpt/metadata/css/css-variables/variable-transitions-to-no-value.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-transitions-to-no-value.html.ini @@ -1,5 +1,5 @@ [variable-transitions-to-no-value.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value after transition] expected: NOTRUN diff --git a/tests/wpt/metadata/css/css-variables/variable-transitions-transition-property-variable-before-value.html.ini b/tests/wpt/metadata/css/css-variables/variable-transitions-transition-property-variable-before-value.html.ini index 53ad4c18ded..cb48c956d7d 100644 --- a/tests/wpt/metadata/css/css-variables/variable-transitions-transition-property-variable-before-value.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-transitions-transition-property-variable-before-value.html.ini @@ -1,5 +1,5 @@ [variable-transitions-transition-property-variable-before-value.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value after transition] expected: NOTRUN diff --git a/tests/wpt/metadata/css/css-variables/variable-transitions-value-before-transition-property-variable.html.ini b/tests/wpt/metadata/css/css-variables/variable-transitions-value-before-transition-property-variable.html.ini index d9b360988cd..81ca758a599 100644 --- a/tests/wpt/metadata/css/css-variables/variable-transitions-value-before-transition-property-variable.html.ini +++ b/tests/wpt/metadata/css/css-variables/variable-transitions-value-before-transition-property-variable.html.ini @@ -1,5 +1,5 @@ [variable-transitions-value-before-transition-property-variable.html] - bug: https://github.com/servo/servo/issues/21564 + bug: https://github.com/servo/servo/issues/26625 expected: TIMEOUT [Verify CSS variable value after transition] expected: NOTRUN diff --git a/tests/wpt/mozilla/meta-layout-2020/css/animations/animation-events.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/animations/animation-events.html.ini new file mode 100644 index 00000000000..33f08914f30 --- /dev/null +++ b/tests/wpt/mozilla/meta-layout-2020/css/animations/animation-events.html.ini @@ -0,0 +1,2 @@ +prefs: ["layout.animations.test.enabled:false", + "dom.testbinding.enabled:false"] diff --git a/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-events.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-events.html.ini new file mode 100644 index 00000000000..f53fd6a5050 --- /dev/null +++ b/tests/wpt/mozilla/meta-layout-2020/css/animations/transition-events.html.ini @@ -0,0 +1,3 @@ +prefs: ["layout.animations.test.enabled:false", + "dom.testbinding.enabled:false"] + diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 70c41cd6def..f5225f39757 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -12849,6 +12849,15 @@ }, "css": { "animations": { + "animation-events.html": [ + "0975aa64ec47ca4b4c8fc1e0a40414a51719ad67", + [ + null, + { + "timeout": "long" + } + ] + ], "animation-fill-mode.html": [ "4cfaab9fbce0adccd83f592935e63fa8ff58a1cf", [ @@ -12884,6 +12893,13 @@ {} ] ], + "transition-events.html": [ + "b561fc8353276e6bdd13a9d1b965f57733ecd19b", + [ + null, + {} + ] + ], "transition-raf.html": [ "c38404503408e04b3c75b42df18ec3a7ec0819f5", [ diff --git a/tests/wpt/mozilla/meta/css/animations/animation-events.html.ini b/tests/wpt/mozilla/meta/css/animations/animation-events.html.ini new file mode 100644 index 00000000000..33f08914f30 --- /dev/null +++ b/tests/wpt/mozilla/meta/css/animations/animation-events.html.ini @@ -0,0 +1,2 @@ +prefs: ["layout.animations.test.enabled:false", + "dom.testbinding.enabled:false"] diff --git a/tests/wpt/mozilla/meta/css/animations/transition-events.html.ini b/tests/wpt/mozilla/meta/css/animations/transition-events.html.ini new file mode 100644 index 00000000000..f53fd6a5050 --- /dev/null +++ b/tests/wpt/mozilla/meta/css/animations/transition-events.html.ini @@ -0,0 +1,3 @@ +prefs: ["layout.animations.test.enabled:false", + "dom.testbinding.enabled:false"] + diff --git a/tests/wpt/mozilla/tests/css/animations/animation-events.html b/tests/wpt/mozilla/tests/css/animations/animation-events.html new file mode 100644 index 00000000000..0975aa64ec4 --- /dev/null +++ b/tests/wpt/mozilla/tests/css/animations/animation-events.html @@ -0,0 +1,160 @@ + + +CSS animation event dispatch + + + + + +
+ diff --git a/tests/wpt/mozilla/tests/css/animations/transition-events.html b/tests/wpt/mozilla/tests/css/animations/transition-events.html new file mode 100644 index 00000000000..b561fc83532 --- /dev/null +++ b/tests/wpt/mozilla/tests/css/animations/transition-events.html @@ -0,0 +1,131 @@ + + +CSS transition event dispatch + + + +
+