Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for animationend event #26362

Merged
merged 1 commit into from May 1, 2020
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -13,20 +13,20 @@ use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, TransitionEventType,
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg,
TransitionOrAnimationEventType,
};
use servo_arc::Arc;
use style::animation::{
update_style_for_animation, Animation, ElementAnimationState, PropertyAnimation,
};
use style::animation::{update_style_for_animation, Animation, ElementAnimationState};
use style::dom::TElement;
use style::font_metrics::ServoMetricsProvider;
use style::selector_parser::RestyleDamage;
use style::timer::Timer;

/// Collect newly transitioning nodes, which is used by the script process during
/// forced, synchronous reflows to root DOM nodes for the duration of their transitions.
pub fn collect_newly_transitioning_nodes(
/// 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, ElementAnimationState>,
mut out: Option<&mut Vec<UntrustedNodeAddress>>,
) {
@@ -35,12 +35,7 @@ pub fn collect_newly_transitioning_nodes(
// currently stores a rooted node for every property that is transitioning.
if let Some(ref mut out) = out {
out.extend(animation_states.iter().flat_map(|(node, state)| {
let num_transitions = state
.new_animations
.iter()
.filter(|animation| animation.is_transition())
.count();
std::iter::repeat(node.to_untrusted_node_address()).take(num_transitions)
std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len())
}));
}
}
@@ -101,29 +96,36 @@ pub fn update_animation_state(
now: f64,
node: OpaqueNode,
) {
let send_transition_event = |property_animation: &PropertyAnimation, event_type| {
let send_event = |animation: &Animation, event_type, elapsed_time| {
let property_or_animation_name = match *animation {
Animation::Transition(_, _, ref property_animation) => {
property_animation.property_name().into()
},
Animation::Keyframes(_, _, ref name, _) => name.to_string(),
};

script_channel
.send(ConstellationControlMsg::TransitionEvent {
.send(ConstellationControlMsg::TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: node.to_untrusted_node_address(),
property_name: property_animation.property_name().into(),
elapsed_time: property_animation.duration,
property_or_animation_name,
elapsed_time,
})
.unwrap()
};

handle_cancelled_animations(animation_state, send_transition_event);
handle_running_animations(animation_state, now, send_transition_event);
handle_new_animations(animation_state, send_transition_event);
handle_cancelled_animations(animation_state, now, send_event);
handle_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.
pub fn handle_running_animations(
animation_state: &mut ElementAnimationState,
now: f64,
mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType),
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
) {
let mut running_animations =
std::mem::replace(&mut animation_state.running_animations, Vec::new());
@@ -144,9 +146,18 @@ pub fn handle_running_animations(
animation_state.running_animations.push(running_animation);
} else {
debug!("Finishing transition: {:?}", running_animation);
if let Animation::Transition(_, _, ref property_animation) = running_animation {
send_transition_event(property_animation, TransitionEventType::TransitionEnd);
}
let (event_type, elapsed_time) = match running_animation {
Animation::Transition(_, _, ref property_animation) => (
TransitionOrAnimationEventType::TransitionEnd,
property_animation.duration,
),
Animation::Keyframes(_, _, _, ref mut state) => (
TransitionOrAnimationEventType::AnimationEnd,
state.active_duration(),
),
};

send_event(&running_animation, event_type, elapsed_time);
animation_state.finished_animations.push(running_animation);
}
}
@@ -157,12 +168,19 @@ pub fn handle_running_animations(
/// well.
pub fn handle_cancelled_animations(
animation_state: &mut ElementAnimationState,
mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType),
now: f64,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
) {
for animation in animation_state.cancelled_animations.drain(..) {
match animation {
Animation::Transition(_, _, ref property_animation) => {
send_transition_event(property_animation, TransitionEventType::TransitionCancel)
Animation::Transition(_, start_time, _) => {
// TODO(mrobinson): We need to properly compute the elapsed_time here
// according to https://drafts.csswg.org/css-transitions/#event-transitionevent
send_event(
&animation,
TransitionOrAnimationEventType::TransitionCancel,
(now - start_time).max(0.),
);
},
// TODO(mrobinson): We should send animationcancel events.
Animation::Keyframes(..) => {},
@@ -172,12 +190,18 @@ pub fn handle_cancelled_animations(

pub fn handle_new_animations(
animation_state: &mut ElementAnimationState,
mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType),
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
) {
for animation in animation_state.new_animations.drain(..) {
match animation {
Animation::Transition(_, _, ref property_animation) => {
send_transition_event(property_animation, TransitionEventType::TransitionRun)
Animation::Transition(..) => {
// TODO(mrobinson): We need to properly compute the elapsed_time here
// according to https://drafts.csswg.org/css-transitions/#event-transitionevent
send_event(
&animation,
TransitionOrAnimationEventType::TransitionRun,
0.,
)
},
Animation::Keyframes(..) => {},
}
@@ -86,9 +86,9 @@ pub struct LayoutContext<'a> {
/// A None value means that this layout was not initiated by the script thread.
pub pending_images: Option<Mutex<Vec<PendingImage>>>,

/// A list of nodes that have just initiated a CSS transition.
/// A list of nodes that have just initiated a CSS transition or animation.
/// A None value means that this layout was not initiated by the script thread.
pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
pub newly_animating_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
}

impl<'a> Drop for LayoutContext<'a> {
@@ -648,7 +648,7 @@ impl LayoutThread {
} else {
None
},
newly_transitioning_nodes: if script_initiated_layout {
newly_animating_nodes: if script_initiated_layout {
Some(Mutex::new(vec![]))
} else {
None
@@ -1565,11 +1565,11 @@ impl LayoutThread {
};
reflow_result.pending_images = pending_images;

let newly_transitioning_nodes = match context.newly_transitioning_nodes {
let newly_animating_nodes = match context.newly_animating_nodes {
Some(ref nodes) => std::mem::replace(&mut *nodes.lock().unwrap(), vec![]),
None => vec![],
};
reflow_result.newly_transitioning_nodes = newly_transitioning_nodes;
reflow_result.newly_animating_nodes = newly_animating_nodes;

let mut root_flow = match self.root_flow.borrow().clone() {
Some(root_flow) => root_flow,
@@ -1741,7 +1741,7 @@ impl LayoutThread {
invalid_nodes,
);
assert!(layout_context.pending_images.is_none());
assert!(layout_context.newly_transitioning_nodes.is_none());
assert!(layout_context.newly_animating_nodes.is_none());
}
}

@@ -1756,19 +1756,13 @@ impl LayoutThread {
invalid_nodes: FxHashSet<OpaqueNode>,
) {
{
let mut newly_transitioning_nodes = context
.newly_transitioning_nodes
let mut newly_animating_nodes = context
.newly_animating_nodes
.as_ref()
.map(|nodes| nodes.lock().unwrap());
let newly_transitioning_nodes =
newly_transitioning_nodes.as_mut().map(|nodes| &mut **nodes);
let newly_animating_nodes = newly_animating_nodes.as_mut().map(|nodes| &mut **nodes);
let mut animation_states = self.animation_states.write();

animation::collect_newly_transitioning_nodes(
&animation_states,
newly_transitioning_nodes,
);

animation::collect_newly_animating_nodes(&animation_states, newly_animating_nodes);
animation::update_animation_states(
&self.constellation_chan,
&self.script_chan,
@@ -0,0 +1,76 @@
/* 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/. */

use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::{
AnimationEventInit, AnimationEventMethods,
};
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::window::Window;
use dom_struct::dom_struct;
use servo_atoms::Atom;

#[dom_struct]
pub struct AnimationEvent {
event: Event,
animation_name: Atom,
elapsed_time: Finite<f32>,
pseudo_element: DOMString,
}

impl AnimationEvent {
fn new_inherited(init: &AnimationEventInit) -> AnimationEvent {
AnimationEvent {
event: Event::new_inherited(),
animation_name: Atom::from(init.animationName.clone()),
elapsed_time: init.elapsedTime.clone(),
pseudo_element: init.pseudoElement.clone(),
}
}

pub fn new(window: &Window, type_: Atom, init: &AnimationEventInit) -> DomRoot<AnimationEvent> {
let ev = reflect_dom_object(Box::new(AnimationEvent::new_inherited(init)), window);
{
let event = ev.upcast::<Event>();
event.init_event(type_, init.parent.bubbles, init.parent.cancelable);
}
ev
}

#[allow(non_snake_case)]
pub fn Constructor(
window: &Window,
type_: DOMString,
init: &AnimationEventInit,
) -> DomRoot<AnimationEvent> {
AnimationEvent::new(window, Atom::from(type_), init)
}
}

impl AnimationEventMethods for AnimationEvent {
// https://drafts.csswg.org/css-animations/#interface-animationevent-attributes
fn AnimationName(&self) -> DOMString {
DOMString::from(&*self.animation_name)
}

// https://drafts.csswg.org/css-animations/#interface-animationevent-attributes
fn ElapsedTime(&self) -> Finite<f32> {
self.elapsed_time.clone()
}

// https://drafts.csswg.org/css-animations/#interface-animationevent-attributes
fn PseudoElement(&self) -> DOMString {
self.pseudo_element.clone()
}

// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.upcast::<Event>().IsTrusted()
}
}
@@ -442,6 +442,7 @@ macro_rules! global_event_handlers(
);
(NoOnload) => (
event_handler!(abort, GetOnabort, SetOnabort);
event_handler!(animationend, GetOnanimationend, SetOnanimationend);
event_handler!(cancel, GetOncancel, SetOncancel);
event_handler!(canplay, GetOncanplay, SetOncanplay);
event_handler!(canplaythrough, GetOncanplaythrough, SetOncanplaythrough);
@@ -213,6 +213,7 @@ pub mod abstractworker;
pub mod abstractworkerglobalscope;
pub mod activation;
pub mod analysernode;
pub mod animationevent;
pub mod attr;
pub mod audiobuffer;
pub mod audiobuffersourcenode;
@@ -0,0 +1,26 @@
/* 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 http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://www.w3.org/TR/css3-animations/#animation-events-
* http://dev.w3.org/csswg/css3-animations/#animation-events-
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/

[Exposed=Window]
interface AnimationEvent : Event {
constructor(DOMString type, optional AnimationEventInit eventInitDict = {});

readonly attribute DOMString animationName;
readonly attribute float elapsedTime;
readonly attribute DOMString pseudoElement;
};

dictionary AnimationEventInit : EventInit {
DOMString animationName = "";
float elapsedTime = 0;
DOMString pseudoElement = "";
};
@@ -90,6 +90,11 @@ interface mixin GlobalEventHandlers {
attribute EventHandler onwaiting;
};

// https://drafts.csswg.org/css-animations/#interface-globaleventhandlers-idl
partial interface mixin GlobalEventHandlers {
attribute EventHandler onanimationend;
This conversation was marked as resolved by mrobinson

This comment has been minimized.

@emilio

emilio Apr 30, 2020

Member

Rather odd indentation, but I see other places use it too so...

This comment has been minimized.

@mrobinson

mrobinson Apr 30, 2020

Author Member

Yeah, the indentation in some of the webidls files is a bit bizarre. I think it's due to copy-and-paste effects from specification documents. 😄 I've tried to match the indentation of the lines surrounding this one.

};

// https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl
partial interface mixin GlobalEventHandlers {
attribute EventHandler ontransitionrun;
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.