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

Have animations more closely match the HTML spec #26407

Merged
merged 2 commits into from May 6, 2020
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Prev

Start having animations conform to the HTML spec

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
commit 3a74013abcec241d67d2685e52a031409dc59dd4
@@ -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};

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

@@ -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};
@@ -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;
@@ -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,

@@ -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(),
@@ -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,
@@ -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,
},
@@ -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 =
@@ -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 {
@@ -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;
@@ -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;
@@ -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,

@@ -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,
@@ -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,
@@ -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,
},
@@ -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 =
@@ -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 = {
@@ -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;
}
}
@@ -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;
@@ -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)]
@@ -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())
},
}
}

@@ -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 {
@@ -173,8 +173,7 @@ pub enum ReflowReason {
IFrameLoadEvent,
MissingExplicitReflow,
ElementStateChanged,
TickAnimations,
AdvanceClock(i32),
PendingReflow,
}

#[dom_struct]
@@ -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
@@ -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 {
@@ -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
@@ -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 {
@@ -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 {
@@ -47,6 +47,7 @@ extern crate servo_atoms;
#[macro_use]
extern crate style;

mod animation_timeline;
#[warn(deprecated)]
#[macro_use]
mod task;
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.