mirror of
https://github.com/servo/servo.git
synced 2025-07-30 18:50:36 +01:00
Root nodes for the duration of their CSS transitions.
This ensures that we can pass a node address as part of the asynchronous transition end notification, making it safe to fire the corresponding DOM event on the node from the script thread. Without explicitly rooting this node when the transition starts, we risk the node being GCed before the transition is complete.
This commit is contained in:
parent
f3c8f7e0d0
commit
dabebdfbf5
11 changed files with 126 additions and 25 deletions
|
@ -9,7 +9,9 @@ use flow::{self, Flow};
|
||||||
use gfx::display_list::OpaqueNode;
|
use gfx::display_list::OpaqueNode;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
|
use opaque_node::OpaqueNodeMethods;
|
||||||
use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
|
use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
|
||||||
|
use script_traits::UntrustedNodeAddress;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use style::animation::{Animation, update_style_for_animation};
|
use style::animation::{Animation, update_style_for_animation};
|
||||||
|
@ -24,6 +26,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
|
||||||
script_chan: &IpcSender<ConstellationControlMsg>,
|
script_chan: &IpcSender<ConstellationControlMsg>,
|
||||||
running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
|
running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
|
||||||
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
|
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
|
||||||
|
newly_transitioning_nodes: &mut Vec<UntrustedNodeAddress>,
|
||||||
new_animations_receiver: &Receiver<Animation>,
|
new_animations_receiver: &Receiver<Animation>,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
timer: &Timer) {
|
timer: &Timer) {
|
||||||
|
@ -71,7 +74,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
|
||||||
let mut animations_still_running = vec![];
|
let mut animations_still_running = vec![];
|
||||||
for mut running_animation in running_animations.drain(..) {
|
for mut running_animation in running_animations.drain(..) {
|
||||||
let still_running = !running_animation.is_expired() && match running_animation {
|
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
|
now < started_at + frame.duration
|
||||||
}
|
}
|
||||||
Animation::Keyframes(_, _, ref mut state) => {
|
Animation::Keyframes(_, _, ref mut state) => {
|
||||||
|
@ -86,8 +89,8 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Animation::Transition(_, unsafe_node, _, ref frame, _) = running_animation {
|
if let Animation::Transition(node, _, ref frame, _) = running_animation {
|
||||||
script_chan.send(ConstellationControlMsg::TransitionEnd(unsafe_node,
|
script_chan.send(ConstellationControlMsg::TransitionEnd(node.to_untrusted_node_address(),
|
||||||
frame.property_animation
|
frame.property_animation
|
||||||
.property_name().into(),
|
.property_name().into(),
|
||||||
frame.duration))
|
frame.duration))
|
||||||
|
@ -112,6 +115,10 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
|
||||||
|
|
||||||
// Add new running animations.
|
// Add new running animations.
|
||||||
for new_running_animation in 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())
|
running_animations.entry(*new_running_animation.node())
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
.push(new_running_animation)
|
.push(new_running_animation)
|
||||||
|
|
|
@ -94,6 +94,9 @@ pub struct LayoutThreadData {
|
||||||
|
|
||||||
/// A queued response for the list of nodes at a given point.
|
/// A queued response for the list of nodes at a given point.
|
||||||
pub nodes_from_point_response: Vec<UntrustedNodeAddress>,
|
pub nodes_from_point_response: Vec<UntrustedNodeAddress>,
|
||||||
|
|
||||||
|
/// A list of nodes that have just started a CSS transition.
|
||||||
|
pub newly_transitioning_nodes: Vec<UntrustedNodeAddress>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
|
pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
|
||||||
|
@ -204,6 +207,12 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
let mut rw_data = rw_data.lock().unwrap();
|
let mut rw_data = rw_data.lock().unwrap();
|
||||||
mem::replace(&mut rw_data.pending_images, vec![])
|
mem::replace(&mut rw_data.pending_images, vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn newly_transitioning_nodes(&self) -> Vec<UntrustedNodeAddress> {
|
||||||
|
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 {
|
struct UnioningFragmentBorderBoxIterator {
|
||||||
|
|
|
@ -478,6 +478,7 @@ impl LayoutThread {
|
||||||
text_index_response: TextIndexResponse(None),
|
text_index_response: TextIndexResponse(None),
|
||||||
pending_images: vec![],
|
pending_images: vec![],
|
||||||
nodes_from_point_response: vec![],
|
nodes_from_point_response: vec![],
|
||||||
|
newly_transitioning_nodes: vec![],
|
||||||
})),
|
})),
|
||||||
error_reporter: CSSErrorReporter {
|
error_reporter: CSSErrorReporter {
|
||||||
pipelineid: id,
|
pipelineid: id,
|
||||||
|
@ -1436,11 +1437,14 @@ impl LayoutThread {
|
||||||
document: Option<&ServoLayoutDocument>,
|
document: Option<&ServoLayoutDocument>,
|
||||||
rw_data: &mut LayoutThreadData,
|
rw_data: &mut LayoutThreadData,
|
||||||
context: &mut LayoutContext) {
|
context: &mut LayoutContext) {
|
||||||
|
assert!(rw_data.newly_transitioning_nodes.is_empty());
|
||||||
|
|
||||||
// Kick off animations if any were triggered, expire completed ones.
|
// Kick off animations if any were triggered, expire completed ones.
|
||||||
animation::update_animation_state(&self.constellation_chan,
|
animation::update_animation_state(&self.constellation_chan,
|
||||||
&self.script_chan,
|
&self.script_chan,
|
||||||
&mut *self.running_animations.write(),
|
&mut *self.running_animations.write(),
|
||||||
&mut *self.expired_animations.write(),
|
&mut *self.expired_animations.write(),
|
||||||
|
&mut rw_data.newly_transitioning_nodes,
|
||||||
&self.new_animations_receiver,
|
&self.new_animations_receiver,
|
||||||
self.id,
|
self.id,
|
||||||
&self.timer);
|
&self.timer);
|
||||||
|
|
|
@ -76,7 +76,7 @@ use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse
|
||||||
use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse};
|
use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse};
|
||||||
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
|
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
|
||||||
use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper};
|
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::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress};
|
||||||
use script_traits::{DocumentState, TimerEvent, TimerEventId};
|
use script_traits::{DocumentState, TimerEvent, TimerEventId};
|
||||||
use script_traits::{ScriptMsg as ConstellationMsg, TimerSchedulerMsg, WindowSizeData, WindowSizeType};
|
use script_traits::{ScriptMsg as ConstellationMsg, TimerSchedulerMsg, WindowSizeData, WindowSizeType};
|
||||||
|
@ -1150,6 +1150,7 @@ impl Window {
|
||||||
/// off-main-thread layout.
|
/// off-main-thread layout.
|
||||||
///
|
///
|
||||||
/// Returns true if layout actually happened, false otherwise.
|
/// Returns true if layout actually happened, false otherwise.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
pub fn force_reflow(&self,
|
pub fn force_reflow(&self,
|
||||||
goal: ReflowGoal,
|
goal: ReflowGoal,
|
||||||
query_type: ReflowQueryType,
|
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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ use dom::globalscope::GlobalScope;
|
||||||
use dom::htmlanchorelement::HTMLAnchorElement;
|
use dom::htmlanchorelement::HTMLAnchorElement;
|
||||||
use dom::htmliframeelement::{HTMLIFrameElement, NavigationType};
|
use dom::htmliframeelement::{HTMLIFrameElement, NavigationType};
|
||||||
use dom::mutationobserver::MutationObserver;
|
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::serviceworker::TrustedServiceWorkerAddress;
|
||||||
use dom::serviceworkerregistration::ServiceWorkerRegistration;
|
use dom::serviceworkerregistration::ServiceWorkerRegistration;
|
||||||
use dom::servoparser::{ParserContext, ServoParser};
|
use dom::servoparser::{ParserContext, ServoParser};
|
||||||
|
@ -69,7 +69,6 @@ use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks};
|
||||||
use js::jsapi::{JSTracer, SetWindowProxyClass};
|
use js::jsapi::{JSTracer, SetWindowProxyClass};
|
||||||
use js::jsval::UndefinedValue;
|
use js::jsval::UndefinedValue;
|
||||||
use js::rust::Runtime;
|
use js::rust::Runtime;
|
||||||
use layout_wrapper::ServoLayoutNode;
|
|
||||||
use mem::heap_size_of_self_and_children;
|
use mem::heap_size_of_self_and_children;
|
||||||
use microtask::{MicrotaskQueue, Microtask};
|
use microtask::{MicrotaskQueue, Microtask};
|
||||||
use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace};
|
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::sync::mpsc::{Receiver, Select, Sender, channel};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use style::context::ReflowGoal;
|
use style::context::ReflowGoal;
|
||||||
use style::dom::{TNode, UnsafeNode};
|
|
||||||
use style::thread_state;
|
use style::thread_state;
|
||||||
use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource};
|
use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource};
|
||||||
use task_source::file_reading::FileReadingTaskSource;
|
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
|
/// A list of pipelines containing documents that finished loading all their blocking
|
||||||
/// resources during a turn of the event loop.
|
/// resources during a turn of the event loop.
|
||||||
docs_with_no_blocking_loads: DOMRefCell<HashSet<JS<Document>>>,
|
docs_with_no_blocking_loads: DOMRefCell<HashSet<JS<Document>>>,
|
||||||
|
|
||||||
|
/// A list of nodes with in-progress CSS transitions, which roots them for the duration
|
||||||
|
/// of the transition.
|
||||||
|
transitioning_nodes: DOMRefCell<Vec<JS<Node>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In the event of thread panic, all data on the stack runs its destructor. However, there
|
/// 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 {
|
impl ScriptThread {
|
||||||
|
pub fn note_newly_transitioning_nodes(nodes: Vec<UntrustedNodeAddress>) {
|
||||||
|
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) {
|
pub fn add_mutation_observer(observer: &MutationObserver) {
|
||||||
SCRIPT_THREAD_ROOT.with(|root| {
|
SCRIPT_THREAD_ROOT.with(|root| {
|
||||||
let script_thread = unsafe { &*root.get().unwrap() };
|
let script_thread = unsafe { &*root.get().unwrap() };
|
||||||
|
@ -742,6 +755,8 @@ impl ScriptThread {
|
||||||
webvr_thread: state.webvr_thread,
|
webvr_thread: state.webvr_thread,
|
||||||
|
|
||||||
docs_with_no_blocking_loads: Default::default(),
|
docs_with_no_blocking_loads: Default::default(),
|
||||||
|
|
||||||
|
transitioning_nodes: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1602,11 +1617,27 @@ impl ScriptThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles firing of transition events.
|
/// Handles firing of transition events.
|
||||||
#[allow(unsafe_code)]
|
fn handle_transition_event(&self, unsafe_node: UntrustedNodeAddress, name: String, duration: f64) {
|
||||||
fn handle_transition_event(&self, unsafe_node: UnsafeNode, name: String, duration: f64) {
|
let js_runtime = self.js_runtime.rt();
|
||||||
let node = unsafe { ServoLayoutNode::from_unsafe(&unsafe_node) };
|
let node = from_untrusted_node_address(js_runtime, unsafe_node);
|
||||||
let node = unsafe { node.get_jsmanaged().get_for_script() };
|
|
||||||
let window = window_from_node(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.
|
// Not quite the right thing - see #13865.
|
||||||
node.dirty(NodeDamage::NodeStyleDamaged);
|
node.dirty(NodeDamage::NodeStyleDamaged);
|
||||||
|
|
|
@ -42,6 +42,8 @@ pub trait LayoutRPC {
|
||||||
fn pending_images(&self) -> Vec<PendingImage>;
|
fn pending_images(&self) -> Vec<PendingImage>;
|
||||||
/// Requests the list of nodes from the given point.
|
/// Requests the list of nodes from the given point.
|
||||||
fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>;
|
fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>;
|
||||||
|
/// Requests the list of nodes that have just started CSS transitions in the last reflow.
|
||||||
|
fn newly_transitioning_nodes(&self) -> Vec<UntrustedNodeAddress>;
|
||||||
|
|
||||||
fn text_index(&self) -> TextIndexResponse;
|
fn text_index(&self) -> TextIndexResponse;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use style_traits::{CSSPixel, UnsafeNode};
|
use style_traits::CSSPixel;
|
||||||
use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
|
use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
|
||||||
use webrender_traits::ClipId;
|
use webrender_traits::ClipId;
|
||||||
use webvr_traits::{WebVREvent, WebVRMsg};
|
use webvr_traits::{WebVREvent, WebVRMsg};
|
||||||
|
@ -274,7 +274,7 @@ pub enum ConstellationControlMsg {
|
||||||
/// Notifies script thread that all animations are done
|
/// Notifies script thread that all animations are done
|
||||||
TickAllAnimations(PipelineId),
|
TickAllAnimations(PipelineId),
|
||||||
/// Notifies the script thread of a transition end
|
/// 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
|
/// Notifies the script thread that a new Web font has been loaded, and thus the page should be
|
||||||
/// reflowed.
|
/// reflowed.
|
||||||
WebFontLoaded(PipelineId),
|
WebFontLoaded(PipelineId),
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
use Atom;
|
use Atom;
|
||||||
use bezier::Bezier;
|
use bezier::Bezier;
|
||||||
use context::SharedStyleContext;
|
use context::SharedStyleContext;
|
||||||
use dom::{OpaqueNode, UnsafeNode};
|
use dom::OpaqueNode;
|
||||||
use euclid::point::Point2D;
|
use euclid::point::Point2D;
|
||||||
use font_metrics::FontMetricsProvider;
|
use font_metrics::FontMetricsProvider;
|
||||||
use keyframes::{KeyframesStep, KeyframesStepValue};
|
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 f64 field is the start time as returned by `time::precise_time_s()`.
|
||||||
///
|
///
|
||||||
/// The `bool` field is werther this animation should no longer run.
|
/// 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
|
/// A keyframes animation is identified by a name, and can have a
|
||||||
/// node-dependent state (i.e. iteration count, etc.).
|
/// node-dependent state (i.e. iteration count, etc.).
|
||||||
Keyframes(OpaqueNode, Atom, KeyframesAnimationState),
|
Keyframes(OpaqueNode, Atom, KeyframesAnimationState),
|
||||||
|
@ -200,7 +200,7 @@ impl Animation {
|
||||||
pub fn mark_as_expired(&mut self) {
|
pub fn mark_as_expired(&mut self) {
|
||||||
debug_assert!(!self.is_expired());
|
debug_assert!(!self.is_expired());
|
||||||
match *self {
|
match *self {
|
||||||
Animation::Transition(_, _, _, _, ref mut expired) => *expired = true,
|
Animation::Transition(_, _, _, ref mut expired) => *expired = true,
|
||||||
Animation::Keyframes(_, _, ref mut state) => state.expired = true,
|
Animation::Keyframes(_, _, ref mut state) => state.expired = true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ impl Animation {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_expired(&self) -> bool {
|
pub fn is_expired(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Animation::Transition(_, _, _, _, expired) => expired,
|
Animation::Transition(_, _, _, expired) => expired,
|
||||||
Animation::Keyframes(_, _, ref state) => state.expired,
|
Animation::Keyframes(_, _, ref state) => state.expired,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,7 @@ impl Animation {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn node(&self) -> &OpaqueNode {
|
pub fn node(&self) -> &OpaqueNode {
|
||||||
match *self {
|
match *self {
|
||||||
Animation::Transition(ref node, _, _, _, _) => node,
|
Animation::Transition(ref node, _, _, _) => node,
|
||||||
Animation::Keyframes(ref node, _, _) => node,
|
Animation::Keyframes(ref node, _, _) => node,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,6 +231,15 @@ impl Animation {
|
||||||
Animation::Keyframes(_, _, ref state) => state.is_paused(),
|
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")]
|
#[cfg(feature = "servo")]
|
||||||
pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>,
|
pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>,
|
||||||
opaque_node: OpaqueNode,
|
opaque_node: OpaqueNode,
|
||||||
unsafe_node: UnsafeNode,
|
|
||||||
old_style: &ComputedValues,
|
old_style: &ComputedValues,
|
||||||
new_style: &mut Arc<ComputedValues>,
|
new_style: &mut Arc<ComputedValues>,
|
||||||
timer: &Timer,
|
timer: &Timer,
|
||||||
|
@ -436,7 +444,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>
|
||||||
let start_time =
|
let start_time =
|
||||||
now + (box_style.transition_delay_mod(i).seconds() as f64);
|
now + (box_style.transition_delay_mod(i).seconds() as f64);
|
||||||
new_animations_sender
|
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,
|
duration: box_style.transition_duration_mod(i).seconds() as f64,
|
||||||
property_animation: property_animation,
|
property_animation: property_animation,
|
||||||
}, /* is_expired = */ false)).unwrap();
|
}, /* is_expired = */ false)).unwrap();
|
||||||
|
@ -589,7 +597,7 @@ pub fn update_style_for_animation(context: &SharedStyleContext,
|
||||||
debug_assert!(!animation.is_expired());
|
debug_assert!(!animation.is_expired());
|
||||||
|
|
||||||
match *animation {
|
match *animation {
|
||||||
Animation::Transition(_, _, start_time, ref frame, _) => {
|
Animation::Transition(_, start_time, ref frame, _) => {
|
||||||
debug!("update_style_for_animation: transition found");
|
debug!("update_style_for_animation: transition found");
|
||||||
let now = context.timer.seconds();
|
let now = context.timer.seconds();
|
||||||
let mut new_style = (*style).clone();
|
let mut new_style = (*style).clone();
|
||||||
|
@ -767,7 +775,7 @@ pub fn complete_expired_transitions(node: OpaqueNode, style: &mut Arc<ComputedVa
|
||||||
if let Some(ref animations) = animations_to_expire {
|
if let Some(ref animations) = animations_to_expire {
|
||||||
for animation in *animations {
|
for animation in *animations {
|
||||||
// TODO: support animation-fill-mode
|
// 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);
|
frame.property_animation.update(Arc::make_mut(style), 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -745,7 +745,6 @@ trait PrivateMatchMethods: TElement {
|
||||||
animation::start_transitions_if_applicable(
|
animation::start_transitions_if_applicable(
|
||||||
new_animations_sender,
|
new_animations_sender,
|
||||||
this_opaque,
|
this_opaque,
|
||||||
self.as_node().to_unsafe(),
|
|
||||||
&**values,
|
&**values,
|
||||||
new_values,
|
new_values,
|
||||||
&shared_context.timer,
|
&shared_context.timer,
|
||||||
|
@ -843,7 +842,7 @@ trait PrivateMatchMethods: TElement {
|
||||||
running_animation,
|
running_animation,
|
||||||
style,
|
style,
|
||||||
font_metrics);
|
font_metrics);
|
||||||
if let Animation::Transition(_, _, _, ref frame, _) = *running_animation {
|
if let Animation::Transition(_, _, ref frame, _) = *running_animation {
|
||||||
possibly_expired_animations.push(frame.property_animation.clone())
|
possibly_expired_animations.push(frame.property_animation.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19897,6 +19897,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"mozilla/transitionend_safety.html": [
|
||||||
|
[
|
||||||
|
"/_mozilla/mozilla/transitionend_safety.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"mozilla/union.html": [
|
"mozilla/union.html": [
|
||||||
[
|
[
|
||||||
"/_mozilla/mozilla/union.html",
|
"/_mozilla/mozilla/union.html",
|
||||||
|
@ -31544,6 +31550,10 @@
|
||||||
"38d8991444d05c40f1d0168bfce8472e378b603c",
|
"38d8991444d05c40f1d0168bfce8472e378b603c",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
|
"mozilla/transitionend_safety.html": [
|
||||||
|
"778e43b049aa421bad7f86eb03d0955576a84ce0",
|
||||||
|
"testharness"
|
||||||
|
],
|
||||||
"mozilla/union.html": [
|
"mozilla/union.html": [
|
||||||
"47ee847e660eb907a7bd916cf37cf3ceba68ea7d",
|
"47ee847e660eb907a7bd916cf37cf3ceba68ea7d",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
|
27
tests/wpt/mozilla/tests/mozilla/transitionend_safety.html
Normal file
27
tests/wpt/mozilla/tests/mozilla/transitionend_safety.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Asynchronous transitionend event is not a GC hazard</title>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
async_test(function(t) {
|
||||||
|
var elem = document.createElement('div');
|
||||||
|
document.body.appendChild(elem);
|
||||||
|
elem.textContent = 'hi there';
|
||||||
|
elem.style.transition = 'color 10ms';
|
||||||
|
elem.style.color = 'black';
|
||||||
|
elem.ontransitionend = t.step_func_done();
|
||||||
|
|
||||||
|
t.step_timeout(function() {
|
||||||
|
elem.style.color = 'red';
|
||||||
|
|
||||||
|
t.step_timeout(function() {
|
||||||
|
document.body.removeChild(elem);
|
||||||
|
elem = null;
|
||||||
|
window.gc();
|
||||||
|
}, 0);
|
||||||
|
}, 0);
|
||||||
|
}, 'Nodes cannot be GCed while a CSS transition is in effect.');
|
||||||
|
</script>
|
||||||
|
</body>
|
Loading…
Add table
Add a link
Reference in a new issue