Move most animation processing to script

This is preparation for sharing this code with layout_2020 and
implementing selective off-the-main-thread animations.

We still look for nodes not in the flow tree in the layout thread.
This commit is contained in:
Martin Robinson 2020-05-07 18:37:18 +02:00
parent aa9f16ce45
commit 3b0619aedd
21 changed files with 444 additions and 371 deletions

3
Cargo.lock generated
View file

@ -4562,6 +4562,7 @@ dependencies = [
"enum-iterator",
"euclid",
"fnv",
"fxhash",
"headers",
"html5ever",
"http",
@ -4641,6 +4642,7 @@ dependencies = [
"canvas_traits",
"crossbeam-channel",
"euclid",
"fxhash",
"gfx_traits",
"html5ever",
"ipc-channel",
@ -4650,6 +4652,7 @@ dependencies = [
"metrics",
"msg",
"net_traits",
"parking_lot",
"profile_traits",
"range",
"script_traits",

View file

@ -2237,9 +2237,6 @@ where
fn handle_request_from_layout(&mut self, message: FromLayoutMsg) {
debug!("Constellation got {:?} message", message);
match message {
FromLayoutMsg::ChangeRunningAnimationsState(pipeline_id, animation_state) => {
self.handle_change_running_animations_state(pipeline_id, animation_state)
},
// Layout sends new sizes for all subframes. This needs to be reflected by all
// frame trees in the navigation context containing the subframe.
FromLayoutMsg::IFrameSizes(iframe_sizes) => {

View file

@ -1,218 +0,0 @@
/* 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/. */
//! CSS transitions and animations.
use crate::display_list::items::OpaqueNode;
use crate::flow::{Flow, GetBaseFlow};
use crate::opaque_node::OpaqueNodeMethods;
use fxhash::{FxHashMap, FxHashSet};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{
AnimationState as AnimationsPresentState, ConstellationControlMsg,
LayoutMsg as ConstellationMsg, TransitionOrAnimationEvent, TransitionOrAnimationEventType,
};
use style::animation::{AnimationState, ElementAnimationSet};
/// Processes any new animations that were discovered after style recalculation and
/// remove animations for any disconnected nodes. Send messages that trigger events
/// for any events that changed state.
pub fn do_post_style_animations_update(
constellation_chan: &IpcSender<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>,
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationSet>,
pipeline_id: PipelineId,
now: f64,
out: &mut Vec<UntrustedNodeAddress>,
root_flow: &mut dyn Flow,
) {
let had_running_animations = animation_states
.values()
.any(|state| state.needs_animation_ticks());
cancel_animations_for_disconnected_nodes(animation_states, root_flow);
collect_newly_animating_nodes(animation_states, out);
for (node, animation_state) in animation_states.iter_mut() {
update_animation_state(script_chan, animation_state, pipeline_id, now, *node);
}
// 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.
animation_states.retain(|_, state| !state.is_empty());
let have_running_animations = animation_states
.values()
.any(|state| state.needs_animation_ticks());
let present = match (had_running_animations, have_running_animations) {
(true, false) => AnimationsPresentState::NoAnimationsPresent,
(false, true) => AnimationsPresentState::AnimationsPresent,
_ => return,
};
constellation_chan
.send(ConstellationMsg::ChangeRunningAnimationsState(
pipeline_id,
present,
))
.unwrap();
}
/// 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.
pub fn collect_newly_animating_nodes(
animation_states: &FxHashMap<OpaqueNode, ElementAnimationSet>,
out: &mut Vec<UntrustedNodeAddress>,
) {
// 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.
out.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();
std::iter::repeat(node.to_untrusted_node_address()).take(num_new_animations)
}));
}
/// Cancel animations for any nodes which have been removed from the DOM or are display:none.
/// We detect this by looking for nodes that are used in the flow tree.
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
/// This also doesn't yet handles nodes that have been reparented.
pub fn cancel_animations_for_disconnected_nodes(
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationSet>,
root_flow: &mut dyn Flow,
) {
// Assume all nodes have been removed until proven otherwise.
let mut invalid_nodes: FxHashSet<OpaqueNode> = animation_states.keys().cloned().collect();
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<OpaqueNode>) {
flow.mutate_fragments(&mut |fragment| {
invalid_nodes.remove(&fragment.node);
});
for kid in flow.mut_base().children.iter_mut() {
traverse_flow(kid, invalid_nodes)
}
}
traverse_flow(root_flow, &mut invalid_nodes);
// Cancel animations for any nodes that are no longer in the flow tree.
for node in &invalid_nodes {
if let Some(state) = animation_states.get_mut(node) {
state.cancel_all_animations();
}
}
}
fn update_animation_state(
script_channel: &IpcSender<ConstellationControlMsg>,
animation_state: &mut ElementAnimationSet,
pipeline_id: PipelineId,
now: f64,
node: OpaqueNode,
) {
let send_event = |property_or_animation_name, event_type, elapsed_time| {
script_channel
.send(ConstellationControlMsg::TransitionOrAnimationEvent(
TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: node.to_untrusted_node_address(),
property_or_animation_name,
elapsed_time,
},
))
.unwrap()
};
handle_canceled_animations(animation_state, now, send_event);
finish_running_animations(animation_state, now, send_event);
handle_new_animations(animation_state, send_event);
}
/// Walk through the list of running animations and remove all of the ones that
/// have ended.
fn finish_running_animations(
animation_state: &mut ElementAnimationSet,
now: f64,
mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) {
for animation in animation_state.animations.iter_mut() {
if animation.state == AnimationState::Running && animation.has_ended(now) {
animation.state = AnimationState::Finished;
send_event(
animation.name.to_string(),
TransitionOrAnimationEventType::AnimationEnd,
animation.active_duration(),
);
}
}
for transition in animation_state.transitions.iter_mut() {
if transition.state == AnimationState::Running && transition.has_ended(now) {
transition.state = AnimationState::Finished;
send_event(
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(
animation_state: &mut ElementAnimationSet,
now: f64,
mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) {
for transition in &animation_state.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
send_event(
transition.property_animation.property_name().into(),
TransitionOrAnimationEventType::TransitionCancel,
(now - transition.start_time).max(0.),
);
}
}
// TODO(mrobinson): We need to send animationcancel events.
animation_state.clear_canceled_animations();
}
fn handle_new_animations(
animation_state: &mut ElementAnimationSet,
mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) {
for animation in animation_state.animations.iter_mut() {
animation.is_new = false;
}
for transition in animation_state.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
send_event(
transition.property_animation.property_name().into(),
TransitionOrAnimationEventType::TransitionRun,
0.,
);
transition.is_new = false;
}
}
}

View file

@ -17,7 +17,6 @@ use net_traits::image_cache::{
use parking_lot::RwLock;
use script_layout_interface::{PendingImage, PendingImageState};
use script_traits::Painter;
use script_traits::UntrustedNodeAddress;
use servo_atoms::Atom;
use servo_url::{ImmutableOrigin, ServoUrl};
use std::cell::{RefCell, RefMut};
@ -85,9 +84,6 @@ pub struct LayoutContext<'a> {
/// A list of in-progress image loads to be shared with the script thread.
pub pending_images: Mutex<Vec<PendingImage>>,
/// A list of nodes that have just initiated a CSS transition or animation.
pub newly_animating_nodes: Mutex<Vec<UntrustedNodeAddress>>,
}
impl<'a> Drop for LayoutContext<'a> {

View file

@ -20,7 +20,6 @@ extern crate serde;
#[macro_use]
pub mod layout_debug;
pub mod animation;
mod block;
pub mod construct;
pub mod context;

View file

@ -29,7 +29,7 @@ use crossbeam_channel::{Receiver, Sender};
use embedder_traits::resources::{self, Resource};
use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D};
use fnv::FnvHashMap;
use fxhash::FxHashMap;
use fxhash::{FxHashMap, FxHashSet};
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
@ -37,7 +37,6 @@ use gfx_traits::{node_id_from_scroll_id, Epoch};
use histogram::Histogram;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use layout::animation;
use layout::construct::ConstructionResult;
use layout::context::malloc_size_of_persistent_local_context;
use layout::context::LayoutContext;
@ -191,9 +190,6 @@ pub struct LayoutThread {
/// The document-specific shared lock used for author-origin stylesheets
document_shared_lock: Option<SharedRwLock>,
/// The animation state for all of our nodes.
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
/// A counter for epoch messages
epoch: Cell<Epoch>,
@ -552,7 +548,6 @@ impl LayoutThread {
outstanding_web_fonts: Arc::new(AtomicUsize::new(0)),
root_flow: RefCell::new(None),
document_shared_lock: None,
animation_states: 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)),
@ -613,6 +608,7 @@ impl LayoutThread {
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
) -> LayoutContext<'a> {
LayoutContext {
id: self.id,
@ -622,7 +618,7 @@ impl LayoutThread {
options: GLOBAL_STYLE_DATA.options.clone(),
guards,
visited_styles_enabled: false,
animation_states: self.animation_states.clone(),
animation_states,
registered_speculative_painters: &self.registered_painters,
current_time_for_animations: animation_timeline_value,
traversal_flags: TraversalFlags::empty(),
@ -632,7 +628,6 @@ impl LayoutThread {
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: Mutex::new(vec![]),
newly_animating_nodes: Mutex::new(vec![]),
registered_painters: &self.registered_painters,
}
}
@ -657,7 +652,6 @@ impl LayoutThread {
},
Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint,
Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart,
Msg::GetRunningAnimations(..) => LayoutHangAnnotation::GetRunningAnimations,
};
self.background_hang_monitor
.as_ref()
@ -824,15 +818,6 @@ impl LayoutThread {
Msg::SetNavigationStart(time) => {
self.paint_time_metrics.set_navigation_start(time);
},
Msg::GetRunningAnimations(sender) => {
let running_animation_count = self
.animation_states
.read()
.values()
.map(|state| state.running_animation_and_transition_count())
.sum();
let _ = sender.send(running_animation_count);
},
}
true
@ -1413,8 +1398,13 @@ impl LayoutThread {
self.stylist.flush(&guards, Some(element), Some(&map));
// Create a layout context for use throughout the following passes.
let mut layout_context =
self.build_layout_context(guards.clone(), &map, origin, data.animation_timeline_value);
let mut layout_context = self.build_layout_context(
guards.clone(),
&map,
origin,
data.animation_timeline_value,
data.animations.clone(),
);
let pool;
let (thread_pool, num_threads) = if self.parallel_flag {
@ -1516,8 +1506,6 @@ impl LayoutThread {
) {
reflow_result.pending_images =
std::mem::replace(&mut *context.pending_images.lock().unwrap(), vec![]);
reflow_result.newly_animating_nodes =
std::mem::replace(&mut *context.newly_animating_nodes.lock().unwrap(), vec![]);
let mut root_flow = match self.root_flow.borrow().clone() {
Some(root_flow) => root_flow,
@ -1629,6 +1617,33 @@ impl LayoutThread {
rw_data.scroll_offsets = layout_scroll_states
}
/// Cancel animations for any nodes which have been removed from flow tree.
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
/// This also doesn't yet handles nodes that have been reparented.
fn cancel_animations_for_nodes_not_in_flow_tree(
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationSet>,
root_flow: &mut dyn Flow,
) {
// Assume all nodes have been removed until proven otherwise.
let mut invalid_nodes: FxHashSet<OpaqueNode> = animation_states.keys().cloned().collect();
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<OpaqueNode>) {
flow.mutate_fragments(&mut |fragment| {
invalid_nodes.remove(&fragment.node);
});
for kid in flow.mut_base().children.iter_mut() {
traverse_flow(kid, invalid_nodes)
}
}
traverse_flow(root_flow, &mut invalid_nodes);
// Cancel animations for any nodes that are no longer in the flow tree.
for node in &invalid_nodes {
if let Some(state) = animation_states.get_mut(node) {
state.cancel_all_animations();
}
}
}
fn perform_post_style_recalc_layout_passes(
&self,
root_flow: &mut FlowRef,
@ -1638,18 +1653,10 @@ impl LayoutThread {
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext,
) {
{
let mut newly_animating_nodes = context.newly_animating_nodes.lock().unwrap();
animation::do_post_style_animations_update(
&self.constellation_chan,
&self.script_chan,
&mut *(self.animation_states.write()),
self.id,
context.style_context.current_time_for_animations,
&mut newly_animating_nodes,
FlowRef::deref_mut(root_flow),
);
}
Self::cancel_animations_for_nodes_not_in_flow_tree(
&mut *(context.style_context.animation_states.write()),
FlowRef::deref_mut(root_flow),
);
profile(
profile_time::ProfilerCategory::LayoutRestyleDamagePropagation,

View file

@ -615,7 +615,6 @@ impl LayoutThread {
},
Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint,
Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart,
Msg::GetRunningAnimations(..) => LayoutHangAnnotation::GetRunningAnimations,
};
self.background_hang_monitor
.as_ref()
@ -766,9 +765,6 @@ impl LayoutThread {
Msg::SetNavigationStart(time) => {
self.paint_time_metrics.set_navigation_start(time);
},
Msg::GetRunningAnimations(sender) => {
let _ = sender.send(0);
},
}
true

View file

@ -541,7 +541,6 @@ pub enum LayoutHangAnnotation {
UpdateScrollStateFromScript,
RegisterPaint,
SetNavigationStart,
GetRunningAnimations,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]

View file

@ -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"

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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]

View file

@ -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 {

View file

@ -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

View file

@ -48,6 +48,7 @@ extern crate servo_atoms;
extern crate style;
mod animation_timeline;
mod animations;
#[warn(deprecated)]
#[macro_use]
mod task;

View file

@ -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)
},

View file

@ -16,6 +16,7 @@ atomic_refcell = "0.1"
canvas_traits = {path = "../canvas_traits"}
crossbeam-channel = "0.4"
euclid = "0.20"
fxhash = "0.2"
gfx_traits = {path = "../gfx_traits"}
html5ever = "0.25"
ipc-channel = "0.14"
@ -25,6 +26,7 @@ malloc_size_of_derive = "0.1"
metrics = {path = "../metrics"}
msg = {path = "../msg"}
net_traits = {path = "../net_traits"}
parking_lot = "0.9"
profile_traits = {path = "../profile_traits"}
range = {path = "../range"}
script_traits = {path = "../script_traits"}

View file

@ -7,20 +7,25 @@ use crate::{PendingImage, TrustedNodeAddress};
use app_units::Au;
use crossbeam_channel::{Receiver, Sender};
use euclid::default::{Point2D, Rect};
use fxhash::FxHashMap;
use gfx_traits::Epoch;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use metrics::PaintTimeMetrics;
use msg::constellation_msg::{BackgroundHangMonitorRegister, BrowsingContextId, PipelineId};
use net_traits::image_cache::ImageCache;
use parking_lot::RwLock;
use profile_traits::mem::ReportsChan;
use script_traits::Painter;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData};
use script_traits::{
ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, ScrollState,
WindowSizeData,
};
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_url::{ImmutableOrigin, ServoUrl};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use style::animation::ElementAnimationSet;
use style::context::QuirksMode;
use style::dom::OpaqueNode;
use style::invalidation::element::restyle_hints::RestyleHint;
@ -87,9 +92,6 @@ pub enum Msg {
/// Send to layout the precise time when the navigation started.
SetNavigationStart(u64),
/// Request the current number of animations that are running.
GetRunningAnimations(IpcSender<usize>),
}
#[derive(Debug, PartialEq)]
@ -183,8 +185,6 @@ pub struct Reflow {
pub struct ReflowComplete {
/// The list of images that were encountered that are in progress.
pub pending_images: Vec<PendingImage>,
/// The list of nodes that initiated a CSS transition.
pub newly_animating_nodes: Vec<UntrustedNodeAddress>,
}
/// Information needed for a script-initiated reflow.
@ -209,6 +209,8 @@ pub struct ScriptReflow {
pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
/// The current animation timeline value.
pub animation_timeline_value: f64,
/// The set of animations for this document.
pub animations: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
}
pub struct LayoutThreadInit {

View file

@ -282,58 +282,6 @@ pub enum UpdatePipelineIdReason {
Traversal,
}
/// The type of transition event to trigger. These are defined by
/// CSS Transitions § 6.1 and CSS Animations § 4.2
#[derive(Clone, Debug, Deserialize, 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, 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,
}
/// Messages sent from the constellation or layout to the script thread.
#[derive(Deserialize, Serialize)]
pub enum ConstellationControlMsg {
@ -420,8 +368,6 @@ pub enum ConstellationControlMsg {
WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
/// Notifies script thread that all animations are done
TickAllAnimations(PipelineId, AnimationTickType),
/// Notifies the script thread that a transition or animation related event should be sent.
TransitionOrAnimationEvent(TransitionOrAnimationEvent),
/// Notifies the script thread that a new Web font has been loaded, and thus the page should be
/// reflowed.
WebFontLoaded(PipelineId),
@ -481,7 +427,6 @@ impl fmt::Debug for ConstellationControlMsg {
FocusIFrame(..) => "FocusIFrame",
WebDriverScriptCommand(..) => "WebDriverScriptCommand",
TickAllAnimations(..) => "TickAllAnimations",
TransitionOrAnimationEvent { .. } => "TransitionOrAnimationEvent",
WebFontLoaded(..) => "WebFontLoaded",
DispatchIFrameLoadEvent { .. } => "DispatchIFrameLoadEvent",
DispatchStorageEvent(..) => "DispatchStorageEvent",

View file

@ -61,8 +61,6 @@ pub struct IFrameSizeMsg {
/// Messages from the layout to the constellation.
#[derive(Deserialize, Serialize)]
pub enum LayoutMsg {
/// Indicates whether this pipeline is currently running animations.
ChangeRunningAnimationsState(PipelineId, AnimationState),
/// Inform the constellation of the size of the iframe's viewport.
IFrameSizes(Vec<IFrameSizeMsg>),
/// Requests that the constellation inform the compositor that it needs to record
@ -76,7 +74,6 @@ impl fmt::Debug for LayoutMsg {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
use self::LayoutMsg::*;
let variant = match *self {
ChangeRunningAnimationsState(..) => "ChangeRunningAnimationsState",
IFrameSizes(..) => "IFrameSizes",
PendingPaintMetric(..) => "PendingPaintMetric",
ViewportConstrained(..) => "ViewportConstrained",

View file

@ -27,7 +27,7 @@ use servo_arc::Arc;
use std::fmt;
/// Represents an animation for a given property.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, MallocSizeOf)]
pub struct PropertyAnimation {
/// An `AnimatedProperty` that this `PropertyAnimation` corresponds to.
property: AnimatedProperty,
@ -136,7 +136,7 @@ impl PropertyAnimation {
}
/// This structure represents the state of an animation.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub enum AnimationState {
/// This animation is paused. The inner field is the percentage of progress
/// when it was paused, from 0 to 1.
@ -153,7 +153,7 @@ pub enum AnimationState {
///
/// If the iteration count is infinite, there's no other state, otherwise we
/// have to keep track the current iteration and the max iteration count.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, MallocSizeOf)]
pub enum KeyframesIterationState {
/// Infinite iterations, so no need to track a state.
Infinite,
@ -164,7 +164,7 @@ pub enum KeyframesIterationState {
}
/// A CSS Animation
#[derive(Clone)]
#[derive(Clone, MallocSizeOf)]
pub struct Animation {
/// The node associated with this animation.
pub node: OpaqueNode,
@ -198,6 +198,7 @@ pub struct Animation {
/// The original cascade style, needed to compute the generated keyframes of
/// the animation.
#[ignore_malloc_size_of = "ComputedValues"]
pub cascade_style: Arc<ComputedValues>,
/// Whether or not this animation is new and or has already been tracked
@ -542,7 +543,7 @@ impl fmt::Debug for Animation {
}
/// A CSS Transition
#[derive(Clone, Debug)]
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Transition {
/// The node associated with this animation.
pub node: OpaqueNode,
@ -597,7 +598,7 @@ impl Transition {
}
/// Holds the animation state for a particular element.
#[derive(Debug, Default)]
#[derive(Debug, Default, MallocSizeOf)]
pub struct ElementAnimationSet {
/// The animations for this element.
pub animations: Vec<Animation>,