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

Root nodes for the duration of their CSS transitions #16295

Merged
merged 5 commits into from May 15, 2017
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

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.
  • Loading branch information
jdm committed May 15, 2017
commit dabebdfbf5e502e3eef7cb0159b20f0fa5e74f65
@@ -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<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>,
running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
newly_transitioning_nodes: &mut Vec<UntrustedNodeAddress>,
new_animations_receiver: &Receiver<Animation>,
pipeline_id: PipelineId,
timer: &Timer) {
@@ -71,7 +74,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) => {
@@ -86,8 +89,8 @@ 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,
if let Animation::Transition(node, _, ref frame, _) = running_animation {
script_chan.send(ConstellationControlMsg::TransitionEnd(node.to_untrusted_node_address(),

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

Why can't we send back the node itself?

This comment has been minimized.

Copy link
@jdm

jdm Apr 11, 2017

Author Member

It's a layout type.

frame.property_animation
.property_name().into(),
frame.duration))
@@ -112,6 +115,10 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,

// 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)
@@ -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<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>>);
@@ -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<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 {
@@ -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);
@@ -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();

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

Layout can send back garbage addresses and then everything goes to hell.

ScriptThread::note_newly_transitioning_nodes(newly_transitioning_nodes);

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

This function definitely should be unsafe.


true
}

@@ -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<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
@@ -574,6 +576,17 @@ impl ScriptThreadFactory for ScriptThread {
}

impl ScriptThread {
pub fn note_newly_transitioning_nodes(nodes: Vec<UntrustedNodeAddress>) {

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

Can GC happen before this even gets a change to be called?

IMO this whole function should be unsafe.

This comment has been minimized.

Copy link
@jdm

jdm Apr 11, 2017

Author Member

Anything that has a transition starting during layout is part of the DOM, therefore cannot be GCed. There's no need for this to be unsafe.

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

It needs to be unsafe.

ScriptThread::note_newly_transitioning_nodes(
    vec![UntrustedNodeAddress:from_id(0xdeadbeaf)])
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)));

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

Function from_untrusted_node_address should have definitely be unsafe.

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) {

This comment has been minimized.

Copy link
@nox

nox Apr 11, 2017

Member

This should be unsafe.

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);
@@ -42,6 +42,8 @@ pub trait LayoutRPC {
fn pending_images(&self) -> Vec<PendingImage>;
/// Requests the list of nodes from the given point.
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;
}
@@ -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),
@@ -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<Animation>,
opaque_node: OpaqueNode,
unsafe_node: UnsafeNode,
old_style: &ComputedValues,
new_style: &mut Arc<ComputedValues>,
timer: &Timer,
@@ -436,7 +444,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(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<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);
}
}
@@ -745,7 +745,6 @@ trait PrivateMatchMethods: TElement {
animation::start_transitions_if_applicable(
new_animations_sender,
this_opaque,
self.as_node().to_unsafe(),
&**values,
new_values,
&shared_context.timer,
@@ -843,7 +842,7 @@ trait PrivateMatchMethods: TElement {
running_animation,
style,
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())
}
}
{}
]
],
"mozilla/transitionend_safety.html": [
[
"/_mozilla/mozilla/transitionend_safety.html",
{}
]
],
"mozilla/union.html": [
[
"/_mozilla/mozilla/union.html",
"38d8991444d05c40f1d0168bfce8472e378b603c",
"testharness"
],
"mozilla/transitionend_safety.html": [
"778e43b049aa421bad7f86eb03d0955576a84ce0",
"testharness"
],
"mozilla/union.html": [
"47ee847e660eb907a7bd916cf37cf3ceba68ea7d",
"testharness"
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.