diff options
author | Keith Yeung <kungfukeith11@gmail.com> | 2016-09-03 07:27:39 -0700 |
---|---|---|
committer | Keith Yeung <kungfukeith11@gmail.com> | 2016-10-11 19:36:06 -0700 |
commit | 668163ec5c4091806b155ef14d3b3522cb4697cd (patch) | |
tree | c392fa270399e7fdf1ae103720ea7b7406ddfc2d | |
parent | 752c6e6019f72e6ee1b7ee959e80f588b6714cfd (diff) | |
download | servo-668163ec5c4091806b155ef14d3b3522cb4697cd.tar.gz servo-668163ec5c4091806b155ef14d3b3522cb4697cd.zip |
Emit TransitionEnd events in the layout thread and process it in the script thread
21 files changed, 103 insertions, 62 deletions
diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 71a388dcf0c..22387dff562 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -10,7 +10,7 @@ use gfx::display_list::OpaqueNode; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use script_layout_interface::restyle_damage::RestyleDamage; -use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; +use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; @@ -21,6 +21,7 @@ use style::timer::Timer; /// Also expire any old animations that have completed, inserting them into /// `expired_animations`. pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, + script_chan: &IpcSender<ConstellationControlMsg>, running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, new_animations_receiver: &Receiver<Animation>, @@ -70,7 +71,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, let mut animations_still_running = vec![]; for mut running_animation in running_animations.drain(..) { let still_running = !running_animation.is_expired() && match running_animation { - Animation::Transition(_, started_at, ref frame, _expired) => { + Animation::Transition(_, _, started_at, ref frame, _expired) => { now < started_at + frame.duration } Animation::Keyframes(_, _, ref mut state) => { @@ -85,6 +86,13 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, continue } + if let Animation::Transition(_, unsafe_node, _, ref frame, _) = running_animation { + script_chan.send(ConstellationControlMsg::TransitionEnd(unsafe_node, + frame.property_animation.property_name(), + frame.duration)) + .unwrap(); + } + expired_animations.entry(*key) .or_insert_with(Vec::new) .push(running_animation); diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 4797b8814aa..026e9796883 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1477,6 +1477,7 @@ impl LayoutThread { if let Some(mut root_flow) = self.root_flow.clone() { // Kick off animations if any were triggered, expire completed ones. animation::update_animation_state(&self.constellation_chan, + &self.script_chan, &mut *self.running_animations.write().unwrap(), &mut *self.expired_animations.write().unwrap(), &self.new_animations_receiver, diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs index 1fd127b3b28..fc881fee3b7 100644 --- a/components/script/dom/bindings/js.rs +++ b/components/script/dom/bindings/js.rs @@ -444,6 +444,14 @@ impl<T: Reflectable> LayoutJS<T> { debug_assert!(thread_state::get().is_layout()); *self.ptr } + + /// Returns a reference to the interior of this JS object. This method is + /// safe to call because it originates from the layout thread, and it cannot + /// mutate DOM nodes. + pub fn get_for_script(&self) -> &T { + debug_assert!(thread_state::get().is_script()); + unsafe { &**self.ptr } + } } /// Get an `&T` out of a `Rc<T>` diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index e69255b6aed..5935f02cde2 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -472,6 +472,7 @@ macro_rules! global_event_handlers( event_handler!(suspend, GetOnsuspend, SetOnsuspend); event_handler!(timeupdate, GetOntimeupdate, SetOntimeupdate); event_handler!(toggle, GetOntoggle, SetOntoggle); + event_handler!(transitionend, GetOntransitionend, SetOntransitionend); event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange); event_handler!(waiting, GetOnwaiting, SetOnwaiting); ) diff --git a/components/script/dom/webidls/EventHandler.webidl b/components/script/dom/webidls/EventHandler.webidl index 1af56394c35..1ffa128e128 100644 --- a/components/script/dom/webidls/EventHandler.webidl +++ b/components/script/dom/webidls/EventHandler.webidl @@ -89,6 +89,11 @@ interface GlobalEventHandlers { attribute EventHandler onwaiting; }; +// https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl +partial interface GlobalEventHandlers { + attribute EventHandler ontransitionend; +}; + // https://html.spec.whatwg.org/multipage/#windoweventhandlers [NoInterfaceObject, Exposed=(Window,Worker)] interface WindowEventHandlers { diff --git a/components/script/dom/webidls/TransitionEvent.webidl b/components/script/dom/webidls/TransitionEvent.webidl index f1e284ac02d..5e7a3ff6913 100644 --- a/components/script/dom/webidls/TransitionEvent.webidl +++ b/components/script/dom/webidls/TransitionEvent.webidl @@ -6,7 +6,8 @@ * https://dom.spec.whatwg.org/#event */ -[Constructor(DOMString type, optional TransitionEventInit transitionEventInitDict)] +[Constructor(DOMString type, optional TransitionEventInit transitionEventInitDict), + Exposed=Window] interface TransitionEvent : Event { readonly attribute DOMString propertyName; readonly attribute float elapsedTime; diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index f2947bedc93..451cfe82623 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -413,7 +413,7 @@ impl<'ln> ServoLayoutNode<'ln> { /// Returns the interior of this node as a `LayoutJS`. This is highly unsafe for layout to /// call and as such is marked `unsafe`. - unsafe fn get_jsmanaged(&self) -> &LayoutJS<Node> { + pub unsafe fn get_jsmanaged(&self) -> &LayoutJS<Node> { &self.node } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 87d4d148253..5667bca4be5 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -23,13 +23,17 @@ use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; use devtools_traits::CSSError; use document_loader::DocumentLoader; use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; +use dom::bindings::codegen::Bindings::EventBinding::EventInit; use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; +use dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, StringificationBehavior}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, MutNullableHeap, Root, RootCollection}; use dom::bindings::js::{RootCollectionPtr, RootedReference}; +use dom::bindings::num::Finite; use dom::bindings::refcounted::Trusted; use dom::bindings::reflector::Reflectable; use dom::bindings::str::DOMString; @@ -47,6 +51,7 @@ use dom::serviceworkerregistration::ServiceWorkerRegistration; use dom::servoparser::{ParserContext, ServoParser}; use dom::servoparser::html::{ParseContext, parse_html}; use dom::servoparser::xml::{self, parse_xml}; +use dom::transitionevent::TransitionEvent; use dom::uievent::UIEvent; use dom::window::{ReflowReason, Window}; use dom::worker::TrustedWorkerAddress; @@ -65,6 +70,7 @@ use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks}; use js::jsapi::{JSTracer, SetWindowProxyClass}; use js::jsval::UndefinedValue; use js::rust::Runtime; +use layout_wrapper::ServoLayoutNode; use mem::heap_size_of_self_and_children; use msg::constellation_msg::{FrameType, LoadData, PipelineId, PipelineNamespace}; use msg::constellation_msg::{ReferrerPolicy, WindowSizeType}; @@ -97,6 +103,7 @@ use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Select, Sender, channel}; use style::context::ReflowGoal; +use style::dom::{TNode, UnsafeNode}; use style::thread_state; use task_source::TaskSource; use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource}; @@ -917,6 +924,8 @@ impl ScriptThread { self.handle_webdriver_msg(pipeline_id, msg), 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::WebFontLoaded(pipeline_id) => self.handle_web_font_loaded(pipeline_id), ConstellationControlMsg::DispatchFrameLoadEvent { @@ -1523,6 +1532,35 @@ impl ScriptThread { document.run_the_animation_frame_callbacks(); } + /// Handles firing of transition events. + #[allow(unsafe_code)] + fn handle_transition_event(&self, unsafe_node: UnsafeNode, name: String, duration: f64) { + let node = unsafe { ServoLayoutNode::from_unsafe(&unsafe_node) }; + let node = unsafe { node.get_jsmanaged().get_for_script() }; + let window = window_from_node(node); + + if let Some(el) = node.downcast::<Element>() { + if &*window.GetComputedStyle(el, None).Display() == "none" { + return; + } + } + + let init = TransitionEventInit { + parent: EventInit { + bubbles: true, + cancelable: false, + }, + propertyName: DOMString::from(name), + elapsedTime: Finite::new(duration as f32).unwrap(), + // FIXME: Handle pseudo-elements properly + pseudoElement: DOMString::new() + }; + let transition_event = TransitionEvent::new(window.upcast(), + atom!("transitionend"), + &init); + transition_event.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) { if let Some(context) = self.find_child_context(pipeline_id) { diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 2220a9cf2e1..c5fa8fee4e6 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -66,7 +66,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::fmt; use std::sync::mpsc::{Receiver, Sender}; -use style_traits::{PagePx, ViewportPx}; +use style_traits::{PagePx, UnsafeNode, ViewportPx}; use url::Url; use util::ipc::OptionalOpaqueIpcSender; use webdriver_msg::{LoadStatus, WebDriverScriptCommand}; @@ -197,6 +197,8 @@ pub enum ConstellationControlMsg { WebDriverScriptCommand(PipelineId, WebDriverScriptCommand), /// Notifies script thread that all animations are done TickAllAnimations(PipelineId), + /// Notifies the script thread of a transition end + TransitionEnd(UnsafeNode, String, f64), /// Notifies the script thread that a new Web font has been loaded, and thus the page should be /// reflowed. WebFontLoaded(PipelineId), @@ -238,6 +240,7 @@ impl fmt::Debug for ConstellationControlMsg { FocusIFrame(..) => "FocusIFrame", WebDriverScriptCommand(..) => "WebDriverScriptCommand", TickAllAnimations(..) => "TickAllAnimations", + TransitionEnd(..) => "TransitionEnd", WebFontLoaded(..) => "WebFontLoaded", DispatchFrameLoadEvent { .. } => "DispatchFrameLoadEvent", FramedContentChanged(..) => "FramedContentChanged", diff --git a/components/style/animation.rs b/components/style/animation.rs index 9e55ca2e52a..bf74fa643f2 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -6,7 +6,7 @@ use bezier::Bezier; use context::SharedStyleContext; -use dom::OpaqueNode; +use dom::{OpaqueNode, UnsafeNode}; use euclid::point::Point2D; use keyframes::{KeyframesStep, KeyframesStepValue}; use properties::{self, ComputedValues, Importance}; @@ -186,7 +186,7 @@ pub enum Animation { /// the f64 field is the start time as returned by `time::precise_time_s()`. /// /// The `bool` field is werther this animation should no longer run. - Transition(OpaqueNode, f64, AnimationFrame, bool), + Transition(OpaqueNode, UnsafeNode, f64, AnimationFrame, bool), /// A keyframes animation is identified by a name, and can have a /// node-dependent state (i.e. iteration count, etc.). Keyframes(OpaqueNode, Atom, KeyframesAnimationState), @@ -197,7 +197,7 @@ impl Animation { pub fn mark_as_expired(&mut self) { debug_assert!(!self.is_expired()); match *self { - Animation::Transition(_, _, _, ref mut expired) => *expired = true, + Animation::Transition(_, _, _, _, ref mut expired) => *expired = true, Animation::Keyframes(_, _, ref mut state) => state.expired = true, } } @@ -205,7 +205,7 @@ impl Animation { #[inline] pub fn is_expired(&self) -> bool { match *self { - Animation::Transition(_, _, _, expired) => expired, + Animation::Transition(_, _, _, _, expired) => expired, Animation::Keyframes(_, _, ref state) => state.expired, } } @@ -213,7 +213,7 @@ impl Animation { #[inline] pub fn node(&self) -> &OpaqueNode { match *self { - Animation::Transition(ref node, _, _, _) => node, + Animation::Transition(ref node, _, _, _, _) => node, Animation::Keyframes(ref node, _, _) => node, } } @@ -246,6 +246,10 @@ pub struct PropertyAnimation { } impl PropertyAnimation { + pub fn property_name(&self) -> String { + self.property.name() + } + /// Creates a new property animation for the given transition index and old and new styles. /// Any number of animations may be returned, from zero (if the property did not animate) to /// one (for a single transition property) to arbitrarily many (for `all`). @@ -341,7 +345,8 @@ impl PropertyAnimation { // TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a // cloneable part and a non-cloneable part.. pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>, - node: OpaqueNode, + opaque_node: OpaqueNode, + unsafe_node: UnsafeNode, old_style: &ComputedValues, new_style: &mut Arc<ComputedValues>, timer: &Timer) @@ -362,7 +367,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation> let start_time = now + (box_style.transition_delay_mod(i).seconds() as f64); new_animations_sender - .send(Animation::Transition(node, start_time, AnimationFrame { + .send(Animation::Transition(opaque_node, unsafe_node, start_time, AnimationFrame { duration: box_style.transition_duration_mod(i).seconds() as f64, property_animation: property_animation, }, /* is_expired = */ false)).unwrap(); @@ -499,7 +504,7 @@ pub fn update_style_for_animation(context: &SharedStyleContext, debug_assert!(!animation.is_expired()); match *animation { - Animation::Transition(_, start_time, ref frame, _) => { + Animation::Transition(_, _, start_time, ref frame, _) => { debug!("update_style_for_animation: transition found"); let now = context.timer.seconds(); let mut new_style = (*style).clone(); @@ -673,7 +678,7 @@ pub fn complete_expired_transitions(node: OpaqueNode, style: &mut Arc<ComputedVa if let Some(ref animations) = animations_to_expire { for animation in *animations { // TODO: support animation-fill-mode - if let Animation::Transition(_, _, ref frame, _) = *animation { + if let Animation::Transition(_, _, _, ref frame, _) = *animation { frame.property_animation.update(Arc::make_mut(style), 1.0); } } diff --git a/components/style/dom.rs b/components/style/dom.rs index 5b2bbcb135e..3c61a5533d9 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -19,9 +19,7 @@ use std::ops::BitOr; use std::sync::Arc; use string_cache::{Atom, Namespace}; -/// Opaque type stored in type-unsafe work queues for parallel layout. -/// Must be transmutable to and from TNode. -pub type UnsafeNode = (usize, usize); +pub use style_traits::UnsafeNode; /// An opaque handle to a node, which, unlike UnsafeNode, cannot be transformed /// back into a non-opaque representation. The only safe operation that can be diff --git a/components/style/matching.rs b/components/style/matching.rs index 18e45a32ece..35e31b66062 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -569,6 +569,7 @@ trait PrivateMatchMethods: TNode { animation::start_transitions_if_applicable( new_animations_sender, this_opaque, + self.to_unsafe(), &**style, &mut this_style, &shared_context.timer); diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 384e5436976..c0f2fbe3b28 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -101,6 +101,16 @@ pub enum AnimatedProperty { } impl AnimatedProperty { + pub fn name(&self) -> String { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(..) => "${prop.name}".to_owned(), + % endif + % endfor + } + } + pub fn does_animate(&self) -> bool { match *self { % for prop in data.longhands: diff --git a/components/style_traits/lib.rs b/components/style_traits/lib.rs index 54657108341..449aca96ee3 100644 --- a/components/style_traits/lib.rs +++ b/components/style_traits/lib.rs @@ -27,6 +27,10 @@ extern crate rustc_serialize; #[cfg(feature = "servo")] extern crate serde; #[cfg(feature = "servo")] #[macro_use] extern crate serde_derive; +/// Opaque type stored in type-unsafe work queues for parallel layout. +/// Must be transmutable to and from TNode. +pub type UnsafeNode = (usize, usize); + /// One CSS "px" in the coordinate system of the "initial viewport": /// http://www.w3.org/TR/css-device-adapt/#initial-viewport /// diff --git a/tests/wpt/metadata-css/css-transitions-1_dev/html/changing-while-transition.htm.ini b/tests/wpt/metadata-css/css-transitions-1_dev/html/changing-while-transition.htm.ini index 3ea02d5025a..e1464721388 100644 --- a/tests/wpt/metadata-css/css-transitions-1_dev/html/changing-while-transition.htm.ini +++ b/tests/wpt/metadata-css/css-transitions-1_dev/html/changing-while-transition.htm.ini @@ -1,5 +1,7 @@ [changing-while-transition.htm] type: testharness + [changing transition-property / values] + expected: FAIL [changing transition-duration / values] expected: FAIL diff --git a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-001.htm.ini b/tests/wpt/metadata-css/css-transitions-1_dev/html/events-001.htm.ini index 7bbfe535d94..69679779896 100644 --- a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-001.htm.ini +++ b/tests/wpt/metadata-css/css-transitions-1_dev/html/events-001.htm.ini @@ -1,14 +1,5 @@ [events-001.htm] type: testharness - [transition:all, changing padding-left / events] - expected: FAIL - - [transition:all, changing padding / events] - expected: FAIL - - [transition:all, changing padding but not padding-bottom / events] - expected: FAIL - [transition:padding, changing padding-left / events] expected: FAIL @@ -18,12 +9,3 @@ [transition:padding, changing padding but not padding-bottom / events] expected: FAIL - [transition:padding-left, changing padding-left / events] - expected: FAIL - - [transition:padding-left, changing padding / events] - expected: FAIL - - [transition:padding-left, changing padding but not padding-bottom / events] - expected: FAIL - diff --git a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-002.htm.ini b/tests/wpt/metadata-css/css-transitions-1_dev/html/events-002.htm.ini deleted file mode 100644 index c46bd5155b2..00000000000 --- a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-002.htm.ini +++ /dev/null @@ -1,8 +0,0 @@ -[events-002.htm] - type: testharness - [transitions:all, changing padding-left from nothing / events] - expected: FAIL - - [transitions:all, changing padding from nothing / events] - expected: FAIL - diff --git a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-003.htm.ini b/tests/wpt/metadata-css/css-transitions-1_dev/html/events-003.htm.ini deleted file mode 100644 index 15ac729dffd..00000000000 --- a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-003.htm.ini +++ /dev/null @@ -1,8 +0,0 @@ -[events-003.htm] - type: testharness - [duration: 0.1s, delay: -0.05s / events] - expected: FAIL - - [duration: 0.1s, delay: -0.1s / events] - expected: FAIL - diff --git a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-004.htm.ini b/tests/wpt/metadata-css/css-transitions-1_dev/html/events-004.htm.ini deleted file mode 100644 index 3b5af3606d9..00000000000 --- a/tests/wpt/metadata-css/css-transitions-1_dev/html/events-004.htm.ini +++ /dev/null @@ -1,8 +0,0 @@ -[events-004.htm] - type: testharness - [repeating lists / events] - expected: FAIL - - [truncating lists / events] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta/css/incremental_trailing_whitespace_a.html.ini b/tests/wpt/mozilla/meta/css/incremental_trailing_whitespace_a.html.ini deleted file mode 100644 index 2a72cc1da38..00000000000 --- a/tests/wpt/mozilla/meta/css/incremental_trailing_whitespace_a.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[incremental_trailing_whitespace_a.html] - type: reftest - disabled: https://github.com/servo/servo/issues/10473 diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index 8599e2a1780..8c318e4d7d3 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -161,6 +161,7 @@ test_interfaces([ "Touch", "TouchEvent", "TouchList", + "TransitionEvent", "TreeWalker", "UIEvent", "URL", |