mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Have Animations struct handle rooting nodes
Instead of having `ScriptThread` handle rooting nodes, do this in `Animations`. This makes it easier to know when it is appropriate to root and unroot nodes instead of relying on a certain order of events. This also allows reducing quite a bit the amount of unsafe code.
This commit is contained in:
parent
7170a69695
commit
47642e0eee
5 changed files with 278 additions and 339 deletions
|
@ -6,85 +6,130 @@
|
|||
|
||||
//! The set of animations for a document.
|
||||
|
||||
use crate::dom::animationevent::AnimationEvent;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::node::{from_untrusted_node_address, window_from_node, Node, NodeDamage};
|
||||
use crate::dom::transitionevent::TransitionEvent;
|
||||
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 std::cell::Cell;
|
||||
use style::animation::{
|
||||
Animation, AnimationState, ElementAnimationSet, KeyframesIterationState, Transition,
|
||||
};
|
||||
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)]
|
||||
#[derive(Default, JSTraceable, MallocSizeOf)]
|
||||
#[unrooted_must_root_lint::must_root]
|
||||
pub(crate) struct Animations {
|
||||
/// The map of nodes to their animation states.
|
||||
#[ignore_malloc_size_of = "Arc is hard"]
|
||||
pub sets: Arc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
|
||||
have_running_animations: bool,
|
||||
|
||||
/// Whether or not we have animations that are running.
|
||||
have_running_animations: Cell<bool>,
|
||||
|
||||
/// A list of nodes with in-progress CSS transitions or pending events.
|
||||
rooted_nodes: DomRefCell<FxHashMap<OpaqueNode, Dom<Node>>>,
|
||||
|
||||
/// A list of pending animation-related events.
|
||||
pending_events: DomRefCell<Vec<TransitionOrAnimationEvent>>,
|
||||
}
|
||||
|
||||
impl Animations {
|
||||
pub(crate) fn new() -> Self {
|
||||
Animations {
|
||||
sets: Default::default(),
|
||||
have_running_animations: false,
|
||||
have_running_animations: Cell::new(false),
|
||||
rooted_nodes: Default::default(),
|
||||
pending_events: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_for_new_timeline_value(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
now: f64,
|
||||
) -> AnimationsUpdate {
|
||||
let mut update = AnimationsUpdate::new(window.pipeline_id());
|
||||
pub(crate) fn clear(&self) {
|
||||
self.sets.write().clear();
|
||||
self.rooted_nodes.borrow_mut().clear();
|
||||
self.pending_events.borrow_mut().clear();
|
||||
}
|
||||
|
||||
pub(crate) fn mark_animating_nodes_as_dirty(&self) {
|
||||
let sets = self.sets.read();
|
||||
let rooted_nodes = self.rooted_nodes.borrow();
|
||||
for node in sets.keys().filter_map(|node| rooted_nodes.get(&node)) {
|
||||
node.dirty(NodeDamage::NodeStyleDamaged);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_for_new_timeline_value(&self, window: &Window, now: f64) {
|
||||
let pipeline_id = window.pipeline_id();
|
||||
let mut sets = self.sets.write();
|
||||
|
||||
for set in sets.values_mut() {
|
||||
// When necessary, iterate our running animations to the next iteration.
|
||||
for animation in set.animations.iter_mut() {
|
||||
if animation.iterate_if_necessary(now) {
|
||||
update.add_event(
|
||||
animation.node,
|
||||
animation.name.to_string(),
|
||||
self.add_animation_event(
|
||||
animation,
|
||||
TransitionOrAnimationEventType::AnimationIteration,
|
||||
animation.active_duration(),
|
||||
pipeline_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Self::finish_running_animations(set, now, &mut update);
|
||||
self.finish_running_animations(set, now, pipeline_id);
|
||||
}
|
||||
update
|
||||
|
||||
self.unroot_unused_nodes(&sets);
|
||||
}
|
||||
|
||||
/// 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());
|
||||
pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) {
|
||||
let pipeline_id = window.pipeline_id();
|
||||
let mut sets = self.sets.write();
|
||||
self.root_newly_animating_dom_nodes(&sets, window);
|
||||
|
||||
{
|
||||
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::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());
|
||||
for set in sets.values_mut() {
|
||||
self.handle_canceled_animations(set, now, pipeline_id);
|
||||
self.handle_new_animations(set, now, pipeline_id);
|
||||
}
|
||||
|
||||
self.update_running_animations_presence(window);
|
||||
// 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());
|
||||
let have_running_animations = sets.values().any(|state| state.needs_animation_ticks());
|
||||
|
||||
update
|
||||
self.update_running_animations_presence(window, have_running_animations);
|
||||
}
|
||||
|
||||
fn update_running_animations_presence(&self, window: &Window, new_value: bool) {
|
||||
let have_running_animations = self.have_running_animations.get();
|
||||
if new_value == have_running_animations {
|
||||
return;
|
||||
}
|
||||
|
||||
self.have_running_animations.set(new_value);
|
||||
let state = match new_value {
|
||||
true => AnimationsPresentState::AnimationsPresent,
|
||||
false => AnimationsPresentState::NoAnimationsPresent,
|
||||
};
|
||||
|
||||
window.send_to_constellation(ScriptMsg::ChangeRunningAnimationsState(state));
|
||||
}
|
||||
|
||||
pub(crate) fn running_animation_count(&self) -> usize {
|
||||
|
@ -95,40 +140,21 @@ impl Animations {
|
|||
.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(
|
||||
&self,
|
||||
set: &mut ElementAnimationSet,
|
||||
now: f64,
|
||||
update: &mut AnimationsUpdate,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
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(),
|
||||
self.add_animation_event(
|
||||
animation,
|
||||
TransitionOrAnimationEventType::AnimationEnd,
|
||||
animation.active_duration(),
|
||||
pipeline_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -136,11 +162,11 @@ impl Animations {
|
|||
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_id().name().into(),
|
||||
self.add_transition_event(
|
||||
transition,
|
||||
TransitionOrAnimationEventType::TransitionEnd,
|
||||
transition.property_animation.duration,
|
||||
now,
|
||||
pipeline_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -150,19 +176,18 @@ impl Animations {
|
|||
/// transitions, but eventually this should handle canceled CSS animations as
|
||||
/// well.
|
||||
fn handle_canceled_animations(
|
||||
&self,
|
||||
set: &mut ElementAnimationSet,
|
||||
now: f64,
|
||||
update: &mut AnimationsUpdate,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
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_id().name().into(),
|
||||
self.add_transition_event(
|
||||
transition,
|
||||
TransitionOrAnimationEventType::TransitionCancel,
|
||||
(now - transition.start_time).max(0.),
|
||||
now,
|
||||
pipeline_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -171,103 +196,187 @@ impl Animations {
|
|||
set.clear_canceled_animations();
|
||||
}
|
||||
|
||||
fn handle_new_animations(set: &mut ElementAnimationSet, update: &mut AnimationsUpdate) {
|
||||
fn handle_new_animations(
|
||||
&self,
|
||||
set: &mut ElementAnimationSet,
|
||||
now: f64,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
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_id().name().into(),
|
||||
self.add_transition_event(
|
||||
transition,
|
||||
TransitionOrAnimationEventType::TransitionRun,
|
||||
0.,
|
||||
now,
|
||||
pipeline_id,
|
||||
);
|
||||
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)
|
||||
}
|
||||
}
|
||||
/// Ensure that all nodes with new animations are rooted. This should be called
|
||||
/// immediately after a restyle, to ensure that these addresses are still valid.
|
||||
#[allow(unsafe_code)]
|
||||
fn root_newly_animating_dom_nodes(
|
||||
&self,
|
||||
sets: &FxHashMap<OpaqueNode, ElementAnimationSet>,
|
||||
window: &Window,
|
||||
) {
|
||||
let js_runtime = window.get_js_runtime().as_ref().unwrap().rt();
|
||||
let mut rooted_nodes = self.rooted_nodes.borrow_mut();
|
||||
for (opaque_node, set) in sets.iter() {
|
||||
if rooted_nodes.contains_key(opaque_node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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(),
|
||||
if set.animations.iter().any(|animation| animation.is_new) ||
|
||||
set.transitions.iter().any(|transition| transition.is_new)
|
||||
{
|
||||
let address = UntrustedNodeAddress(opaque_node.0 as *const c_void);
|
||||
unsafe {
|
||||
rooted_nodes.insert(
|
||||
opaque_node.clone(),
|
||||
Dom::from_ref(&*from_untrusted_node_address(js_runtime, address)),
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// Unroot any nodes that we have rooted but are no longer tracking animations for.
|
||||
fn unroot_unused_nodes(&self, sets: &FxHashMap<OpaqueNode, ElementAnimationSet>) {
|
||||
let pending_events = self.pending_events.borrow();
|
||||
self.rooted_nodes.borrow_mut().retain(|key, _| {
|
||||
sets.contains_key(key) || pending_events.iter().any(|event| event.node == *key)
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.events.is_empty() && self.newly_animating_nodes.is_empty()
|
||||
fn add_transition_event(
|
||||
&self,
|
||||
transition: &Transition,
|
||||
event_type: TransitionOrAnimationEventType,
|
||||
now: f64,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
let elapsed_time = match event_type {
|
||||
TransitionOrAnimationEventType::TransitionRun |
|
||||
TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration,
|
||||
TransitionOrAnimationEventType::TransitionCancel => {
|
||||
(now - transition.start_time).max(0.)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.pending_events
|
||||
.borrow_mut()
|
||||
.push(TransitionOrAnimationEvent {
|
||||
pipeline_id,
|
||||
event_type,
|
||||
node: transition.node.clone(),
|
||||
property_or_animation_name: transition
|
||||
.property_animation
|
||||
.property_id()
|
||||
.name()
|
||||
.into(),
|
||||
elapsed_time,
|
||||
});
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
fn add_animation_event(
|
||||
&self,
|
||||
animation: &Animation,
|
||||
event_type: TransitionOrAnimationEventType,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
// 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 num_iterations = match animation.iteration_state {
|
||||
KeyframesIterationState::Finite(current, _) |
|
||||
KeyframesIterationState::Infinite(current) => current,
|
||||
};
|
||||
|
||||
let node = UntrustedNodeAddress(node.0 as *const c_void);
|
||||
std::iter::repeat(node).take(num_new_animations)
|
||||
}));
|
||||
let elapsed_time = match event_type {
|
||||
TransitionOrAnimationEventType::AnimationIteration |
|
||||
TransitionOrAnimationEventType::AnimationEnd => num_iterations * animation.duration,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.pending_events
|
||||
.borrow_mut()
|
||||
.push(TransitionOrAnimationEvent {
|
||||
pipeline_id,
|
||||
event_type,
|
||||
node: animation.node.clone(),
|
||||
property_or_animation_name: animation.name.to_string(),
|
||||
elapsed_time,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn send_pending_events(&self) {
|
||||
// Take all of the events here, in case sending one of these events
|
||||
// triggers adding new events by forcing a layout.
|
||||
let events = std::mem::replace(&mut *self.pending_events.borrow_mut(), Vec::new());
|
||||
|
||||
for event in events.into_iter() {
|
||||
// We root the node here to ensure that sending this event doesn't
|
||||
// unroot it as a side-effect.
|
||||
let node = match self.rooted_nodes.borrow().get(&event.node) {
|
||||
Some(node) => DomRoot::from_ref(&**node),
|
||||
None => {
|
||||
warn!("Tried to send an event for an unrooted node");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let event_atom = match event.event_type {
|
||||
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
|
||||
TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
|
||||
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
|
||||
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
|
||||
TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
|
||||
};
|
||||
let parent = EventInit {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
};
|
||||
|
||||
// TODO: Handle pseudo-elements properly
|
||||
let property_or_animation_name =
|
||||
DOMString::from(event.property_or_animation_name.clone());
|
||||
let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
|
||||
let window = window_from_node(&*node);
|
||||
|
||||
if event.event_type.is_transition_event() {
|
||||
let event_init = TransitionEventInit {
|
||||
parent,
|
||||
propertyName: property_or_animation_name,
|
||||
elapsedTime: elapsed_time,
|
||||
pseudoElement: DOMString::new(),
|
||||
};
|
||||
TransitionEvent::new(&window, event_atom, &event_init)
|
||||
.upcast::<Event>()
|
||||
.fire(node.upcast());
|
||||
} else {
|
||||
let event_init = AnimationEventInit {
|
||||
parent,
|
||||
animationName: property_or_animation_name,
|
||||
elapsedTime: elapsed_time,
|
||||
pseudoElement: DOMString::new(),
|
||||
};
|
||||
AnimationEvent::new(&window, event_atom, &event_init)
|
||||
.upcast::<Event>()
|
||||
.fire(node.upcast());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
#[derive(Clone, Debug, Deserialize, JSTraceable, MallocSizeOf, 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)."
|
||||
|
@ -278,23 +387,14 @@ pub enum TransitionOrAnimationEventType {
|
|||
TransitionEnd,
|
||||
/// "The transitioncancel event occurs when a transition is canceled."
|
||||
TransitionCancel,
|
||||
/// "The animationend event occurs when the animation finishes"
|
||||
AnimationEnd,
|
||||
/// "The animationiteration event occurs at the end of each iteration of an
|
||||
/// animation, except when an animationend event would fire at the same time."
|
||||
AnimationIteration,
|
||||
/// "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 | Self::AnimationIteration => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether or not this event is a transition-related event.
|
||||
pub fn is_transition_event(&self) -> bool {
|
||||
match *self {
|
||||
|
@ -304,7 +404,7 @@ impl TransitionOrAnimationEventType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JSTraceable, Serialize)]
|
||||
#[derive(Deserialize, JSTraceable, MallocSizeOf, Serialize)]
|
||||
/// A transition or animation event.
|
||||
pub struct TransitionOrAnimationEvent {
|
||||
/// The pipeline id of the layout task that sent this message.
|
||||
|
@ -312,7 +412,7 @@ pub struct TransitionOrAnimationEvent {
|
|||
/// The type of transition event this should trigger.
|
||||
pub event_type: TransitionOrAnimationEventType,
|
||||
/// The address of the node which owns this transition.
|
||||
pub node: UntrustedNodeAddress,
|
||||
pub node: OpaqueNode,
|
||||
/// 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,
|
||||
|
|
|
@ -3,7 +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::animations::Animations;
|
||||
use crate::document_loader::{DocumentLoader, LoadType};
|
||||
use crate::dom::attr::Attr;
|
||||
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
|
||||
|
@ -3750,15 +3750,15 @@ impl Document {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) -> AnimationsUpdate {
|
||||
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) {
|
||||
self.animation_timeline.borrow_mut().advance_specific(delta);
|
||||
let current_timeline_value = self.current_animation_timeline_value();
|
||||
self.animations
|
||||
.borrow_mut()
|
||||
.update_for_new_timeline_value(&self.window, current_timeline_value)
|
||||
.borrow()
|
||||
.update_for_new_timeline_value(&self.window, current_timeline_value);
|
||||
}
|
||||
|
||||
pub(crate) fn update_animation_timeline(&self) -> AnimationsUpdate {
|
||||
pub(crate) fn update_animation_timeline(&self) {
|
||||
// Only update the time if it isn't being managed by a test.
|
||||
if !pref!(layout.animations.test.enabled) {
|
||||
self.animation_timeline.borrow_mut().update();
|
||||
|
@ -3768,8 +3768,8 @@ impl Document {
|
|||
// value might have been advanced previously via the TestBinding.
|
||||
let current_timeline_value = self.current_animation_timeline_value();
|
||||
self.animations
|
||||
.borrow_mut()
|
||||
.update_for_new_timeline_value(&self.window, current_timeline_value)
|
||||
.borrow()
|
||||
.update_for_new_timeline_value(&self.window, current_timeline_value);
|
||||
}
|
||||
|
||||
pub(crate) fn current_animation_timeline_value(&self) -> f64 {
|
||||
|
@ -3780,10 +3780,10 @@ impl Document {
|
|||
self.animations.borrow()
|
||||
}
|
||||
|
||||
pub(crate) fn update_animations_post_reflow(&self) -> AnimationsUpdate {
|
||||
pub(crate) fn update_animations_post_reflow(&self) {
|
||||
self.animations
|
||||
.borrow_mut()
|
||||
.do_post_reflow_update(&self.window, self.current_animation_timeline_value())
|
||||
.borrow()
|
||||
.do_post_reflow_update(&self.window, self.current_animation_timeline_value());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
|
||||
DocumentMethods, DocumentReadyState,
|
||||
};
|
||||
|
@ -410,6 +410,10 @@ impl Window {
|
|||
unsafe { JSContext::from_ptr(self.js_runtime.borrow().as_ref().unwrap().cx()) }
|
||||
}
|
||||
|
||||
pub fn get_js_runtime(&self) -> Ref<Option<Rc<Runtime>>> {
|
||||
self.js_runtime.borrow()
|
||||
}
|
||||
|
||||
pub fn main_thread_script_chan(&self) -> &Sender<MainThreadScriptMsg> {
|
||||
&self.script_chan.0
|
||||
}
|
||||
|
@ -1581,12 +1585,8 @@ impl Window {
|
|||
#[allow(unsafe_code)]
|
||||
pub fn advance_animation_clock(&self, delta_ms: i32) {
|
||||
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
|
||||
let update = self
|
||||
.Document()
|
||||
self.Document()
|
||||
.advance_animation_timeline_for_testing(delta_ms as f64 / 1000.);
|
||||
unsafe {
|
||||
ScriptThread::process_animations_update(update);
|
||||
}
|
||||
ScriptThread::handle_tick_all_animations_for_testing(pipeline_id);
|
||||
}
|
||||
|
||||
|
@ -1752,10 +1752,7 @@ impl Window {
|
|||
}
|
||||
}
|
||||
|
||||
let update = document.update_animations_post_reflow();
|
||||
unsafe {
|
||||
ScriptThread::process_animations_update(update);
|
||||
}
|
||||
document.update_animations_post_reflow();
|
||||
|
||||
true
|
||||
}
|
||||
|
|
|
@ -17,26 +17,18 @@
|
|||
//! 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;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
|
||||
DocumentMethods, DocumentReadyState,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
use crate::dom::bindings::conversions::{
|
||||
ConversionResult, FromJSValConvertible, StringificationBehavior,
|
||||
};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::DomObject;
|
||||
use crate::dom::bindings::root::ThreadLocalStackRoots;
|
||||
|
@ -57,14 +49,11 @@ use crate::dom::htmlanchorelement::HTMLAnchorElement;
|
|||
use crate::dom::htmliframeelement::{HTMLIFrameElement, NavigationType};
|
||||
use crate::dom::identityhub::Identities;
|
||||
use crate::dom::mutationobserver::MutationObserver;
|
||||
use crate::dom::node::{
|
||||
from_untrusted_node_address, window_from_node, Node, NodeDamage, ShadowIncluding,
|
||||
};
|
||||
use crate::dom::node::{window_from_node, Node, ShadowIncluding};
|
||||
use crate::dom::performanceentry::PerformanceEntry;
|
||||
use crate::dom::performancepainttiming::PerformancePaintTiming;
|
||||
use crate::dom::serviceworker::TrustedServiceWorkerAddress;
|
||||
use crate::dom::servoparser::{ParserContext, ServoParser};
|
||||
use crate::dom::transitionevent::TransitionEvent;
|
||||
use crate::dom::uievent::UIEvent;
|
||||
use crate::dom::window::{ReflowReason, Window};
|
||||
use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy};
|
||||
|
@ -636,13 +625,6 @@ pub struct ScriptThread {
|
|||
/// resources during a turn of the event loop.
|
||||
docs_with_no_blocking_loads: DomRefCell<HashSet<Dom<Document>>>,
|
||||
|
||||
/// A list of nodes with in-progress CSS transitions, which roots them for the duration
|
||||
/// 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,
|
||||
|
||||
|
@ -831,40 +813,6 @@ impl ScriptThread {
|
|||
})
|
||||
}
|
||||
|
||||
/// 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 = 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(update.pipeline_id)
|
||||
.or_insert_with(Vec::new)
|
||||
.extend(new_nodes);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_mutation_observer_microtask_queued(value: bool) {
|
||||
SCRIPT_THREAD_ROOT.with(|root| {
|
||||
let script_thread = unsafe { &*root.get().unwrap() };
|
||||
|
@ -1363,9 +1311,6 @@ impl ScriptThread {
|
|||
|
||||
docs_with_no_blocking_loads: Default::default(),
|
||||
|
||||
animating_nodes: Default::default(),
|
||||
animation_events: Default::default(),
|
||||
|
||||
custom_element_reaction_stack: CustomElementReactionStack::new(),
|
||||
|
||||
webrender_document: state.webrender_document,
|
||||
|
@ -1644,19 +1589,13 @@ impl ScriptThread {
|
|||
}
|
||||
|
||||
// Perform step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops.
|
||||
// TODO(mrobinson): This should also update the current animations to conform to
|
||||
// the HTML specification.
|
||||
fn update_animations_and_send_events(&self) {
|
||||
// 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);
|
||||
for (_, document) in self.documents.borrow().iter() {
|
||||
document.animations().send_pending_events();
|
||||
}
|
||||
|
||||
for (_, document) in self.documents.borrow().iter() {
|
||||
let update = document.update_animation_timeline();
|
||||
unsafe { ScriptThread::process_animations_update(update) };
|
||||
document.update_animation_timeline();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2859,11 +2798,11 @@ impl ScriptThread {
|
|||
.send((id, ScriptMsg::PipelineExited))
|
||||
.ok();
|
||||
|
||||
// Remove any rooted nodes for active animations and transitions.
|
||||
self.animating_nodes.borrow_mut().remove(&id);
|
||||
|
||||
// Now that layout is shut down, it's OK to remove the document.
|
||||
if let Some(document) = document {
|
||||
// Clear any active animations and unroot all of the associated DOM objects.
|
||||
document.animations().clear();
|
||||
|
||||
// We don't want to dispatch `mouseout` event pointing to non-existing element
|
||||
if let Some(target) = self.topmost_mouse_over_target.get() {
|
||||
if target.upcast::<Node>().owner_doc() == document {
|
||||
|
@ -2939,99 +2878,11 @@ impl ScriptThread {
|
|||
document.run_the_animation_frame_callbacks();
|
||||
}
|
||||
if tick_type.contains(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS) {
|
||||
match self.animating_nodes.borrow().get(&id) {
|
||||
Some(nodes) => {
|
||||
for node in nodes.iter() {
|
||||
node.dirty(NodeDamage::NodeStyleDamaged);
|
||||
}
|
||||
},
|
||||
None => return,
|
||||
}
|
||||
|
||||
document.animations().mark_animating_nodes_as_dirty();
|
||||
document.window().add_pending_reflow();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles firing of transition-related events.
|
||||
///
|
||||
/// TODO(mrobinson): Add support for more events.
|
||||
fn handle_transition_or_animation_event(&self, event: &TransitionOrAnimationEvent) {
|
||||
// We limit the scope of the borrow here so that we aren't holding it when
|
||||
// sending events. Event handlers may trigger another layout, resulting in
|
||||
// a double mutable borrow of `animating_nodes`.
|
||||
let node = {
|
||||
let mut animating_nodes = self.animating_nodes.borrow_mut();
|
||||
let nodes = match animating_nodes.get_mut(&event.pipeline_id) {
|
||||
Some(nodes) => nodes,
|
||||
None => {
|
||||
return warn!(
|
||||
"Ignoring transition event for pipeline without animating nodes."
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
let node_index = nodes
|
||||
.iter()
|
||||
.position(|n| n.to_untrusted_node_address() == event.node);
|
||||
let node_index = match node_index {
|
||||
Some(node_index) => node_index,
|
||||
None => {
|
||||
// If no index is found, we can't know whether this node is safe to use.
|
||||
// It's better not to fire a DOM event than crash.
|
||||
warn!("Ignoring transition event for unknown node.");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// We need to root the node now, because if we remove it from the map
|
||||
// a garbage collection might clean it up while we are sending events.
|
||||
let node = DomRoot::from_ref(&*nodes[node_index]);
|
||||
if event.event_type.finalizes_transition_or_animation() {
|
||||
nodes.remove(node_index);
|
||||
}
|
||||
node
|
||||
};
|
||||
|
||||
let event_atom = match event.event_type {
|
||||
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
|
||||
TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
|
||||
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
|
||||
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
|
||||
TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
|
||||
};
|
||||
let parent = EventInit {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
};
|
||||
|
||||
// TODO: Handle pseudo-elements properly
|
||||
let property_or_animation_name = DOMString::from(event.property_or_animation_name.clone());
|
||||
let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
|
||||
let window = window_from_node(&*node);
|
||||
|
||||
if event.event_type.is_transition_event() {
|
||||
let event_init = TransitionEventInit {
|
||||
parent,
|
||||
propertyName: property_or_animation_name,
|
||||
elapsedTime: elapsed_time,
|
||||
pseudoElement: DOMString::new(),
|
||||
};
|
||||
TransitionEvent::new(&window, event_atom, &event_init)
|
||||
.upcast::<Event>()
|
||||
.fire(node.upcast());
|
||||
} else {
|
||||
let event_init = AnimationEventInit {
|
||||
parent,
|
||||
animationName: property_or_animation_name,
|
||||
elapsedTime: elapsed_time,
|
||||
pseudoElement: DOMString::new(),
|
||||
};
|
||||
AnimationEvent::new(&window, event_atom, &event_init)
|
||||
.upcast::<Event>()
|
||||
.fire(node.upcast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a Web font being loaded. Does nothing if the page no longer exists.
|
||||
fn handle_web_font_loaded(&self, pipeline_id: PipelineId) {
|
||||
let document = self.documents.borrow().find_document(pipeline_id);
|
||||
|
|
|
@ -346,15 +346,6 @@ impl Animation {
|
|||
self.started_at = new_started_at;
|
||||
}
|
||||
|
||||
/// Calculate the active-duration of this animation according to
|
||||
/// https://drafts.csswg.org/css-animations/#active-duration.
|
||||
pub fn active_duration(&self) -> f64 {
|
||||
match self.iteration_state {
|
||||
KeyframesIterationState::Finite(current, _) |
|
||||
KeyframesIterationState::Infinite(current) => self.duration * current,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the given style to reflect the values specified by this `Animation`
|
||||
/// at the time provided by the given `SharedStyleContext`.
|
||||
fn update_style<E>(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue