Skip to content

Commit

Permalink
Start having animations conform to the HTML spec
Browse files Browse the repository at this point in the history
This is a small step toward fixing #19242. The main idea is that the
clock for animations should advance as the event loop ticks. We
accomplish this by moving the clock from layout and naming it the
"animation timeline" which is the spec language. This should fix
flakiness with animations and transitions tests where a reflow could
move animations forward while script was running.

This change also starts to break out transition and animation events
into their own data structure, because it's quite likely that the next
step in fixing #19242 is to no longer send these events through a
channel.
  • Loading branch information
mrobinson committed May 5, 2020
1 parent b585ce5 commit 3a74013
Show file tree
Hide file tree
Showing 45 changed files with 160 additions and 2,095 deletions.
18 changes: 10 additions & 8 deletions components/layout/animation.rs
Expand Up @@ -13,7 +13,7 @@ use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg,
TransitionOrAnimationEventType,
TransitionOrAnimationEvent, TransitionOrAnimationEventType,
};
use style::animation::{Animation, ElementAnimationState};

Expand Down Expand Up @@ -120,13 +120,15 @@ fn update_animation_state(
};

script_channel
.send(ConstellationControlMsg::TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: node.to_untrusted_node_address(),
property_or_animation_name,
elapsed_time,
})
.send(ConstellationControlMsg::TransitionOrAnimationEvent(
TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: node.to_untrusted_node_address(),
property_or_animation_name,
elapsed_time,
},
))
.unwrap()
};

Expand Down
23 changes: 4 additions & 19 deletions components/layout_thread/lib.rs
Expand Up @@ -87,7 +87,6 @@ use script_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData};
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_config::opts;
use servo_config::pref;
use servo_url::{ImmutableOrigin, ServoUrl};
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
Expand Down Expand Up @@ -117,7 +116,6 @@ use style::stylesheets::{
};
use style::stylist::Stylist;
use style::thread_state::{self, ThreadState};
use style::timer::Timer;
use style::traversal::DomTraversal;
use style::traversal_flags::TraversalFlags;
use style_traits::CSSPixel;
Expand Down Expand Up @@ -220,10 +218,6 @@ pub struct LayoutThread {
/// Webrender document.
webrender_document: webrender_api::DocumentId,

/// The timer object to control the timing of the animations. This should
/// only be a test-mode timer during testing for animations.
timer: Timer,

/// Paint time metrics.
paint_time_metrics: PaintTimeMetrics,

Expand Down Expand Up @@ -583,11 +577,6 @@ impl LayoutThread {
inner_window_dimensions_response: None,
})),
webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())),
timer: if pref!(layout.animations.test.enabled) {
Timer::test_mode()
} else {
Timer::new()
},
paint_time_metrics: paint_time_metrics,
layout_query_waiting_time: Histogram::new(),
last_iframe_sizes: Default::default(),
Expand Down Expand Up @@ -623,6 +612,7 @@ impl LayoutThread {
guards: StylesheetGuards<'a>,
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
) -> LayoutContext<'a> {
LayoutContext {
id: self.id,
Expand All @@ -634,7 +624,7 @@ impl LayoutThread {
visited_styles_enabled: false,
animation_states: self.animation_states.clone(),
registered_speculative_painters: &self.registered_painters,
current_time_for_animations: self.timer.seconds(),
current_time_for_animations: animation_timeline_value,
traversal_flags: TraversalFlags::empty(),
snapshot_map: snapshot_map,
},
Expand Down Expand Up @@ -1306,12 +1296,6 @@ impl LayoutThread {
let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio);
let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards);

if pref!(layout.animations.test.enabled) {
if let Some(delta) = data.advance_clock_delta {
self.timer.increment(delta as f64 / 1000.0);
}
}

self.stylist
.force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change);
self.viewport_size =
Expand Down Expand Up @@ -1429,7 +1413,8 @@ impl LayoutThread {
self.stylist.flush(&guards, Some(element), Some(&map));

// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(guards.clone(), &map, origin);
let mut layout_context =
self.build_layout_context(guards.clone(), &map, origin, data.animation_timeline_value);

let pool;
let (thread_pool, num_threads) = if self.parallel_flag {
Expand Down
23 changes: 4 additions & 19 deletions components/layout_thread_2020/lib.rs
Expand Up @@ -71,7 +71,6 @@ use script_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData};
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_config::opts;
use servo_config::pref;
use servo_url::{ImmutableOrigin, ServoUrl};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
Expand All @@ -98,7 +97,6 @@ use style::stylesheets::{
};
use style::stylist::Stylist;
use style::thread_state::{self, ThreadState};
use style::timer::Timer;
use style::traversal::DomTraversal;
use style::traversal_flags::TraversalFlags;
use style_traits::CSSPixel;
Expand Down Expand Up @@ -195,10 +193,6 @@ pub struct LayoutThread {
/// Webrender document.
webrender_document: webrender_api::DocumentId,

/// The timer object to control the timing of the animations. This should
/// only be a test-mode timer during testing for animations.
timer: Timer,

/// Paint time metrics.
paint_time_metrics: PaintTimeMetrics,

Expand Down Expand Up @@ -545,11 +539,6 @@ impl LayoutThread {
inner_window_dimensions_response: None,
})),
webrender_image_cache: Default::default(),
timer: if pref!(layout.animations.test.enabled) {
Timer::test_mode()
} else {
Timer::new()
},
paint_time_metrics: paint_time_metrics,
busy,
load_webfonts_synchronously,
Expand Down Expand Up @@ -582,6 +571,7 @@ impl LayoutThread {
guards: StylesheetGuards<'a>,
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
) -> LayoutContext<'a> {
LayoutContext {
id: self.id,
Expand All @@ -593,7 +583,7 @@ impl LayoutThread {
visited_styles_enabled: false,
animation_states: Default::default(),
registered_speculative_painters: &self.registered_painters,
timer: self.timer.clone(),
current_time_for_animations: animation_timeline_value,
traversal_flags: TraversalFlags::empty(),
snapshot_map: snapshot_map,
},
Expand Down Expand Up @@ -977,12 +967,6 @@ impl LayoutThread {
let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio);
let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards);

if pref!(layout.animations.test.enabled) {
if let Some(delta) = data.advance_clock_delta {
self.timer.increment(delta as f64 / 1000.0);
}
}

self.stylist
.force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change);
self.viewport_size =
Expand Down Expand Up @@ -1082,7 +1066,8 @@ impl LayoutThread {
self.stylist.flush(&guards, Some(element), Some(&map));

// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(guards.clone(), &map, origin);
let mut layout_context =
self.build_layout_context(guards.clone(), &map, origin, data.animation_timeline_value);

let traversal = RecalcStyle::new(layout_context);
let token = {
Expand Down
49 changes: 49 additions & 0 deletions components/script/animation_timeline.rs
@@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#![deny(missing_docs)]

//! A timeline module, used to specify an `AnimationTimeline` which determines
//! the time used for synchronizing animations in the script thread.

use time;

/// A `AnimationTimeline` which is used to synchronize animations during the script
/// event loop.
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
pub struct AnimationTimeline {
current_value: f64,
}

impl AnimationTimeline {
/// Creates a new "normal" timeline, i.e., a "Current" mode timer.
#[inline]
pub fn new() -> Self {
Self {
current_value: time::precise_time_s(),
}
}

/// Creates a new "test mode" timeline, with initial time 0.
#[inline]
pub fn new_for_testing() -> Self {
Self { current_value: 0. }
}

/// Returns the current value of the timeline in seconds.
pub fn current_value(&self) -> f64 {
self.current_value
}

/// Updates the value of the `AnimationTimeline` to the current clock time.
pub fn update(&mut self) {
self.current_value = time::precise_time_s();
}

/// Increments the current value of the timeline by a specific number of seconds.
/// This is used for testing.
pub fn advance_specific(&mut self, by: f64) {
self.current_value += by;
}
}
21 changes: 21 additions & 0 deletions components/script/dom/document.rs
Expand Up @@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::animation_timeline::AnimationTimeline;
use crate::document_loader::{DocumentLoader, LoadType};
use crate::dom::attr::Attr;
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
Expand Down Expand Up @@ -380,6 +381,9 @@ pub struct Document {
csp_list: DomRefCell<Option<CspList>>,
/// https://w3c.github.io/slection-api/#dfn-selection
selection: MutNullableDom<Selection>,
/// A timeline for animations which is used for synchronizing animations.
/// https://drafts.csswg.org/web-animations/#timeline
animation_timeline: DomRefCell<AnimationTimeline>,
}

#[derive(JSTraceable, MallocSizeOf)]
Expand Down Expand Up @@ -2904,6 +2908,11 @@ impl Document {
dirty_webgl_contexts: DomRefCell::new(HashMap::new()),
csp_list: DomRefCell::new(None),
selection: MutNullableDom::new(None),
animation_timeline: if pref!(layout.animations.test.enabled) {
DomRefCell::new(AnimationTimeline::new_for_testing())
} else {
DomRefCell::new(AnimationTimeline::new())
},
}
}

Expand Down Expand Up @@ -3605,6 +3614,18 @@ impl Document {
})
.collect()
}

pub fn advance_animation_timeline_for_testing(&self, delta: f64) {
self.animation_timeline.borrow_mut().advance_specific(delta);
}

pub fn update_animation_timeline(&self) {
self.animation_timeline.borrow_mut().update();
}

pub fn current_animation_timeline_value(&self) -> f64 {
self.animation_timeline.borrow().current_value()
}
}

impl Element {
Expand Down
53 changes: 9 additions & 44 deletions components/script/dom/window.rs
Expand Up @@ -173,8 +173,7 @@ pub enum ReflowReason {
IFrameLoadEvent,
MissingExplicitReflow,
ElementStateChanged,
TickAnimations,
AdvanceClock(i32),
PendingReflow,
}

#[dom_struct]
Expand Down Expand Up @@ -1550,12 +1549,9 @@ impl Window {
/// layout animation clock.
pub fn advance_animation_clock(&self, delta: i32) {
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
ScriptThread::restyle_animating_nodes_for_advancing_clock(&pipeline_id);
self.force_reflow(
ReflowGoal::TickAnimations,
ReflowReason::AdvanceClock(delta),
None,
);
self.Document()
.advance_animation_timeline_for_testing(delta as f64 / 1000.);
ScriptThread::handle_tick_all_animations_for_testing(pipeline_id);
}

/// Reflows the page unconditionally if possible and not suppressed. This
Expand Down Expand Up @@ -1632,11 +1628,6 @@ impl Window {
document.flush_dirty_canvases();
}

let advance_clock_delta = match reason {
ReflowReason::AdvanceClock(delta) => Some(delta),
_ => None,
};

// Send new document and relevant styles to layout.
let needs_display = reflow_goal.needs_display();
let reflow = ScriptReflow {
Expand All @@ -1651,7 +1642,7 @@ impl Window {
script_join_chan: join_chan,
dom_count: document.dom_count(),
pending_restyles: document.drain_pending_restyles(),
advance_clock_delta,
animation_timeline_value: document.current_animation_timeline_value(),
};

self.layout_chan
Expand Down Expand Up @@ -2453,8 +2444,7 @@ fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f
}

fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &ReflowReason) {
let mut debug_msg = format!("**** pipeline={}", id);
debug_msg.push_str(match *reflow_goal {
let goal_string = match *reflow_goal {
ReflowGoal::Full => "\tFull",
ReflowGoal::TickAnimations => "\tTickAnimations",
ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg {
Expand All @@ -2471,34 +2461,9 @@ fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &Reflow
&QueryMsg::ElementInnerTextQuery(_) => "\tElementInnerTextQuery",
&QueryMsg::InnerWindowDimensionsQuery(_) => "\tInnerWindowDimensionsQuery",
},
});

debug_msg.push_str(match *reason {
ReflowReason::CachedPageNeededReflow => "\tCachedPageNeededReflow",
ReflowReason::RefreshTick => "\tRefreshTick",
ReflowReason::FirstLoad => "\tFirstLoad",
ReflowReason::KeyEvent => "\tKeyEvent",
ReflowReason::MouseEvent => "\tMouseEvent",
ReflowReason::Query => "\tQuery",
ReflowReason::Timer => "\tTimer",
ReflowReason::Viewport => "\tViewport",
ReflowReason::WindowResize => "\tWindowResize",
ReflowReason::DOMContentLoaded => "\tDOMContentLoaded",
ReflowReason::DocumentLoaded => "\tDocumentLoaded",
ReflowReason::StylesheetLoaded => "\tStylesheetLoaded",
ReflowReason::ImageLoaded => "\tImageLoaded",
ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame",
ReflowReason::WebFontLoaded => "\tWebFontLoaded",
ReflowReason::WorkletLoaded => "\tWorkletLoaded",
ReflowReason::FramedContentChanged => "\tFramedContentChanged",
ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent",
ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow",
ReflowReason::ElementStateChanged => "\tElementStateChanged",
ReflowReason::TickAnimations => "\tTickAnimations",
ReflowReason::AdvanceClock(..) => "\tAdvanceClock",
});

println!("{}", debug_msg);
};

println!("**** pipeline={}\t{}\t{:?}", id, goal_string, reason);
}

impl Window {
Expand Down
1 change: 1 addition & 0 deletions components/script/lib.rs
Expand Up @@ -47,6 +47,7 @@ extern crate servo_atoms;
#[macro_use]
extern crate style;

mod animation_timeline;
#[warn(deprecated)]
#[macro_use]
mod task;
Expand Down

0 comments on commit 3a74013

Please sign in to comment.