diff --git a/components/layout/animation.rs b/components/layout/animation.rs index be6d5baf77c..ebde01323e5 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -9,7 +9,9 @@ use flow::{self, Flow}; use gfx::display_list::OpaqueNode; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; +use opaque_node::OpaqueNodeMethods; use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; +use script_traits::UntrustedNodeAddress; use std::collections::HashMap; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; @@ -24,6 +26,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, script_chan: &IpcSender, running_animations: &mut HashMap>, expired_animations: &mut HashMap>, + newly_transitioning_nodes: &mut Vec, new_animations_receiver: &Receiver, pipeline_id: PipelineId, timer: &Timer) { @@ -71,7 +74,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, 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) => { @@ -86,8 +89,8 @@ pub fn update_animation_state(constellation_chan: &IpcSender, continue } - if let Animation::Transition(_, unsafe_node, _, ref frame, _) = running_animation { - script_chan.send(ConstellationControlMsg::TransitionEnd(unsafe_node, + if let Animation::Transition(node, _, ref frame, _) = running_animation { + script_chan.send(ConstellationControlMsg::TransitionEnd(node.to_untrusted_node_address(), frame.property_animation .property_name().into(), frame.duration)) @@ -112,6 +115,10 @@ pub fn update_animation_state(constellation_chan: &IpcSender, // Add new running animations. for new_running_animation in new_running_animations { + if new_running_animation.is_transition() { + newly_transitioning_nodes.push(new_running_animation.node().to_untrusted_node_address()); + } + running_animations.entry(*new_running_animation.node()) .or_insert_with(Vec::new) .push(new_running_animation) diff --git a/components/layout/query.rs b/components/layout/query.rs index c12a2b7f63d..a8d5913e3db 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -94,6 +94,9 @@ pub struct LayoutThreadData { /// A queued response for the list of nodes at a given point. pub nodes_from_point_response: Vec, + + /// A list of nodes that have just started a CSS transition. + pub newly_transitioning_nodes: Vec, } pub struct LayoutRPCImpl(pub Arc>); @@ -204,6 +207,12 @@ impl LayoutRPC for LayoutRPCImpl { let mut rw_data = rw_data.lock().unwrap(); mem::replace(&mut rw_data.pending_images, vec![]) } + + fn newly_transitioning_nodes(&self) -> Vec { + let &LayoutRPCImpl(ref rw_data) = self; + let mut rw_data = rw_data.lock().unwrap(); + mem::replace(&mut rw_data.newly_transitioning_nodes, vec![]) + } } struct UnioningFragmentBorderBoxIterator { diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 9e8cc9bc165..2bef4bc1c21 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -478,6 +478,7 @@ impl LayoutThread { text_index_response: TextIndexResponse(None), pending_images: vec![], nodes_from_point_response: vec![], + newly_transitioning_nodes: vec![], })), error_reporter: CSSErrorReporter { pipelineid: id, @@ -1436,11 +1437,14 @@ impl LayoutThread { document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, context: &mut LayoutContext) { + assert!(rw_data.newly_transitioning_nodes.is_empty()); + // 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(), &mut *self.expired_animations.write(), + &mut rw_data.newly_transitioning_nodes, &self.new_animations_receiver, self.id, &self.timer); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 993749b00c2..a783c15531b 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -76,7 +76,7 @@ use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory}; use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper}; -use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg}; +use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg, ScriptThread}; use script_traits::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress}; use script_traits::{DocumentState, TimerEvent, TimerEventId}; use script_traits::{ScriptMsg as ConstellationMsg, TimerSchedulerMsg, WindowSizeData, WindowSizeType}; @@ -1150,6 +1150,7 @@ impl Window { /// off-main-thread layout. /// /// Returns true if layout actually happened, false otherwise. + #[allow(unsafe_code)] pub fn force_reflow(&self, goal: ReflowGoal, query_type: ReflowQueryType, @@ -1261,6 +1262,9 @@ impl Window { } } + let newly_transitioning_nodes = self.layout_rpc.newly_transitioning_nodes(); + ScriptThread::note_newly_transitioning_nodes(newly_transitioning_nodes); + true } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 8a861e92570..805b19a24b2 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -47,7 +47,7 @@ use dom::globalscope::GlobalScope; use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmliframeelement::{HTMLIFrameElement, NavigationType}; use dom::mutationobserver::MutationObserver; -use dom::node::{Node, NodeDamage, window_from_node}; +use dom::node::{Node, NodeDamage, window_from_node, from_untrusted_node_address}; use dom::serviceworker::TrustedServiceWorkerAddress; use dom::serviceworkerregistration::ServiceWorkerRegistration; use dom::servoparser::{ParserContext, ServoParser}; @@ -69,7 +69,6 @@ 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 microtask::{MicrotaskQueue, Microtask}; use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace}; @@ -109,7 +108,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Select, Sender, channel}; use std::thread; use style::context::ReflowGoal; -use style::dom::{TNode, UnsafeNode}; use style::thread_state; use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource}; use task_source::file_reading::FileReadingTaskSource; @@ -490,6 +488,10 @@ pub struct ScriptThread { /// A list of pipelines containing documents that finished loading all their blocking /// resources during a turn of the event loop. docs_with_no_blocking_loads: DOMRefCell>>, + + /// A list of nodes with in-progress CSS transitions, which roots them for the duration + /// of the transition. + transitioning_nodes: DOMRefCell>>, } /// In the event of thread panic, all data on the stack runs its destructor. However, there @@ -574,6 +576,17 @@ impl ScriptThreadFactory for ScriptThread { } impl ScriptThread { + pub fn note_newly_transitioning_nodes(nodes: Vec) { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = unsafe { &*root.get().unwrap() }; + let js_runtime = script_thread.js_runtime.rt(); + let new_nodes = nodes + .into_iter() + .map(|n| JS::from_ref(&*from_untrusted_node_address(js_runtime, n))); + script_thread.transitioning_nodes.borrow_mut().extend(new_nodes); + }) + } + pub fn add_mutation_observer(observer: &MutationObserver) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; @@ -742,6 +755,8 @@ impl ScriptThread { webvr_thread: state.webvr_thread, docs_with_no_blocking_loads: Default::default(), + + transitioning_nodes: Default::default(), } } @@ -1602,11 +1617,27 @@ impl ScriptThread { } /// 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); + fn handle_transition_event(&self, unsafe_node: UntrustedNodeAddress, name: String, duration: f64) { + let js_runtime = self.js_runtime.rt(); + let node = from_untrusted_node_address(js_runtime, unsafe_node); + + let idx = self.transitioning_nodes + .borrow() + .iter() + .position(|n| &**n as *const _ == &*node as *const _); + match idx { + Some(idx) => { + self.transitioning_nodes.borrow_mut().remove(idx); + } + 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; + } + } + + let window = window_from_node(&*node); // Not quite the right thing - see #13865. node.dirty(NodeDamage::NodeStyleDamaged); diff --git a/components/script_layout_interface/rpc.rs b/components/script_layout_interface/rpc.rs index 82dd9b9ff08..1c694a7ff8c 100644 --- a/components/script_layout_interface/rpc.rs +++ b/components/script_layout_interface/rpc.rs @@ -42,6 +42,8 @@ pub trait LayoutRPC { fn pending_images(&self) -> Vec; /// Requests the list of nodes from the given point. fn nodes_from_point_response(&self) -> Vec; + /// Requests the list of nodes that have just started CSS transitions in the last reflow. + fn newly_transitioning_nodes(&self) -> Vec; fn text_index(&self) -> TextIndexResponse; } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 29baf8821e6..4f3cfca76a5 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -69,7 +69,7 @@ use std::collections::HashMap; use std::fmt; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; -use style_traits::{CSSPixel, UnsafeNode}; +use style_traits::CSSPixel; use webdriver_msg::{LoadStatus, WebDriverScriptCommand}; use webrender_traits::ClipId; use webvr_traits::{WebVREvent, WebVRMsg}; @@ -274,7 +274,7 @@ pub enum ConstellationControlMsg { /// Notifies script thread that all animations are done TickAllAnimations(PipelineId), /// Notifies the script thread of a transition end - TransitionEnd(UnsafeNode, String, f64), + TransitionEnd(UntrustedNodeAddress, String, f64), /// Notifies the script thread that a new Web font has been loaded, and thus the page should be /// reflowed. WebFontLoaded(PipelineId), diff --git a/components/style/animation.rs b/components/style/animation.rs index 632588988c2..5f53508ba4e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -8,7 +8,7 @@ use Atom; use bezier::Bezier; use context::SharedStyleContext; -use dom::{OpaqueNode, UnsafeNode}; +use dom::OpaqueNode; use euclid::point::Point2D; use font_metrics::FontMetricsProvider; use keyframes::{KeyframesStep, KeyframesStepValue}; @@ -188,7 +188,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, UnsafeNode, f64, AnimationFrame, bool), + Transition(OpaqueNode, 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), @@ -200,7 +200,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, } } @@ -209,7 +209,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, } } @@ -218,7 +218,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, } } @@ -231,6 +231,15 @@ impl Animation { Animation::Keyframes(_, _, ref state) => state.is_paused(), } } + + /// Whether this animation is a transition. + #[inline] + pub fn is_transition(&self) -> bool { + match *self { + Animation::Transition(..) => true, + Animation::Keyframes(..) => false, + } + } } @@ -402,7 +411,6 @@ impl PropertyAnimation { #[cfg(feature = "servo")] pub fn start_transitions_if_applicable(new_animations_sender: &Sender, opaque_node: OpaqueNode, - unsafe_node: UnsafeNode, old_style: &ComputedValues, new_style: &mut Arc, timer: &Timer, @@ -436,7 +444,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: &Sender let start_time = now + (box_style.transition_delay_mod(i).seconds() as f64); new_animations_sender - .send(Animation::Transition(opaque_node, unsafe_node, start_time, AnimationFrame { + .send(Animation::Transition(opaque_node, start_time, AnimationFrame { duration: box_style.transition_duration_mod(i).seconds() as f64, property_animation: property_animation, }, /* is_expired = */ false)).unwrap(); @@ -589,7 +597,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(); @@ -767,7 +775,7 @@ pub fn complete_expired_transitions(node: OpaqueNode, style: &mut Arc + +Asynchronous transitionend event is not a GC hazard + + + + +