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 transitionrun events #26308

Merged
merged 1 commit into from Apr 25, 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

@@ -131,6 +131,7 @@ toggle
track
transitioncancel
transitionend
transitionrun
unhandledrejection
unload
url
@@ -142,5 +143,6 @@ webkitAnimationEnd
webkitAnimationIteration
webkitAnimationStart
webkitTransitionEnd
webkitTransitionRun
week
width
@@ -15,54 +15,62 @@ use script_traits::UntrustedNodeAddress;
use script_traits::{
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, TransitionEventType,
};
use style::animation::{update_style_for_animation, Animation, ElementAnimationState};
use style::animation::{
update_style_for_animation, Animation, ElementAnimationState, PropertyAnimation,
};
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(
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
mut out: Option<&mut Vec<UntrustedNodeAddress>>,
) {
// This extends the output vector with an iterator that contains a copy of the node
// address for every new animation. This is a bit goofy, but the script thread
// 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)
}));
}
}

/// Processes any new animations that were discovered after style recalculation. Also
/// finish any animations that have completed, inserting them into `finished_animations`.
pub fn update_animation_states<E>(
pub fn update_animation_states(
constellation_chan: &IpcSender<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>,
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>,
invalid_nodes: FxHashSet<OpaqueNode>,
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
pipeline_id: PipelineId,
timer: &Timer,
) where
E: TElement,
{
) {
let had_running_animations = animation_states
.values()
.any(|state| !state.running_animations.is_empty());

// Cancel all animations on any invalid nodes. These entries will later
// be removed from the list of states, because their states will become
// empty.
for node in &invalid_nodes {
if let Some(mut state) = animation_states.remove(node) {
state.cancel_all_animations();
send_events_for_cancelled_animations(script_chan, &mut state, pipeline_id);
}
}

let now = timer.seconds();
let mut have_running_animations = false;
for (node, animation_state) in animation_states.iter_mut() {
// TODO(mrobinson): This should really be triggering transitionrun messages
// on the script thread.
if let Some(ref mut newly_transitioning_nodes) = newly_transitioning_nodes {
let number_of_new_transitions = animation_state
.new_animations
.iter()
.filter(|animation| animation.is_transition())
.count();
for _ in 0..number_of_new_transitions {
newly_transitioning_nodes.push(node.to_untrusted_node_address());
}
}

update_animation_state::<E>(script_chan, animation_state, pipeline_id, now);

update_animation_state(script_chan, animation_state, pipeline_id, now, *node);
have_running_animations =
have_running_animations || !animation_state.running_animations.is_empty();
}
@@ -85,16 +93,37 @@ pub fn update_animation_states<E>(
.unwrap();
}

pub fn update_animation_state<E>(
script_chan: &IpcSender<ConstellationControlMsg>,
pub fn update_animation_state(
script_channel: &IpcSender<ConstellationControlMsg>,
animation_state: &mut ElementAnimationState,
pipeline_id: PipelineId,
now: f64,
) where
E: TElement,
{
send_events_for_cancelled_animations(script_chan, animation_state, pipeline_id);
node: OpaqueNode,
) {
let send_transition_event = |property_animation: &PropertyAnimation, event_type| {
script_channel
.send(ConstellationControlMsg::TransitionEvent {
pipeline_id,
event_type,
node: node.to_untrusted_node_address(),
property_name: property_animation.property_name().into(),
elapsed_time: property_animation.duration,
})
.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);
}

/// 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),
) {
let mut running_animations =
std::mem::replace(&mut animation_state.running_animations, Vec::new());
for mut running_animation in running_animations.drain(..) {
@@ -115,46 +144,25 @@ pub fn update_animation_state<E>(
animation_state.running_animations.push(running_animation);
} else {
debug!("Finishing transition: {:?}", running_animation);
if let Animation::Transition(node, _, ref property_animation) = running_animation {
script_chan
.send(ConstellationControlMsg::TransitionEvent {
pipeline_id,
event_type: TransitionEventType::TransitionEnd,
node: node.to_untrusted_node_address(),
property_name: property_animation.property_name().into(),
elapsed_time: property_animation.duration,
})
.unwrap();
if let Animation::Transition(_, _, ref property_animation) = running_animation {
send_transition_event(property_animation, TransitionEventType::TransitionEnd);
}
animation_state.finished_animations.push(running_animation);
}
}

animation_state
.running_animations
.append(&mut animation_state.new_animations);
}

/// Send events for cancelled animations. Currently this only handles cancelled
/// transitions, but eventually this should handle cancelled CSS animations as
/// well.
pub fn send_events_for_cancelled_animations(
script_channel: &IpcSender<ConstellationControlMsg>,
pub fn handle_cancelled_animations(
animation_state: &mut ElementAnimationState,
pipeline_id: PipelineId,
mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType),
) {
for animation in animation_state.cancelled_animations.drain(..) {
match animation {
Animation::Transition(node, _, ref property_animation) => {
script_channel
.send(ConstellationControlMsg::TransitionEvent {
pipeline_id,
event_type: TransitionEventType::TransitionCancel,
node: node.to_untrusted_node_address(),
property_name: property_animation.property_name().into(),
elapsed_time: property_animation.duration,
})
.unwrap();
Animation::Transition(_, _, ref property_animation) => {
send_transition_event(property_animation, TransitionEventType::TransitionCancel)
},
Animation::Keyframes(..) => {
warn!("Got unexpected animation in finished transitions list.")
@@ -163,6 +171,21 @@ pub fn send_events_for_cancelled_animations(
}
}

pub fn handle_new_animations(
animation_state: &mut ElementAnimationState,
mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType),
) {
for animation in animation_state.new_animations.drain(..) {
match animation {
Animation::Transition(_, _, ref property_animation) => {
send_transition_event(property_animation, TransitionEventType::TransitionRun)
},
Animation::Keyframes(..) => {},
}
animation_state.running_animations.push(animation);
}
}

/// Recalculates style for a set of animations. This does *not* run with the DOM
/// lock held. Returns a set of nodes associated with animations that are no longer
/// valid.
@@ -1762,13 +1762,18 @@ impl LayoutThread {
.map(|nodes| nodes.lock().unwrap());
let newly_transitioning_nodes =
newly_transitioning_nodes.as_mut().map(|nodes| &mut **nodes);
// Kick off animations if any were triggered, expire completed ones.
animation::update_animation_states::<ServoLayoutElement>(
let mut animation_states = self.animation_states.write();

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

animation::update_animation_states(
&self.constellation_chan,
&self.script_chan,
&mut *self.animation_states.write(),
&mut *animation_states,
invalid_nodes,
newly_transitioning_nodes,
self.id,
&self.timer,
);
@@ -647,6 +647,7 @@ fn invoke(
atom!("animationiteration") => Some(atom!("webkitAnimationIteration")),
atom!("animationstart") => Some(atom!("webkitAnimationStart")),
atom!("transitionend") => Some(atom!("webkitTransitionEnd")),
atom!("transitionrun") => Some(atom!("webkitTransitionRun")),
_ => None,
} {
let original_type = event.type_();
@@ -498,6 +498,7 @@ macro_rules! global_event_handlers(
event_handler!(toggle, GetOntoggle, SetOntoggle);
event_handler!(transitioncancel, GetOntransitioncancel, SetOntransitioncancel);
event_handler!(transitionend, GetOntransitionend, SetOntransitionend);
event_handler!(transitionrun, GetOntransitionrun, SetOntransitionrun);
event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange);
event_handler!(waiting, GetOnwaiting, SetOnwaiting);
)
@@ -92,6 +92,7 @@ interface mixin GlobalEventHandlers {

// https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl
partial interface mixin GlobalEventHandlers {
attribute EventHandler ontransitionrun;
attribute EventHandler ontransitionend;
attribute EventHandler ontransitioncancel;
};
@@ -2920,35 +2920,38 @@ impl ScriptThread {
let js_runtime = self.js_runtime.rt();
let node = unsafe { from_untrusted_node_address(js_runtime, unsafe_node) };

let idx = self
let node_index = self
.transitioning_nodes
.borrow()
.iter()
.position(|n| &**n as *const _ == &*node as *const _);
match idx {
Some(idx) => {
self.transitioning_nodes.borrow_mut().remove(idx);
},
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 end notification for unknown node.");
return;
},
}
};

if self.closed_pipelines.borrow().contains(&pipeline_id) {
warn!("Ignoring transition event for closed pipeline.");
return;
}

let event_atom = match event_type {
TransitionEventType::TransitionRun => atom!("transitionrun"),
TransitionEventType::TransitionEnd => {
// Not quite the right thing - see #13865.
node.dirty(NodeDamage::NodeStyleDamaged);
self.transitioning_nodes.borrow_mut().remove(node_index);
atom!("transitionend")
},
TransitionEventType::TransitionCancel => atom!("transitioncancel"),
TransitionEventType::TransitionCancel => {
self.transitioning_nodes.borrow_mut().remove(node_index);
atom!("transitioncancel")
},
};

let event_init = TransitionEventInit {
@@ -283,8 +283,10 @@ pub enum UpdatePipelineIdReason {
}

/// The type of transition event to trigger.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum TransitionEventType {
/// The transition has started running.
TransitionRun,
/// The transition has ended by reaching the end of its animation.
TransitionEnd,
/// The transition ended early for some reason, such as the property
@@ -1,8 +1,4 @@
[before-load-001.html]
expected: TIMEOUT
[transition height from 10px to 100px / events]
expected: FAIL

[CSS Transitions Test: Transitioning before load event]
expected: TIMEOUT

@@ -1,34 +1,16 @@
[idlharness.html]
[Document interface: document must inherit property "ontransitionrun" with the proper type]
expected: FAIL

[HTMLElement interface: attribute ontransitionstart]
expected: FAIL

[Document interface: document must inherit property "ontransitionstart" with the proper type]
expected: FAIL

[HTMLElement interface: attribute ontransitionrun]
expected: FAIL

[Window interface: attribute ontransitionrun]
expected: FAIL

[HTMLElement interface: document must inherit property "ontransitionstart" with the proper type]
expected: FAIL

[HTMLElement interface: document must inherit property "ontransitionrun" with the proper type]
expected: FAIL

[Window interface: window must inherit property "ontransitionrun" with the proper type]
expected: FAIL

[Document interface: attribute ontransitionstart]
expected: FAIL

[Document interface: attribute ontransitionrun]
expected: FAIL

[Window interface: window must inherit property "ontransitionstart" with the proper type]
expected: FAIL

@@ -1,8 +1,4 @@
[non-rendered-element-001.html]
expected: TIMEOUT
[Transitions are canceled when an element is no longer rendered]
expected: TIMEOUT

[Transitions do not run for an element newly rendered]
expected: FAIL

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.