aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/Cargo.toml1
-rw-r--r--components/script/animation_timeline.rs2
-rw-r--r--components/script/animations.rs293
-rw-r--r--components/script/dom/bindings/trace.rs32
-rw-r--r--components/script/dom/document.rs20
-rw-r--r--components/script/dom/window.rs12
-rw-r--r--components/script/lib.rs1
-rw-r--r--components/script/script_thread.rs56
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)
},