mirror of
https://github.com/servo/servo.git
synced 2025-07-31 19:20:22 +01:00
layout: Tie transitions to the DOM node and finish them instantly when
new styles are set. Tying transitions to the DOM node avoids quadratic complexity when updating them. Finishing transitions instantly when styles are updated makes our behavior more correct.
This commit is contained in:
parent
92cbb93684
commit
7349b6ac28
5 changed files with 90 additions and 49 deletions
|
@ -65,11 +65,12 @@ const MIN_INDENTATION_LENGTH: usize = 4;
|
||||||
/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
|
/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
|
||||||
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
|
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
|
||||||
/// locality reasons. Using `OpaqueNode` enforces this invariant.
|
/// locality reasons. Using `OpaqueNode` enforces this invariant.
|
||||||
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf, Deserialize, Serialize)]
|
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf, Hash, Eq, Deserialize, Serialize)]
|
||||||
pub struct OpaqueNode(pub uintptr_t);
|
pub struct OpaqueNode(pub uintptr_t);
|
||||||
|
|
||||||
impl OpaqueNode {
|
impl OpaqueNode {
|
||||||
/// Returns the address of this node, for debugging purposes.
|
/// Returns the address of this node, for debugging purposes.
|
||||||
|
#[inline]
|
||||||
pub fn id(&self) -> uintptr_t {
|
pub fn id(&self) -> uintptr_t {
|
||||||
let OpaqueNode(pointer) = *self;
|
let OpaqueNode(pointer) = *self;
|
||||||
pointer
|
pointer
|
||||||
|
|
|
@ -13,7 +13,8 @@ use layout_task::{LayoutTask, LayoutTaskData};
|
||||||
use msg::constellation_msg::{AnimationState, Msg, PipelineId};
|
use msg::constellation_msg::{AnimationState, Msg, PipelineId};
|
||||||
use script::layout_interface::Animation;
|
use script::layout_interface::Animation;
|
||||||
use script_traits::{ConstellationControlMsg, ScriptControlChan};
|
use script_traits::{ConstellationControlMsg, ScriptControlChan};
|
||||||
use std::mem;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use style::animation::{GetMod, PropertyAnimation};
|
use style::animation::{GetMod, PropertyAnimation};
|
||||||
|
@ -50,8 +51,30 @@ pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>
|
||||||
|
|
||||||
/// Processes any new animations that were discovered after style recalculation.
|
/// Processes any new animations that were discovered after style recalculation.
|
||||||
pub fn process_new_animations(rw_data: &mut LayoutTaskData, pipeline_id: PipelineId) {
|
pub fn process_new_animations(rw_data: &mut LayoutTaskData, pipeline_id: PipelineId) {
|
||||||
|
let mut new_running_animations = Vec::new();
|
||||||
while let Ok(animation) = rw_data.new_animations_receiver.try_recv() {
|
while let Ok(animation) = rw_data.new_animations_receiver.try_recv() {
|
||||||
rw_data.running_animations.push(animation)
|
new_running_animations.push(animation)
|
||||||
|
}
|
||||||
|
if !new_running_animations.is_empty() {
|
||||||
|
let mut running_animations = (*rw_data.running_animations).clone();
|
||||||
|
|
||||||
|
// Expire old running animations.
|
||||||
|
let now = clock_ticks::precise_time_s();
|
||||||
|
for (_, running_animations) in running_animations.iter_mut() {
|
||||||
|
running_animations.retain(|running_animation| now < running_animation.end_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new running animations.
|
||||||
|
for new_running_animation in new_running_animations.into_iter() {
|
||||||
|
match running_animations.entry(OpaqueNode(new_running_animation.node)) {
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(vec![new_running_animation]);
|
||||||
|
}
|
||||||
|
Entry::Occupied(mut entry) => entry.get_mut().push(new_running_animation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rw_data.running_animations = Arc::new(running_animations);
|
||||||
}
|
}
|
||||||
|
|
||||||
let animation_state;
|
let animation_state;
|
||||||
|
@ -68,48 +91,42 @@ pub fn process_new_animations(rw_data: &mut LayoutTaskData, pipeline_id: Pipelin
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recalculates style for an animation. This does *not* run with the DOM lock held.
|
/// Recalculates style for a set of animations. This does *not* run with the DOM lock held.
|
||||||
pub fn recalc_style_for_animation(flow: &mut Flow, animation: &Animation) {
|
pub fn recalc_style_for_animations(flow: &mut Flow,
|
||||||
|
animations: &HashMap<OpaqueNode,Vec<Animation>>) {
|
||||||
let mut damage = RestyleDamage::empty();
|
let mut damage = RestyleDamage::empty();
|
||||||
flow.mutate_fragments(&mut |fragment| {
|
flow.mutate_fragments(&mut |fragment| {
|
||||||
if fragment.node.id() != animation.node {
|
if let Some(ref animations) = animations.get(&OpaqueNode(fragment.node.id())) {
|
||||||
return
|
for animation in animations.iter() {
|
||||||
}
|
let now = clock_ticks::precise_time_s();
|
||||||
|
let mut progress = (now - animation.start_time) / animation.duration();
|
||||||
|
if progress > 1.0 {
|
||||||
|
progress = 1.0
|
||||||
|
}
|
||||||
|
if progress <= 0.0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let now = clock_ticks::precise_time_s() as f64;
|
let mut new_style = fragment.style.clone();
|
||||||
let mut progress = (now - animation.start_time) / animation.duration();
|
animation.property_animation.update(&mut *Arc::make_unique(&mut new_style),
|
||||||
if progress > 1.0 {
|
progress);
|
||||||
progress = 1.0
|
damage.insert(incremental::compute_damage(&Some(fragment.style.clone()),
|
||||||
|
&new_style));
|
||||||
|
fragment.style = new_style
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if progress <= 0.0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_style = fragment.style.clone();
|
|
||||||
animation.property_animation.update(&mut *Arc::make_unique(&mut new_style), progress);
|
|
||||||
damage.insert(incremental::compute_damage(&Some(fragment.style.clone()), &new_style));
|
|
||||||
fragment.style = new_style
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let base = flow::mut_base(flow);
|
let base = flow::mut_base(flow);
|
||||||
base.restyle_damage.insert(damage);
|
base.restyle_damage.insert(damage);
|
||||||
for kid in base.children.iter_mut() {
|
for kid in base.children.iter_mut() {
|
||||||
recalc_style_for_animation(kid, animation)
|
recalc_style_for_animations(kid, animations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles animation updates.
|
/// Handles animation updates.
|
||||||
pub fn tick_all_animations(layout_task: &LayoutTask, rw_data: &mut LayoutTaskData) {
|
pub fn tick_all_animations(layout_task: &LayoutTask, rw_data: &mut LayoutTaskData) {
|
||||||
let running_animations = mem::replace(&mut rw_data.running_animations, Vec::new());
|
layout_task.tick_animations(rw_data);
|
||||||
let now = clock_ticks::precise_time_s() as f64;
|
|
||||||
for running_animation in running_animations.into_iter() {
|
|
||||||
layout_task.tick_animation(&running_animation, rw_data);
|
|
||||||
|
|
||||||
if now < running_animation.end_time {
|
|
||||||
// Keep running the animation if it hasn't expired.
|
|
||||||
rw_data.running_animations.push(running_animation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ScriptControlChan(ref chan) = layout_task.script_chan;
|
let ScriptControlChan(ref chan) = layout_task.script_chan;
|
||||||
chan.send(ConstellationControlMsg::TickAllAnimations(layout_task.id)).unwrap();
|
chan.send(ConstellationControlMsg::TickAllAnimations(layout_task.id)).unwrap();
|
||||||
|
|
|
@ -126,6 +126,9 @@ pub struct SharedLayoutContext {
|
||||||
/// The visible rects for each layer, as reported to us by the compositor.
|
/// The visible rects for each layer, as reported to us by the compositor.
|
||||||
pub visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>,
|
pub visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>,
|
||||||
|
|
||||||
|
/// The animations that are currently running.
|
||||||
|
pub running_animations: Arc<HashMap<OpaqueNode,Vec<Animation>>>,
|
||||||
|
|
||||||
/// Why is this reflow occurring
|
/// Why is this reflow occurring
|
||||||
pub goal: ReflowGoal,
|
pub goal: ReflowGoal,
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,7 +420,8 @@ trait PrivateMatchMethods {
|
||||||
applicable_declarations_cache:
|
applicable_declarations_cache:
|
||||||
&mut ApplicableDeclarationsCache,
|
&mut ApplicableDeclarationsCache,
|
||||||
new_animations_sender: &Sender<Animation>,
|
new_animations_sender: &Sender<Animation>,
|
||||||
shareable: bool)
|
shareable: bool,
|
||||||
|
animate_properties: bool)
|
||||||
-> RestyleDamage;
|
-> RestyleDamage;
|
||||||
|
|
||||||
fn share_style_with_candidate_if_possible(&self,
|
fn share_style_with_candidate_if_possible(&self,
|
||||||
|
@ -438,8 +439,21 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
||||||
applicable_declarations_cache:
|
applicable_declarations_cache:
|
||||||
&mut ApplicableDeclarationsCache,
|
&mut ApplicableDeclarationsCache,
|
||||||
new_animations_sender: &Sender<Animation>,
|
new_animations_sender: &Sender<Animation>,
|
||||||
shareable: bool)
|
shareable: bool,
|
||||||
|
animate_properties: bool)
|
||||||
-> RestyleDamage {
|
-> RestyleDamage {
|
||||||
|
// Finish any transitions.
|
||||||
|
if animate_properties {
|
||||||
|
if let Some(ref mut style) = *style {
|
||||||
|
let this_opaque = self.opaque();
|
||||||
|
if let Some(ref animations) = layout_context.running_animations.get(&this_opaque) {
|
||||||
|
for animation in animations.iter() {
|
||||||
|
animation.property_animation.update(&mut *Arc::make_unique(style), 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut this_style;
|
let mut this_style;
|
||||||
let cacheable;
|
let cacheable;
|
||||||
match parent_style {
|
match parent_style {
|
||||||
|
@ -470,11 +484,8 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
||||||
|
|
||||||
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
|
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
|
||||||
// it did trigger a transition.
|
// it did trigger a transition.
|
||||||
match *style {
|
if animate_properties {
|
||||||
None => {
|
if let Some(ref style) = *style {
|
||||||
// This is a newly-created node; we've nothing to transition from!
|
|
||||||
}
|
|
||||||
Some(ref style) => {
|
|
||||||
animation::start_transitions_if_applicable(new_animations_sender,
|
animation::start_transitions_if_applicable(new_animations_sender,
|
||||||
self.opaque(),
|
self.opaque(),
|
||||||
&**style,
|
&**style,
|
||||||
|
@ -488,7 +499,8 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
||||||
|
|
||||||
// Cache the resolved style if it was cacheable.
|
// Cache the resolved style if it was cacheable.
|
||||||
if cacheable {
|
if cacheable {
|
||||||
applicable_declarations_cache.insert(applicable_declarations.to_vec(), this_style.clone());
|
applicable_declarations_cache.insert(applicable_declarations.to_vec(),
|
||||||
|
this_style.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write in the final style and return the damage done to our caller.
|
// Write in the final style and return the damage done to our caller.
|
||||||
|
@ -686,7 +698,8 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
&mut layout_data.shared_data.style,
|
&mut layout_data.shared_data.style,
|
||||||
applicable_declarations_cache,
|
applicable_declarations_cache,
|
||||||
new_animations_sender,
|
new_animations_sender,
|
||||||
applicable_declarations.normal_shareable);
|
applicable_declarations.normal_shareable,
|
||||||
|
true);
|
||||||
if applicable_declarations.before.len() > 0 {
|
if applicable_declarations.before.len() > 0 {
|
||||||
damage = damage | self.cascade_node_pseudo_element(
|
damage = damage | self.cascade_node_pseudo_element(
|
||||||
layout_context,
|
layout_context,
|
||||||
|
@ -695,6 +708,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
&mut layout_data.data.before_style,
|
&mut layout_data.data.before_style,
|
||||||
applicable_declarations_cache,
|
applicable_declarations_cache,
|
||||||
new_animations_sender,
|
new_animations_sender,
|
||||||
|
false,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
if applicable_declarations.after.len() > 0 {
|
if applicable_declarations.after.len() > 0 {
|
||||||
|
@ -705,6 +719,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
&mut layout_data.data.after_style,
|
&mut layout_data.data.after_style,
|
||||||
applicable_declarations_cache,
|
applicable_declarations_cache,
|
||||||
new_animations_sender,
|
new_animations_sender,
|
||||||
|
false,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
layout_data.data.restyle_damage = damage;
|
layout_data.data.restyle_damage = damage;
|
||||||
|
|
|
@ -138,7 +138,7 @@ pub struct LayoutTaskData {
|
||||||
pub resolved_style_response: Option<String>,
|
pub resolved_style_response: Option<String>,
|
||||||
|
|
||||||
/// The list of currently-running animations.
|
/// The list of currently-running animations.
|
||||||
pub running_animations: Vec<Animation>,
|
pub running_animations: Arc<HashMap<OpaqueNode,Vec<Animation>>>,
|
||||||
|
|
||||||
/// Receives newly-discovered animations.
|
/// Receives newly-discovered animations.
|
||||||
pub new_animations_receiver: Receiver<Animation>,
|
pub new_animations_receiver: Receiver<Animation>,
|
||||||
|
@ -381,7 +381,7 @@ impl LayoutTask {
|
||||||
content_boxes_response: Vec::new(),
|
content_boxes_response: Vec::new(),
|
||||||
client_rect_response: Rect::zero(),
|
client_rect_response: Rect::zero(),
|
||||||
resolved_style_response: None,
|
resolved_style_response: None,
|
||||||
running_animations: Vec::new(),
|
running_animations: Arc::new(HashMap::new()),
|
||||||
visible_rects: Arc::new(HashMap::with_hash_state(Default::default())),
|
visible_rects: Arc::new(HashMap::with_hash_state(Default::default())),
|
||||||
new_animations_receiver: new_animations_receiver,
|
new_animations_receiver: new_animations_receiver,
|
||||||
new_animations_sender: new_animations_sender,
|
new_animations_sender: new_animations_sender,
|
||||||
|
@ -423,6 +423,7 @@ impl LayoutTask {
|
||||||
generation: rw_data.generation,
|
generation: rw_data.generation,
|
||||||
new_animations_sender: rw_data.new_animations_sender.clone(),
|
new_animations_sender: rw_data.new_animations_sender.clone(),
|
||||||
goal: goal,
|
goal: goal,
|
||||||
|
running_animations: rw_data.running_animations.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1275,23 +1276,27 @@ impl LayoutTask {
|
||||||
animation::tick_all_animations(self, &mut rw_data)
|
animation::tick_all_animations(self, &mut rw_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick_animation<'a>(&'a self, animation: &Animation, rw_data: &mut LayoutTaskData) {
|
pub fn tick_animations<'a>(&'a self, rw_data: &mut LayoutTaskData) {
|
||||||
let reflow_info = Reflow {
|
let reflow_info = Reflow {
|
||||||
goal: ReflowGoal::ForDisplay,
|
goal: ReflowGoal::ForDisplay,
|
||||||
page_clip_rect: MAX_RECT,
|
page_clip_rect: MAX_RECT,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Perform an abbreviated style recalc that operates without access to the DOM.
|
|
||||||
let mut layout_context = self.build_shared_layout_context(&*rw_data,
|
let mut layout_context = self.build_shared_layout_context(&*rw_data,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
&self.url,
|
&self.url,
|
||||||
reflow_info.goal);
|
reflow_info.goal);
|
||||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
|
||||||
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
{
|
||||||
self.profiler_metadata(),
|
// Perform an abbreviated style recalc that operates without access to the DOM.
|
||||||
self.time_profiler_chan.clone(),
|
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||||
|| animation::recalc_style_for_animation(root_flow.deref_mut(), &animation));
|
let animations = &*rw_data.running_animations;
|
||||||
|
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||||
|
self.profiler_metadata(),
|
||||||
|
self.time_profiler_chan.clone(),
|
||||||
|
|| animation::recalc_style_for_animations(root_flow.deref_mut(), animations));
|
||||||
|
}
|
||||||
|
|
||||||
self.perform_post_style_recalc_layout_passes(&reflow_info,
|
self.perform_post_style_recalc_layout_passes(&reflow_info,
|
||||||
&mut *rw_data,
|
&mut *rw_data,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue