mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Auto merge of #26464 - mrobinson:move-animations-to-script, r=jdm
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. <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes do not require tests because they should not change behavior. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
commit
c5617efff0
21 changed files with 444 additions and 371 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -20,7 +20,6 @@ extern crate serde;
|
|||
#[macro_use]
|
||||
pub mod layout_debug;
|
||||
|
||||
pub mod animation;
|
||||
mod block;
|
||||
pub mod construct;
|
||||
pub mod context;
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -541,7 +541,6 @@ pub enum LayoutHangAnnotation {
|
|||
UpdateScrollStateFromScript,
|
||||
RegisterPaint,
|
||||
SetNavigationStart,
|
||||
GetRunningAnimations,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
293
components/script/animations.rs
Normal file
293
components/script/animations.rs
Normal 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,
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,6 +48,7 @@ extern crate servo_atoms;
|
|||
extern crate style;
|
||||
|
||||
mod animation_timeline;
|
||||
mod animations;
|
||||
#[warn(deprecated)]
|
||||
#[macro_use]
|
||||
mod task;
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue