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 canceling CSS transitions #26244

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

@@ -129,6 +129,7 @@ time
timeupdate
toggle
track
transitioncancel
transitionend
unhandledrejection
unload
@@ -13,7 +13,9 @@ use fxhash::{FxHashMap, FxHashSet};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, TransitionEventType,
};
use style::animation::{update_style_for_animation, Animation};
use style::dom::TElement;
use style::font_metrics::ServoMetricsProvider;
@@ -28,6 +30,7 @@ pub fn update_animation_state<E>(
script_chan: &IpcSender<ConstellationControlMsg>,
running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
cancelled_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
mut keys_to_remove: FxHashSet<OpaqueNode>,
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
new_animations_receiver: &Receiver<Animation>,
@@ -36,6 +39,8 @@ pub fn update_animation_state<E>(
) where
E: TElement,
{
send_events_for_cancelled_animations(script_chan, cancelled_animations, pipeline_id);

let mut new_running_animations = vec![];
while let Ok(animation) = new_animations_receiver.try_recv() {
let mut should_push = true;
@@ -102,11 +107,13 @@ pub fn update_animation_state<E>(

if let Animation::Transition(node, _, ref property_animation) = running_animation {
script_chan
.send(ConstellationControlMsg::TransitionEnd(
node.to_untrusted_node_address(),
property_animation.property_name().into(),
property_animation.duration,
))
.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();
}

@@ -161,6 +168,37 @@ pub fn update_animation_state<E>(
.unwrap();
}

/// 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>,
cancelled_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
pipeline_id: PipelineId,
) {
for (node, animations) in cancelled_animations.drain() {
for animation in animations {
match animation {
Animation::Transition(transition_node, _, ref property_animation) => {
debug_assert!(transition_node == node);
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::Keyframes(..) => {
warn!("Got unexpected animation in expired transitions list.")
},
}
}
}
}

/// 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.
@@ -207,6 +207,9 @@ pub struct LayoutThread {
/// The list of animations that have expired since the last style recalculation.
expired_animations: ServoArc<RwLock<FxHashMap<OpaqueNode, Vec<Animation>>>>,

/// The list of animations that have been cancelled during the last style recalculation.
cancelled_animations: ServoArc<RwLock<FxHashMap<OpaqueNode, Vec<Animation>>>>,

/// A counter for epoch messages
epoch: Cell<Epoch>,

@@ -576,6 +579,7 @@ impl LayoutThread {
document_shared_lock: None,
running_animations: ServoArc::new(RwLock::new(Default::default())),
expired_animations: ServoArc::new(RwLock::new(Default::default())),
cancelled_animations: 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)),
@@ -655,6 +659,7 @@ impl LayoutThread {
visited_styles_enabled: false,
running_animations: self.running_animations.clone(),
expired_animations: self.expired_animations.clone(),
cancelled_animations: self.cancelled_animations.clone(),
registered_speculative_painters: &self.registered_painters,
local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
timer: self.timer.clone(),
@@ -1785,6 +1790,7 @@ impl LayoutThread {
&self.script_chan,
&mut *self.running_animations.write(),
&mut *self.expired_animations.write(),
&mut *self.cancelled_animations.write(),
invalid_nodes,
newly_transitioning_nodes,
&self.new_animations_receiver,
@@ -609,6 +609,7 @@ impl LayoutThread {
visited_styles_enabled: false,
running_animations: Default::default(),
expired_animations: Default::default(),
cancelled_animations: Default::default(),
registered_speculative_painters: &self.registered_painters,
local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
timer: self.timer.clone(),
@@ -496,6 +496,7 @@ macro_rules! global_event_handlers(
event_handler!(suspend, GetOnsuspend, SetOnsuspend);
event_handler!(timeupdate, GetOntimeupdate, SetOntimeupdate);
event_handler!(toggle, GetOntoggle, SetOntoggle);
event_handler!(transitioncancel, GetOntransitioncancel, SetOntransitioncancel);
event_handler!(transitionend, GetOntransitionend, SetOntransitionend);
event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange);
event_handler!(waiting, GetOnwaiting, SetOnwaiting);
@@ -93,6 +93,7 @@ interface mixin GlobalEventHandlers {
// https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl
partial interface mixin GlobalEventHandlers {
attribute EventHandler ontransitionend;
attribute EventHandler ontransitioncancel;
};

// https://w3c.github.io/selection-api/#extensions-to-globaleventhandlers-interface
@@ -133,17 +133,15 @@ use script_traits::CompositorEvent::{
CompositionEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent, TouchEvent,
WheelEvent,
};
use script_traits::StructuredSerializedData;
use script_traits::{CompositorEvent, ConstellationControlMsg};
use script_traits::{
DiscardBrowsingContext, DocumentActivity, EventResult, HistoryEntryReplacement,
CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity,
EventResult, HistoryEntryReplacement, InitialScriptState, JsEvalResult, LayoutMsg, LoadData,
LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType, NewLayoutInfo, Painter,
ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory, ScriptToConstellationChan,
StructuredSerializedData, TimerSchedulerMsg, TouchEventType, TouchId, TransitionEventType,
UntrustedNodeAddress, UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, WindowSizeData,
WindowSizeType,
};
use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, LoadOrigin};
use script_traits::{MediaSessionActionType, MouseButton, MouseEventType, NewLayoutInfo};
use script_traits::{Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory};
use script_traits::{ScriptToConstellationChan, TimerSchedulerMsg};
use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta};
use script_traits::{UpdatePipelineIdReason, WebrenderIpcSender, WindowSizeData, WindowSizeType};
use servo_atoms::Atom;
use servo_config::opts;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
@@ -1668,46 +1666,43 @@ impl ScriptThread {
fn message_to_pipeline(&self, msg: &MixedMessage) -> Option<PipelineId> {
use script_traits::ConstellationControlMsg::*;
match *msg {
MixedMessage::FromConstellation(ref inner_msg) => {
match *inner_msg {
StopDelayingLoadEventsMode(id) => Some(id),
NavigationResponse(id, _) => Some(id),
AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id),
Resize(id, ..) => Some(id),
ResizeInactive(id, ..) => Some(id),
UnloadDocument(id) => Some(id),
ExitPipeline(id, ..) => Some(id),
ExitScriptThread => None,
SendEvent(id, ..) => Some(id),
Viewport(id, ..) => Some(id),
SetScrollState(id, ..) => Some(id),
GetTitle(id) => Some(id),
SetDocumentActivity(id, ..) => Some(id),
ChangeFrameVisibilityStatus(id, ..) => Some(id),
NotifyVisibilityChange(id, ..) => Some(id),
NavigateIframe(id, ..) => Some(id),
PostMessage { target: id, .. } => Some(id),
UpdatePipelineId(_, _, _, id, _) => Some(id),
UpdateHistoryState(id, ..) => Some(id),
RemoveHistoryStates(id, ..) => Some(id),
FocusIFrame(id, ..) => Some(id),
WebDriverScriptCommand(id, ..) => Some(id),
TickAllAnimations(id) => Some(id),
// FIXME https://github.com/servo/servo/issues/15079
TransitionEnd(..) => None,
WebFontLoaded(id) => Some(id),
DispatchIFrameLoadEvent {
target: _,
parent: id,
child: _,
} => Some(id),
DispatchStorageEvent(id, ..) => Some(id),
ReportCSSError(id, ..) => Some(id),
Reload(id, ..) => Some(id),
PaintMetric(..) => None,
ExitFullScreen(id, ..) => Some(id),
MediaSessionAction(..) => None,
}
MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg {
StopDelayingLoadEventsMode(id) => Some(id),
NavigationResponse(id, _) => Some(id),
AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id),
Resize(id, ..) => Some(id),
ResizeInactive(id, ..) => Some(id),
UnloadDocument(id) => Some(id),
ExitPipeline(id, ..) => Some(id),
ExitScriptThread => None,
SendEvent(id, ..) => Some(id),
Viewport(id, ..) => Some(id),
SetScrollState(id, ..) => Some(id),
GetTitle(id) => Some(id),
SetDocumentActivity(id, ..) => Some(id),
ChangeFrameVisibilityStatus(id, ..) => Some(id),
NotifyVisibilityChange(id, ..) => Some(id),
NavigateIframe(id, ..) => Some(id),
PostMessage { target: id, .. } => Some(id),
UpdatePipelineId(_, _, _, id, _) => Some(id),
UpdateHistoryState(id, ..) => Some(id),
RemoveHistoryStates(id, ..) => Some(id),
FocusIFrame(id, ..) => Some(id),
WebDriverScriptCommand(id, ..) => Some(id),
TickAllAnimations(id) => Some(id),
TransitionEvent { .. } => None,
WebFontLoaded(id) => Some(id),
DispatchIFrameLoadEvent {
target: _,
parent: id,
child: _,
} => Some(id),
DispatchStorageEvent(id, ..) => Some(id),
ReportCSSError(id, ..) => Some(id),
Reload(id, ..) => Some(id),
PaintMetric(..) => None,
ExitFullScreen(id, ..) => Some(id),
MediaSessionAction(..) => None,
},
MixedMessage::FromDevtools(_) => None,
MixedMessage::FromScript(ref inner_msg) => match *inner_msg {
@@ -1896,8 +1891,20 @@ impl ScriptThread {
ConstellationControlMsg::TickAllAnimations(pipeline_id) => {
self.handle_tick_all_animations(pipeline_id)
},
ConstellationControlMsg::TransitionEnd(unsafe_node, name, duration) => {
self.handle_transition_event(unsafe_node, name, duration)
ConstellationControlMsg::TransitionEvent {
pipeline_id,
event_type,
node,
property_name,
elapsed_time,
} => {
self.handle_transition_event(
pipeline_id,
event_type,
node,
property_name,
elapsed_time,
);
},
ConstellationControlMsg::WebFontLoaded(pipeline_id) => {
self.handle_web_font_loaded(pipeline_id)
@@ -2899,12 +2906,16 @@ impl ScriptThread {
document.run_the_animation_frame_callbacks();
}

/// Handles firing of transition events.
/// Handles firing of transition-related events.
///
/// TODO(mrobinson): Add support for more events.
fn handle_transition_event(
&self,
pipeline_id: PipelineId,
event_type: TransitionEventType,
unsafe_node: UntrustedNodeAddress,
name: String,
duration: f64,
property_name: String,
elapsed_time: f64,
) {
let js_runtime = self.js_runtime.rt();
let node = unsafe { from_untrusted_node_address(js_runtime, unsafe_node) };
@@ -2926,29 +2937,35 @@ impl ScriptThread {
},
}

let window = window_from_node(&*node);

// Not quite the right thing - see #13865.
node.dirty(NodeDamage::NodeStyleDamaged);

if let Some(el) = node.downcast::<Element>() {
if !el.has_css_layout_box() {
return;
}
if self.closed_pipelines.borrow().contains(&pipeline_id) {
warn!("Ignoring transition event for closed pipeline.");
return;
}

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

let event_init = TransitionEventInit {
parent: EventInit {
bubbles: true,
cancelable: false,
},
propertyName: DOMString::from(name),
elapsedTime: Finite::new(duration as f32).unwrap(),
// FIXME: Handle pseudo-elements properly
propertyName: DOMString::from(property_name),
elapsedTime: Finite::new(elapsed_time as f32).unwrap(),
// TODO: Handle pseudo-elements properly
pseudoElement: DOMString::new(),
};
let transition_event = TransitionEvent::new(&window, atom!("transitionend"), &init);
transition_event.upcast::<Event>().fire(node.upcast());

let window = window_from_node(&*node);
TransitionEvent::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.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.