diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 27d45e44d88..1d00cc24214 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -129,6 +129,7 @@ time timeupdate toggle track +transitioncancel transitionend unhandledrejection unload diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 9dc59da1937..a60c0e484f9 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -13,7 +13,9 @@ use fxhash::{FxHashMap, FxHashSet}; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use script_traits::UntrustedNodeAddress; -use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; +use script_traits::{ + AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, TransitionEventType, +}; use style::animation::{update_style_for_animation, Animation}; use style::dom::TElement; use style::font_metrics::ServoMetricsProvider; @@ -28,6 +30,7 @@ pub fn update_animation_state( script_chan: &IpcSender, running_animations: &mut FxHashMap>, expired_animations: &mut FxHashMap>, + cancelled_animations: &mut FxHashMap>, mut keys_to_remove: FxHashSet, mut newly_transitioning_nodes: Option<&mut Vec>, new_animations_receiver: &Receiver, @@ -36,6 +39,8 @@ pub fn update_animation_state( ) where E: TElement, { + send_events_for_cancelled_animations(script_chan, cancelled_animations, pipeline_id); + let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { let mut should_push = true; @@ -102,11 +107,13 @@ pub fn update_animation_state( if let Animation::Transition(node, _, ref property_animation) = running_animation { script_chan - .send(ConstellationControlMsg::TransitionEnd( - node.to_untrusted_node_address(), - property_animation.property_name().into(), - property_animation.duration, - )) + .send(ConstellationControlMsg::TransitionEvent { + pipeline_id, + event_type: TransitionEventType::TransitionEnd, + node: node.to_untrusted_node_address(), + property_name: property_animation.property_name().into(), + elapsed_time: property_animation.duration, + }) .unwrap(); } @@ -161,6 +168,37 @@ pub fn update_animation_state( .unwrap(); } +/// Send events for cancelled animations. Currently this only handles cancelled +/// transitions, but eventually this should handle cancelled CSS animations as +/// well. +pub fn send_events_for_cancelled_animations( + script_channel: &IpcSender, + cancelled_animations: &mut FxHashMap>, + pipeline_id: PipelineId, +) { + for (node, animations) in cancelled_animations.drain() { + for animation in animations { + match animation { + Animation::Transition(transition_node, _, ref property_animation) => { + debug_assert!(transition_node == node); + script_channel + .send(ConstellationControlMsg::TransitionEvent { + pipeline_id, + event_type: TransitionEventType::TransitionCancel, + node: node.to_untrusted_node_address(), + property_name: property_animation.property_name().into(), + elapsed_time: property_animation.duration, + }) + .unwrap(); + }, + Animation::Keyframes(..) => { + warn!("Got unexpected animation in expired transitions list.") + }, + } + } + } +} + /// Recalculates style for a set of animations. This does *not* run with the DOM /// lock held. Returns a set of nodes associated with animations that are no longer /// valid. diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 98cdef1492b..02a5c6debbc 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -207,6 +207,9 @@ pub struct LayoutThread { /// The list of animations that have expired since the last style recalculation. expired_animations: ServoArc>>>, + /// The list of animations that have been cancelled during the last style recalculation. + cancelled_animations: ServoArc>>>, + /// A counter for epoch messages epoch: Cell, @@ -576,6 +579,7 @@ impl LayoutThread { document_shared_lock: None, running_animations: ServoArc::new(RwLock::new(Default::default())), expired_animations: ServoArc::new(RwLock::new(Default::default())), + cancelled_animations: ServoArc::new(RwLock::new(Default::default())), // Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR epoch: Cell::new(Epoch(1)), viewport_size: Size2D::new(Au(0), Au(0)), @@ -655,6 +659,7 @@ impl LayoutThread { visited_styles_enabled: false, running_animations: self.running_animations.clone(), expired_animations: self.expired_animations.clone(), + cancelled_animations: self.cancelled_animations.clone(), registered_speculative_painters: &self.registered_painters, local_context_creation_data: Mutex::new(thread_local_style_context_creation_data), timer: self.timer.clone(), @@ -1785,6 +1790,7 @@ impl LayoutThread { &self.script_chan, &mut *self.running_animations.write(), &mut *self.expired_animations.write(), + &mut *self.cancelled_animations.write(), invalid_nodes, newly_transitioning_nodes, &self.new_animations_receiver, diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 3a808d287ae..56eceee7b87 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -609,6 +609,7 @@ impl LayoutThread { visited_styles_enabled: false, running_animations: Default::default(), expired_animations: Default::default(), + cancelled_animations: Default::default(), registered_speculative_painters: &self.registered_painters, local_context_creation_data: Mutex::new(thread_local_style_context_creation_data), timer: self.timer.clone(), diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index b27c02208be..df0a929a0ff 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -496,6 +496,7 @@ macro_rules! global_event_handlers( event_handler!(suspend, GetOnsuspend, SetOnsuspend); event_handler!(timeupdate, GetOntimeupdate, SetOntimeupdate); event_handler!(toggle, GetOntoggle, SetOntoggle); + event_handler!(transitioncancel, GetOntransitioncancel, SetOntransitioncancel); event_handler!(transitionend, GetOntransitionend, SetOntransitionend); event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange); event_handler!(waiting, GetOnwaiting, SetOnwaiting); diff --git a/components/script/dom/webidls/EventHandler.webidl b/components/script/dom/webidls/EventHandler.webidl index 99be904979b..772c47facd2 100644 --- a/components/script/dom/webidls/EventHandler.webidl +++ b/components/script/dom/webidls/EventHandler.webidl @@ -93,6 +93,7 @@ interface mixin GlobalEventHandlers { // https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl partial interface mixin GlobalEventHandlers { attribute EventHandler ontransitionend; + attribute EventHandler ontransitioncancel; }; // https://w3c.github.io/selection-api/#extensions-to-globaleventhandlers-interface diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index bd65780dc19..7a672851ba6 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -133,17 +133,15 @@ use script_traits::CompositorEvent::{ CompositionEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent, TouchEvent, WheelEvent, }; -use script_traits::StructuredSerializedData; -use script_traits::{CompositorEvent, ConstellationControlMsg}; use script_traits::{ - DiscardBrowsingContext, DocumentActivity, EventResult, HistoryEntryReplacement, + CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity, + EventResult, HistoryEntryReplacement, InitialScriptState, JsEvalResult, LayoutMsg, LoadData, + LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType, NewLayoutInfo, Painter, + ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory, ScriptToConstellationChan, + StructuredSerializedData, TimerSchedulerMsg, TouchEventType, TouchId, TransitionEventType, + UntrustedNodeAddress, UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, WindowSizeData, + WindowSizeType, }; -use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, LoadOrigin}; -use script_traits::{MediaSessionActionType, MouseButton, MouseEventType, NewLayoutInfo}; -use script_traits::{Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory}; -use script_traits::{ScriptToConstellationChan, TimerSchedulerMsg}; -use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta}; -use script_traits::{UpdatePipelineIdReason, WebrenderIpcSender, WindowSizeData, WindowSizeType}; use servo_atoms::Atom; use servo_config::opts; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; @@ -1668,46 +1666,43 @@ impl ScriptThread { fn message_to_pipeline(&self, msg: &MixedMessage) -> Option { use script_traits::ConstellationControlMsg::*; match *msg { - MixedMessage::FromConstellation(ref inner_msg) => { - match *inner_msg { - StopDelayingLoadEventsMode(id) => Some(id), - NavigationResponse(id, _) => Some(id), - AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id), - Resize(id, ..) => Some(id), - ResizeInactive(id, ..) => Some(id), - UnloadDocument(id) => Some(id), - ExitPipeline(id, ..) => Some(id), - ExitScriptThread => None, - SendEvent(id, ..) => Some(id), - Viewport(id, ..) => Some(id), - SetScrollState(id, ..) => Some(id), - GetTitle(id) => Some(id), - SetDocumentActivity(id, ..) => Some(id), - ChangeFrameVisibilityStatus(id, ..) => Some(id), - NotifyVisibilityChange(id, ..) => Some(id), - NavigateIframe(id, ..) => Some(id), - PostMessage { target: id, .. } => Some(id), - UpdatePipelineId(_, _, _, id, _) => Some(id), - UpdateHistoryState(id, ..) => Some(id), - RemoveHistoryStates(id, ..) => Some(id), - FocusIFrame(id, ..) => Some(id), - WebDriverScriptCommand(id, ..) => Some(id), - TickAllAnimations(id) => Some(id), - // FIXME https://github.com/servo/servo/issues/15079 - TransitionEnd(..) => None, - WebFontLoaded(id) => Some(id), - DispatchIFrameLoadEvent { - target: _, - parent: id, - child: _, - } => Some(id), - DispatchStorageEvent(id, ..) => Some(id), - ReportCSSError(id, ..) => Some(id), - Reload(id, ..) => Some(id), - PaintMetric(..) => None, - ExitFullScreen(id, ..) => Some(id), - MediaSessionAction(..) => None, - } + MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg { + StopDelayingLoadEventsMode(id) => Some(id), + NavigationResponse(id, _) => Some(id), + AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id), + Resize(id, ..) => Some(id), + ResizeInactive(id, ..) => Some(id), + UnloadDocument(id) => Some(id), + ExitPipeline(id, ..) => Some(id), + ExitScriptThread => None, + SendEvent(id, ..) => Some(id), + Viewport(id, ..) => Some(id), + SetScrollState(id, ..) => Some(id), + GetTitle(id) => Some(id), + SetDocumentActivity(id, ..) => Some(id), + ChangeFrameVisibilityStatus(id, ..) => Some(id), + NotifyVisibilityChange(id, ..) => Some(id), + NavigateIframe(id, ..) => Some(id), + PostMessage { target: id, .. } => Some(id), + UpdatePipelineId(_, _, _, id, _) => Some(id), + UpdateHistoryState(id, ..) => Some(id), + RemoveHistoryStates(id, ..) => Some(id), + FocusIFrame(id, ..) => Some(id), + WebDriverScriptCommand(id, ..) => Some(id), + TickAllAnimations(id) => Some(id), + TransitionEvent { .. } => None, + WebFontLoaded(id) => Some(id), + DispatchIFrameLoadEvent { + target: _, + parent: id, + child: _, + } => Some(id), + DispatchStorageEvent(id, ..) => Some(id), + ReportCSSError(id, ..) => Some(id), + Reload(id, ..) => Some(id), + PaintMetric(..) => None, + ExitFullScreen(id, ..) => Some(id), + MediaSessionAction(..) => None, }, MixedMessage::FromDevtools(_) => None, MixedMessage::FromScript(ref inner_msg) => match *inner_msg { @@ -1896,8 +1891,20 @@ impl ScriptThread { ConstellationControlMsg::TickAllAnimations(pipeline_id) => { self.handle_tick_all_animations(pipeline_id) }, - ConstellationControlMsg::TransitionEnd(unsafe_node, name, duration) => { - self.handle_transition_event(unsafe_node, name, duration) + ConstellationControlMsg::TransitionEvent { + pipeline_id, + event_type, + node, + property_name, + elapsed_time, + } => { + self.handle_transition_event( + pipeline_id, + event_type, + node, + property_name, + elapsed_time, + ); }, ConstellationControlMsg::WebFontLoaded(pipeline_id) => { self.handle_web_font_loaded(pipeline_id) @@ -2899,12 +2906,16 @@ impl ScriptThread { document.run_the_animation_frame_callbacks(); } - /// Handles firing of transition events. + /// Handles firing of transition-related events. + /// + /// TODO(mrobinson): Add support for more events. fn handle_transition_event( &self, + pipeline_id: PipelineId, + event_type: TransitionEventType, unsafe_node: UntrustedNodeAddress, - name: String, - duration: f64, + property_name: String, + elapsed_time: f64, ) { let js_runtime = self.js_runtime.rt(); let node = unsafe { from_untrusted_node_address(js_runtime, unsafe_node) }; @@ -2926,29 +2937,35 @@ impl ScriptThread { }, } - let window = window_from_node(&*node); - - // Not quite the right thing - see #13865. - node.dirty(NodeDamage::NodeStyleDamaged); - - if let Some(el) = node.downcast::() { - if !el.has_css_layout_box() { - return; - } + if self.closed_pipelines.borrow().contains(&pipeline_id) { + warn!("Ignoring transition event for closed pipeline."); + return; } - let init = TransitionEventInit { + let event_atom = match event_type { + TransitionEventType::TransitionEnd => { + // Not quite the right thing - see #13865. + node.dirty(NodeDamage::NodeStyleDamaged); + atom!("transitionend") + }, + TransitionEventType::TransitionCancel => atom!("transitioncancel"), + }; + + let event_init = TransitionEventInit { parent: EventInit { bubbles: true, cancelable: false, }, - propertyName: DOMString::from(name), - elapsedTime: Finite::new(duration as f32).unwrap(), - // FIXME: Handle pseudo-elements properly + propertyName: DOMString::from(property_name), + elapsedTime: Finite::new(elapsed_time as f32).unwrap(), + // TODO: Handle pseudo-elements properly pseudoElement: DOMString::new(), }; - let transition_event = TransitionEvent::new(&window, atom!("transitionend"), &init); - transition_event.upcast::().fire(node.upcast()); + + let window = window_from_node(&*node); + TransitionEvent::new(&window, event_atom, &event_init) + .upcast::() + .fire(node.upcast()); } /// Handles a Web font being loaded. Does nothing if the page no longer exists. diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 41056f3f6f2..7eb60241f5d 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -282,6 +282,16 @@ pub enum UpdatePipelineIdReason { Traversal, } +/// The type of transition event to trigger. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum TransitionEventType { + /// The transition has ended by reaching the end of its animation. + TransitionEnd, + /// The transition ended early for some reason, such as the property + /// no longer being transitionable or being replaced by another transition. + TransitionCancel, +} + /// Messages sent from the constellation or layout to the script thread. #[derive(Deserialize, Serialize)] pub enum ConstellationControlMsg { @@ -368,8 +378,19 @@ pub enum ConstellationControlMsg { WebDriverScriptCommand(PipelineId, WebDriverScriptCommand), /// Notifies script thread that all animations are done TickAllAnimations(PipelineId), - /// Notifies the script thread of a transition end - TransitionEnd(UntrustedNodeAddress, String, f64), + /// Notifies the script thread that a transition related event should be sent. + TransitionEvent { + /// The pipeline id of the layout task that sent this message. + pipeline_id: PipelineId, + /// The type of transition event this should trigger. + event_type: TransitionEventType, + /// The address of the node which owns this transition. + node: UntrustedNodeAddress, + /// The property name of the property that is transitioning. + property_name: String, + /// The elapsed time property to send with this transition event. + elapsed_time: f64, + }, /// Notifies the script thread that a new Web font has been loaded, and thus the page should be /// reflowed. WebFontLoaded(PipelineId), @@ -429,7 +450,7 @@ impl fmt::Debug for ConstellationControlMsg { FocusIFrame(..) => "FocusIFrame", WebDriverScriptCommand(..) => "WebDriverScriptCommand", TickAllAnimations(..) => "TickAllAnimations", - TransitionEnd(..) => "TransitionEnd", + TransitionEvent { .. } => "TransitionEvent", WebFontLoaded(..) => "WebFontLoaded", DispatchIFrameLoadEvent { .. } => "DispatchIFrameLoadEvent", DispatchStorageEvent(..) => "DispatchStorageEvent", diff --git a/components/style/animation.rs b/components/style/animation.rs index b82d196fa73..68316f8ed5f 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -15,7 +15,7 @@ use crate::font_metrics::FontMetricsProvider; use crate::properties::animated_properties::{AnimatedProperty, TransitionPropertyIteration}; use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState; -use crate::properties::{self, CascadeMode, ComputedValues, LonghandId}; +use crate::properties::{self, CascadeMode, ComputedValues, LonghandId, LonghandIdSet}; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; use crate::stylesheets::Origin; use crate::timer::Timer; @@ -245,6 +245,17 @@ impl Animation { Animation::Keyframes(..) => false, } } + + /// Whether this animation has the same end value as another one. + #[inline] + pub fn is_transition_with_same_end_value(&self, other_animation: &PropertyAnimation) -> bool { + match *self { + Animation::Transition(_, _, ref animation) => { + animation.has_the_same_end_value_as(other_animation) + }, + Animation::Keyframes(..) => false, + } + } } /// Represents an animation for a given property. @@ -261,6 +272,11 @@ pub struct PropertyAnimation { } impl PropertyAnimation { + /// Returns the given property longhand id. + pub fn property_id(&self) -> LonghandId { + self.property.id() + } + /// Returns the given property name. pub fn property_name(&self) -> &'static str { self.property.name() @@ -351,20 +367,86 @@ impl PropertyAnimation { } } -/// Inserts transitions into the queue of running animations as applicable for -/// the given style difference. This is called from the layout worker threads. -/// Returns true if any animations were kicked off and false otherwise. -pub fn start_transitions_if_applicable( +/// Start any new transitions for this node and ensure that any existing transitions +/// that are cancelled are marked as cancelled in the SharedStyleContext. This is +/// at the end of calculating style for a single node. +#[cfg(feature = "servo")] +pub fn update_transitions( + context: &SharedStyleContext, new_animations_sender: &Sender, opaque_node: OpaqueNode, old_style: &ComputedValues, new_style: &mut Arc, - timer: &Timer, - running_and_expired_transitions: &[PropertyAnimation], -) -> bool { - let mut had_animations = false; + expired_transitions: &[PropertyAnimation], +) { + let mut all_running_animations = context.running_animations.write(); + let previously_running_animations = all_running_animations + .remove(&opaque_node) + .unwrap_or_else(Vec::new); + + let properties_that_transition = start_transitions_if_applicable( + context, + new_animations_sender, + opaque_node, + old_style, + new_style, + expired_transitions, + &previously_running_animations, + ); + + let mut all_cancelled_animations = context.cancelled_animations.write(); + let mut cancelled_animations = all_cancelled_animations + .remove(&opaque_node) + .unwrap_or_else(Vec::new); + let mut running_animations = vec![]; + + // For every animation that was running before this style change, we cancel it + // if the property no longer transitions. + for running_animation in previously_running_animations.into_iter() { + if let Animation::Transition(_, _, ref property_animation) = running_animation { + if !properties_that_transition.contains(property_animation.property_id()) { + cancelled_animations.push(running_animation); + continue; + } + } + running_animations.push(running_animation); + } + + if !cancelled_animations.is_empty() { + all_cancelled_animations.insert(opaque_node, cancelled_animations); + } + if !running_animations.is_empty() { + all_running_animations.insert(opaque_node, running_animations); + } +} + +/// Kick off any new transitions for this node and return all of the properties that are +/// transitioning. This is at the end of calculating style for a single node. +#[cfg(feature = "servo")] +pub fn start_transitions_if_applicable( + context: &SharedStyleContext, + new_animations_sender: &Sender, + opaque_node: OpaqueNode, + old_style: &ComputedValues, + new_style: &mut Arc, + expired_transitions: &[PropertyAnimation], + running_animations: &[Animation], +) -> LonghandIdSet { + // If the style of this element is display:none, then we don't start any transitions + // and we cancel any currently running transitions by returning an empty LonghandIdSet. + if new_style.get_box().clone_display().is_none() { + return LonghandIdSet::new(); + } + + let mut properties_that_transition = LonghandIdSet::new(); let transitions: Vec = new_style.transition_properties().collect(); for transition in &transitions { + if properties_that_transition.contains(transition.longhand_id) { + continue; + } else { + properties_that_transition.insert(transition.longhand_id); + } + let property_animation = match PropertyAnimation::from_longhand( transition.longhand_id, new_style @@ -387,20 +469,27 @@ pub fn start_transitions_if_applicable( property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0); // Per [1], don't trigger a new transition if the end state for that - // transition is the same as that of a transition that's already - // running on the same node. - // + // transition is the same as that of a transition that's expired. // [1]: https://drafts.csswg.org/css-transitions/#starting - debug!( - "checking {:?} for matching end value", - running_and_expired_transitions - ); - if running_and_expired_transitions + debug!("checking {:?} for matching end value", expired_transitions); + if expired_transitions .iter() .any(|animation| animation.has_the_same_end_value_as(&property_animation)) { debug!( - "Not initiating transition for {}, other transition \ + "Not initiating transition for {}, expired transition \ + found with the same end value", + property_animation.property_name() + ); + continue; + } + + if running_animations + .iter() + .any(|animation| animation.is_transition_with_same_end_value(&property_animation)) + { + debug!( + "Not initiating transition for {}, running transition \ found with the same end value", property_animation.property_name() ); @@ -410,7 +499,7 @@ pub fn start_transitions_if_applicable( // Kick off the animation. debug!("Kicking off transition of {:?}", property_animation); let box_style = new_style.get_box(); - let now = timer.seconds(); + let now = context.timer.seconds(); let start_time = now + (box_style.transition_delay_mod(transition.index).seconds() as f64); new_animations_sender .send(Animation::Transition( @@ -419,11 +508,9 @@ pub fn start_transitions_if_applicable( property_animation, )) .unwrap(); - - had_animations = true; } - had_animations + properties_that_transition } fn compute_style_for_animation_step( @@ -608,12 +695,6 @@ where if progress >= 0.0 { property_animation.update(Arc::make_mut(style), progress); } - - // FIXME(emilio): Should check before updating the style that the - // transition_property still transitions this, or bail out if not. - // - // Or doing it in process_animations, only if transition_property - // changed somehow (even better). AnimationUpdate::Regular }, Animation::Keyframes(_, ref animation, ref name, ref state) => { diff --git a/components/style/context.rs b/components/style/context.rs index d07cf6bd94c..a9c7f4125b3 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -194,6 +194,10 @@ pub struct SharedStyleContext<'a> { #[cfg(feature = "servo")] pub expired_animations: Arc>>>, + /// The list of animations that have expired since the last style recalculation. + #[cfg(feature = "servo")] + pub cancelled_animations: Arc>>>, + /// Paint worklets #[cfg(feature = "servo")] pub registered_speculative_painters: &'a dyn RegisteredSpeculativePainters, diff --git a/components/style/matching.rs b/components/style/matching.rs index f99f15efc3e..15b952d27e3 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -440,7 +440,7 @@ trait PrivateMatchMethods: TElement { use crate::animation; let this_opaque = self.as_node().opaque(); - let mut running_and_expired_transitions = vec![]; + let mut expired_transitions = vec![]; let shared_context = context.shared; if let Some(ref mut old_values) = *old_values { // We apply the expired transitions and animations to the old style @@ -454,14 +454,13 @@ trait PrivateMatchMethods: TElement { shared_context, this_opaque, old_values, - &mut running_and_expired_transitions, + &mut expired_transitions, ); - Self::update_style_for_animations_and_collect_running_transitions( + Self::update_style_for_animations( shared_context, this_opaque, old_values, - &mut running_and_expired_transitions, &context.thread_local.font_metrics_provider, ); } @@ -479,13 +478,13 @@ trait PrivateMatchMethods: TElement { // Trigger transitions if necessary. This will set `new_values` to // the starting value of the transition if it did trigger a transition. if let Some(ref values) = old_values { - animation::start_transitions_if_applicable( + animation::update_transitions( + &shared_context, new_animations_sender, this_opaque, &values, new_values, - &shared_context.timer, - &running_and_expired_transitions, + &expired_transitions, ); } } @@ -627,33 +626,30 @@ trait PrivateMatchMethods: TElement { } #[cfg(feature = "servo")] - fn update_style_for_animations_and_collect_running_transitions( + fn update_style_for_animations( context: &SharedStyleContext, node: OpaqueNode, style: &mut Arc, - running_transitions: &mut Vec, font_metrics: &dyn crate::font_metrics::FontMetricsProvider, ) { use crate::animation::{self, Animation, AnimationUpdate}; - let had_running_animations = context.running_animations.read().get(&node).is_some(); - if !had_running_animations { - return; - } - let mut all_running_animations = context.running_animations.write(); - for mut running_animation in all_running_animations.get_mut(&node).unwrap() { - if let Animation::Transition(_, _, ref property_animation) = *running_animation { - running_transitions.push(property_animation.clone()); - continue; - } + let running_animations = match all_running_animations.get_mut(&node) { + Some(running_animations) => running_animations, + None => return, + }; - let update = animation::update_style_for_animation::( - context, - &mut running_animation, - style, - font_metrics, - ); + for running_animation in running_animations.iter_mut() { + let update = match *running_animation { + Animation::Transition(..) => continue, + Animation::Keyframes(..) => animation::update_style_for_animation::( + context, + running_animation, + style, + font_metrics, + ), + }; match *running_animation { Animation::Transition(..) => unreachable!(), diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 312c55e5a1e..c87ff76cf0d 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -398363,6 +398363,13 @@ } ] ], + "transitioncancel-002.html": [ + "e62b17b5dc60ef762e0a0780c967b6e014da5bc9", + [ + null, + {} + ] + ], "transitionevent-interface.html": [ "a40ba4537518361c13aab1d9b0648387f7c88aaa", [ diff --git a/tests/wpt/metadata/css/css-transitions/idlharness.html.ini b/tests/wpt/metadata/css/css-transitions/idlharness.html.ini index 8af70218a41..21836fbea38 100644 --- a/tests/wpt/metadata/css/css-transitions/idlharness.html.ini +++ b/tests/wpt/metadata/css/css-transitions/idlharness.html.ini @@ -5,42 +5,24 @@ [HTMLElement interface: attribute ontransitionstart] expected: FAIL - [Document interface: attribute ontransitioncancel] - expected: FAIL - [Document interface: document must inherit property "ontransitionstart" with the proper type] expected: FAIL - [Document interface: document must inherit property "ontransitioncancel" with the proper type] - expected: FAIL - [HTMLElement interface: attribute ontransitionrun] expected: FAIL - [HTMLElement interface: document must inherit property "ontransitioncancel" with the proper type] - expected: FAIL - [Window interface: attribute ontransitionrun] expected: FAIL - [Window interface: attribute ontransitioncancel] - expected: FAIL - [HTMLElement interface: document must inherit property "ontransitionstart" with the proper type] expected: FAIL - [HTMLElement interface: attribute ontransitioncancel] - expected: FAIL - [HTMLElement interface: document must inherit property "ontransitionrun" with the proper type] expected: FAIL [Window interface: window must inherit property "ontransitionrun" with the proper type] expected: FAIL - [Window interface: window must inherit property "ontransitioncancel" with the proper type] - expected: FAIL - [Document interface: attribute ontransitionstart] expected: FAIL diff --git a/tests/wpt/metadata/css/css-transitions/transitioncancel-001.html.ini b/tests/wpt/metadata/css/css-transitions/transitioncancel-001.html.ini deleted file mode 100644 index 86a712cce5b..00000000000 --- a/tests/wpt/metadata/css/css-transitions/transitioncancel-001.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[transitioncancel-001.html] - [transitioncancel should be fired if the element is made display:none during the transition] - expected: FAIL - diff --git a/tests/wpt/web-platform-tests/css/css-transitions/transitioncancel-002.html b/tests/wpt/web-platform-tests/css/css-transitions/transitioncancel-002.html new file mode 100644 index 00000000000..e62b17b5dc6 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-transitions/transitioncancel-002.html @@ -0,0 +1,46 @@ + + + + +CSS Transitions Test: Removing transitioning property from transition-property triggers transitioncancel + + + + + + + + + + +
+ + + + +