diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/Cargo.toml | 1 | ||||
-rw-r--r-- | components/script/animation_timeline.rs | 2 | ||||
-rw-r--r-- | components/script/animations.rs | 293 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 32 | ||||
-rw-r--r-- | components/script/dom/document.rs | 20 | ||||
-rw-r--r-- | components/script/dom/window.rs | 12 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/script_thread.rs | 56 |
8 files changed, 382 insertions, 35 deletions
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index f4d8808c869..9f98ff30909 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -53,6 +53,7 @@ encoding_rs = "0.8" enum-iterator = "0.3" euclid = "0.20" fnv = "1.0" +fxhash = "0.2" headers = "0.2" html5ever = "0.25" http = "0.1" diff --git a/components/script/animation_timeline.rs b/components/script/animation_timeline.rs index e0ad520db61..54127b94e9e 100644 --- a/components/script/animation_timeline.rs +++ b/components/script/animation_timeline.rs @@ -12,7 +12,7 @@ use time; /// A `AnimationTimeline` which is used to synchronize animations during the script /// event loop. #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] -pub struct AnimationTimeline { +pub(crate) struct AnimationTimeline { current_value: f64, } diff --git a/components/script/animations.rs b/components/script/animations.rs new file mode 100644 index 00000000000..a666ac3ed84 --- /dev/null +++ b/components/script/animations.rs @@ -0,0 +1,293 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![deny(missing_docs)] + +//! The set of animations for a document. + +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 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)] +pub(crate) struct Animations { + pub sets: Arc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>, + have_running_animations: bool, +} + +impl Animations { + pub(crate) fn new() -> Self { + Animations { + sets: Default::default(), + have_running_animations: false, + } + } + + /// Processes any new animations that were discovered after reflow. Collect messages + /// that trigger events for any animations that changed state. + /// TODO(mrobinson): The specification dictates that this should happen before reflow. + pub(crate) fn do_post_reflow_update(&mut self, window: &Window, now: f64) -> AnimationsUpdate { + let mut update = AnimationsUpdate::new(window.pipeline_id()); + + { + let mut sets = self.sets.write(); + update.collect_newly_animating_nodes(&sets); + + for set in sets.values_mut() { + Self::handle_canceled_animations(set, now, &mut update); + Self::finish_running_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()); + } + + self.update_running_animations_presence(window); + + update + } + + pub(crate) fn running_animation_count(&self) -> usize { + self.sets + .read() + .values() + .map(|state| state.running_animation_and_transition_count()) + .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( + set: &mut ElementAnimationSet, + now: f64, + update: &mut AnimationsUpdate, + ) { + 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(), + TransitionOrAnimationEventType::AnimationEnd, + animation.active_duration(), + ); + } + } + + 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_name().into(), + TransitionOrAnimationEventType::TransitionEnd, + transition.property_animation.duration, + ); + } + } + } + + /// Send events for canceled animations. Currently this only handles canceled + /// transitions, but eventually this should handle canceled CSS animations as + /// well. + fn handle_canceled_animations( + set: &mut ElementAnimationSet, + now: f64, + update: &mut AnimationsUpdate, + ) { + 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_name().into(), + TransitionOrAnimationEventType::TransitionCancel, + (now - transition.start_time).max(0.), + ); + } + } + + // TODO(mrobinson): We need to send animationcancel events. + set.clear_canceled_animations(); + } + + fn handle_new_animations(set: &mut ElementAnimationSet, update: &mut AnimationsUpdate) { + 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_name().into(), + TransitionOrAnimationEventType::TransitionRun, + 0., + ); + transition.is_new = false; + } + } + } +} + +impl MallocSizeOf for Animations { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.sets.read().size_of(ops) + self.have_running_animations.size_of(ops) + } +} + +pub(crate) struct AnimationsUpdate { + pub pipeline_id: PipelineId, + pub events: Vec<TransitionOrAnimationEvent>, + pub newly_animating_nodes: Vec<UntrustedNodeAddress>, +} + +impl AnimationsUpdate { + fn new(pipeline_id: PipelineId) -> Self { + AnimationsUpdate { + pipeline_id, + events: Default::default(), + newly_animating_nodes: Default::default(), + } + } + + 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, + }); + } + + pub(crate) fn is_empty(&self) -> bool { + self.events.is_empty() && self.newly_animating_nodes.is_empty() + } + + /// 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<OpaqueNode, ElementAnimationSet>, + ) { + // This extends the output vector with an iterator that contains a copy of the node + // address for every new animation. The script thread currently stores a rooted node + // for every property that is transitioning. The current strategy of repeating the + // node address is a holdover from when the code here looked different. + self.newly_animating_nodes + .extend(animation_states.iter().flat_map(|(node, state)| { + let mut num_new_animations = state + .animations + .iter() + .filter(|animation| animation.is_new) + .count(); + num_new_animations += state + .transitions + .iter() + .filter(|transition| transition.is_new) + .count(); + + let node = UntrustedNodeAddress(node.0 as *const c_void); + std::iter::repeat(node).take(num_new_animations) + })); + } +} + +/// 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)] +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 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 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 => false, + } + } + + /// 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 => false, + } + } +} + +#[derive(Deserialize, JSTraceable, Serialize)] +/// A transition or animation event. +pub struct TransitionOrAnimationEvent { + /// The pipeline id of the layout task that sent this message. + pub pipeline_id: PipelineId, + /// The type of transition event this should trigger. + pub event_type: TransitionOrAnimationEventType, + /// The address of the node which owns this transition. + pub node: UntrustedNodeAddress, + /// 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, + /// The elapsed time property to send with this transition event. + pub elapsed_time: f64, +} diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index c2543cc984b..26db47233f9 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -93,6 +93,7 @@ use net_traits::response::HttpsState; use net_traits::response::{Response, ResponseBody}; use net_traits::storage_thread::StorageType; use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming, ResourceThreads}; +use parking_lot::RwLock; use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_layout_interface::message::PendingRestyle; @@ -100,9 +101,11 @@ use script_layout_interface::rpc::LayoutRPC; use script_layout_interface::StyleAndOpaqueLayoutData; use script_traits::serializable::BlobImpl; use script_traits::transferable::MessagePortImpl; -use script_traits::{DocumentActivity, DrawAPaintImageResult}; -use script_traits::{MediaSessionActionType, ScriptToConstellationChan, TimerEventId, TimerSource}; -use script_traits::{UntrustedNodeAddress, WebrenderIpcSender, WindowSizeData, WindowSizeType}; +use script_traits::{ + DocumentActivity, DrawAPaintImageResult, MediaSessionActionType, ScriptToConstellationChan, + TimerEventId, TimerSource, UntrustedNodeAddress, WebrenderIpcSender, WindowSizeData, + WindowSizeType, +}; use selectors::matching::ElementSelectorFlags; use serde::{Deserialize, Serialize}; use servo_arc::Arc as ServoArc; @@ -131,6 +134,7 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::{Arc, Mutex}; use std::time::{Instant, SystemTime}; +use style::animation::ElementAnimationSet; use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto}; use style::author_styles::AuthorStyles; use style::context::QuirksMode; @@ -172,7 +176,6 @@ unsafe_no_jsmanaged_fields!(Box<dyn TaskBox>, Box<dyn EventLoopWaker>); unsafe_no_jsmanaged_fields!(MessagePortImpl); unsafe_no_jsmanaged_fields!(MessagePortId); -unsafe_no_jsmanaged_fields!(RefCell<Option<MessagePortId>>); unsafe_no_jsmanaged_fields!(MessagePortRouterId); unsafe_no_jsmanaged_fields!(BroadcastChannelRouterId); @@ -184,8 +187,7 @@ unsafe_no_jsmanaged_fields!(CSSError); unsafe_no_jsmanaged_fields!(&'static Encoding); -unsafe_no_jsmanaged_fields!(RefCell<Decoder>); -unsafe_no_jsmanaged_fields!(RefCell<Vec<u8>>); +unsafe_no_jsmanaged_fields!(Decoder); unsafe_no_jsmanaged_fields!(Reflector); @@ -252,6 +254,12 @@ unsafe impl<T: JSTraceable> JSTraceable for ServoArc<T> { } } +unsafe impl<T: JSTraceable> JSTraceable for RwLock<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + self.read().trace(trc) + } +} + unsafe impl<T: JSTraceable + ?Sized> JSTraceable for Box<T> { unsafe fn trace(&self, trc: *mut JSTracer) { (**self).trace(trc) @@ -284,6 +292,12 @@ unsafe impl<T: JSTraceable> JSTraceable for DomRefCell<T> { } } +unsafe impl<T: JSTraceable> JSTraceable for RefCell<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + (*self).borrow().trace(trc) + } +} + unsafe impl JSTraceable for Heap<*mut JSObject> { unsafe fn trace(&self, trc: *mut JSTracer) { if self.get().is_null() { @@ -530,8 +544,7 @@ unsafe_no_jsmanaged_fields!(WebGLTextureId); unsafe_no_jsmanaged_fields!(WebGLVertexArrayId); unsafe_no_jsmanaged_fields!(WebGLVersion); unsafe_no_jsmanaged_fields!(WebGLSLVersion); -unsafe_no_jsmanaged_fields!(RefCell<Option<WebGPU>>); -unsafe_no_jsmanaged_fields!(RefCell<Identities>); +unsafe_no_jsmanaged_fields!(Identities); unsafe_no_jsmanaged_fields!(WebGPU); unsafe_no_jsmanaged_fields!(WebGPUAdapter); unsafe_no_jsmanaged_fields!(WebGPUBuffer); @@ -544,7 +557,7 @@ unsafe_no_jsmanaged_fields!(WebGPUShaderModule); unsafe_no_jsmanaged_fields!(WebGPUCommandBuffer); unsafe_no_jsmanaged_fields!(WebGPUCommandEncoder); unsafe_no_jsmanaged_fields!(WebGPUDevice); -unsafe_no_jsmanaged_fields!(RefCell<Option<RawPass>>); +unsafe_no_jsmanaged_fields!(Option<RawPass>); unsafe_no_jsmanaged_fields!(GPUBufferState); unsafe_no_jsmanaged_fields!(WebXRSwapChainId); unsafe_no_jsmanaged_fields!(MediaList); @@ -586,6 +599,7 @@ unsafe_no_jsmanaged_fields!(MediaSessionActionType); unsafe_no_jsmanaged_fields!(MediaMetadata); unsafe_no_jsmanaged_fields!(WebrenderIpcSender); unsafe_no_jsmanaged_fields!(StreamConsumer); +unsafe_no_jsmanaged_fields!(ElementAnimationSet); unsafe impl<'a> JSTraceable for &'a str { #[inline] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 5e40f433da5..86c5b1caaa1 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -3,6 +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::document_loader::{DocumentLoader, LoadType}; use crate::dom::attr::Attr; use crate::dom::beforeunloadevent::BeforeUnloadEvent; @@ -384,6 +385,8 @@ pub struct Document { /// A timeline for animations which is used for synchronizing animations. /// https://drafts.csswg.org/web-animations/#timeline animation_timeline: DomRefCell<AnimationTimeline>, + /// Animations for this Document + animations: DomRefCell<Animations>, } #[derive(JSTraceable, MallocSizeOf)] @@ -2913,6 +2916,7 @@ impl Document { } else { DomRefCell::new(AnimationTimeline::new()) }, + animations: DomRefCell::new(Animations::new()), } } @@ -3615,17 +3619,27 @@ impl Document { .collect() } - pub fn advance_animation_timeline_for_testing(&self, delta: f64) { + pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) { self.animation_timeline.borrow_mut().advance_specific(delta); } - pub fn update_animation_timeline(&self) { + pub(crate) fn update_animation_timeline(&self) { self.animation_timeline.borrow_mut().update(); } - pub fn current_animation_timeline_value(&self) -> f64 { + pub(crate) fn current_animation_timeline_value(&self) -> f64 { self.animation_timeline.borrow().current_value() } + + pub(crate) fn animations(&self) -> Ref<Animations> { + self.animations.borrow() + } + + pub(crate) fn update_animations(&self) -> AnimationsUpdate { + self.animations + .borrow_mut() + .do_post_reflow_update(&self.window, self.current_animation_timeline_value()) + } } impl Element { diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 414ef0f2e45..cbf19039605 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -81,7 +81,7 @@ use dom_struct::dom_struct; use embedder_traits::{EmbedderMsg, EventLoopWaker, PromptDefinition, PromptOrigin, PromptResult}; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; -use ipc_channel::ipc::{channel, IpcSender}; +use ipc_channel::ipc::IpcSender; use ipc_channel::router::ROUTER; use js::jsapi::Heap; use js::jsapi::JSAutoRealm; @@ -1305,9 +1305,9 @@ impl WindowMethods for Window { } fn RunningAnimationCount(&self) -> u32 { - let (sender, receiver) = channel().unwrap(); - let _ = self.layout_chan.send(Msg::GetRunningAnimations(sender)); - receiver.recv().unwrap_or(0) as u32 + self.document + .get() + .map_or(0, |d| d.animations().running_animation_count() as u32) } // https://html.spec.whatwg.org/multipage/#dom-name @@ -1643,6 +1643,7 @@ impl Window { dom_count: document.dom_count(), pending_restyles: document.drain_pending_restyles(), animation_timeline_value: document.current_animation_timeline_value(), + animations: document.animations().sets.clone(), }; self.layout_chan @@ -1706,8 +1707,9 @@ impl Window { } } + let update = document.update_animations(); unsafe { - ScriptThread::note_newly_animating_nodes(pipeline_id, complete.newly_animating_nodes); + ScriptThread::process_animations_update(update); } true diff --git a/components/script/lib.rs b/components/script/lib.rs index cd73045456a..886a76e9c95 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -48,6 +48,7 @@ extern crate servo_atoms; extern crate style; mod animation_timeline; +mod animations; #[warn(deprecated)] #[macro_use] mod task; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 008274ded47..9754aa65ede 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -17,6 +17,9 @@ //! 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; @@ -141,8 +144,8 @@ use script_traits::{ LayoutMsg, LoadData, LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType, NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory, ScriptToConstellationChan, StructuredSerializedData, TimerSchedulerMsg, TouchEventType, - TouchId, TransitionOrAnimationEvent, TransitionOrAnimationEventType, UntrustedNodeAddress, - UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, WindowSizeData, WindowSizeType, + TouchId, UntrustedNodeAddress, UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, + WindowSizeData, WindowSizeType, }; use servo_atoms::Atom; use servo_config::opts; @@ -510,10 +513,8 @@ impl<'a> Iterator for DocumentsIter<'a> { // thread during parsing. For this reason, we don't trace the // incomplete parser contexts during GC. type IncompleteParserContexts = Vec<(PipelineId, ParserContext)>; -unsafe_no_jsmanaged_fields!(RefCell<IncompleteParserContexts>); unsafe_no_jsmanaged_fields!(TaskQueue<MainThreadScriptMsg>); - unsafe_no_jsmanaged_fields!(dyn BackgroundHangMonitorRegister); unsafe_no_jsmanaged_fields!(dyn BackgroundHangMonitor); @@ -644,6 +645,9 @@ pub struct ScriptThread { /// of the transition. animating_nodes: DomRefCell<HashMap<PipelineId, Vec<Dom<Node>>>>, + /// Animations events that are pending to be sent. + animation_events: RefCell<Vec<TransitionOrAnimationEvent>>, + /// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack> custom_element_reaction_stack: CustomElementReactionStack, @@ -826,20 +830,35 @@ impl ScriptThread { }) } - pub unsafe fn note_newly_animating_nodes( - pipeline_id: PipelineId, - nodes: Vec<UntrustedNodeAddress>, - ) { + /// 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 = nodes + 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(pipeline_id) + .entry(update.pipeline_id) .or_insert_with(Vec::new) .extend(new_nodes); }) @@ -1354,6 +1373,7 @@ impl ScriptThread { docs_with_no_blocking_loads: Default::default(), animating_nodes: Default::default(), + animation_events: Default::default(), custom_element_reaction_stack: CustomElementReactionStack::new(), @@ -1620,9 +1640,8 @@ impl ScriptThread { } // Perform step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops. - // TODO(mrobinson): This should also update the current animations and send events - // to conform to the HTML specification. This might mean having events for rooting - // DOM nodes and ultimately all animations living in script. + // TODO(mrobinson): This should also update the current animations to conform to + // the HTML specification. fn update_animations_and_send_events(&self) { for (_, document) in self.documents.borrow().iter() { // Only update the time if it isn't being managed by a test. @@ -1630,6 +1649,13 @@ impl ScriptThread { document.update_animation_timeline(); } } + + // 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); + } } fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory { @@ -1720,7 +1746,6 @@ impl ScriptThread { FocusIFrame(id, ..) => Some(id), WebDriverScriptCommand(id, ..) => Some(id), TickAllAnimations(id, ..) => Some(id), - TransitionOrAnimationEvent { .. } => None, WebFontLoaded(id) => Some(id), DispatchIFrameLoadEvent { target: _, @@ -1921,9 +1946,6 @@ impl ScriptThread { ConstellationControlMsg::TickAllAnimations(pipeline_id, tick_type) => { self.handle_tick_all_animations(pipeline_id, tick_type) }, - ConstellationControlMsg::TransitionOrAnimationEvent(ref event) => { - self.handle_transition_or_animation_event(event); - }, ConstellationControlMsg::WebFontLoaded(pipeline_id) => { self.handle_web_font_loaded(pipeline_id) }, |