Auto merge of #26407 - mrobinson:animation-restyle-model, r=jdm

Have animations more closely match the HTML spec

These two commits do two major things:

**Have animations ticks trigger a restyle**: This corrects synchronization issues with animations,
where the values used in layout are out of sync with what is returned by `getComputedStyle`.

**Tick the animation timer in script according to spec**: This greatly reduces the flakiness of
animation and transitions tests.

Fixes #13865.

<!-- Please describe your changes on the following line: -->

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #13865

<!-- Either: -->
- [x] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2020-05-06 04:12:31 -04:00 committed by GitHub
commit b290ad95c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 1389 additions and 3701 deletions

View file

@ -1042,20 +1042,22 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
let animation_callbacks_running = self
.pipeline_details(pipeline_id)
.animation_callbacks_running;
if animation_callbacks_running {
let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Script);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending tick to constellation failed ({:?}).", e);
}
let animations_running = self.pipeline_details(pipeline_id).animations_running;
if !animation_callbacks_running && !animations_running {
return;
}
// We may need to tick animations in layout. (See #12749.)
let animations_running = self.pipeline_details(pipeline_id).animations_running;
let mut tick_type = AnimationTickType::empty();
if animations_running {
let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Layout);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending tick to constellation failed ({:?}).", e);
}
tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
}
if animation_callbacks_running {
tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME);
}
let msg = ConstellationMsg::TickAnimation(pipeline_id, tick_type);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending tick to constellation failed ({:?}).", e);
}
}

View file

@ -3458,27 +3458,13 @@ where
}
fn handle_tick_animation(&mut self, pipeline_id: PipelineId, tick_type: AnimationTickType) {
let result = match tick_type {
AnimationTickType::Script => {
let msg = ConstellationControlMsg::TickAllAnimations(pipeline_id);
match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.event_loop.send(msg),
None => {
return warn!("Pipeline {:?} got script tick after closure.", pipeline_id);
},
}
},
AnimationTickType::Layout => match self.pipelines.get(&pipeline_id) {
Some(pipeline) => {
let msg = LayoutControlMsg::TickAnimations(pipeline.load_data.url.origin());
pipeline.layout_chan.send(msg)
},
None => {
return warn!("Pipeline {:?} got layout tick after closure.", pipeline_id);
},
},
let pipeline = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline,
None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id),
};
if let Err(e) = result {
let message = ConstellationControlMsg::TickAllAnimations(pipeline_id, tick_type);
if let Err(e) = pipeline.event_loop.send(message) {
self.handle_send_error(pipeline_id, e);
}
}

View file

@ -4,7 +4,6 @@
//! CSS transitions and animations.
use crate::context::LayoutContext;
use crate::display_list::items::OpaqueNode;
use crate::flow::{Flow, GetBaseFlow};
use crate::opaque_node::OpaqueNodeMethods;
@ -14,56 +13,29 @@ use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg,
TransitionOrAnimationEventType,
TransitionOrAnimationEvent, TransitionOrAnimationEventType,
};
use servo_arc::Arc;
use style::animation::{update_style_for_animation, Animation, ElementAnimationState};
use style::dom::TElement;
use style::font_metrics::ServoMetricsProvider;
use style::selector_parser::RestyleDamage;
use style::timer::Timer;
use style::animation::{Animation, ElementAnimationState};
/// Collect newly animating nodes, which is used by the script process during
/// forced, synchronous reflows to root DOM nodes for the duration of their
/// animations or transitions.
pub fn collect_newly_animating_nodes(
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
mut out: Option<&mut Vec<UntrustedNodeAddress>>,
) {
// This extends the output vector with an iterator that contains a copy of the node
// address for every new animation. This is a bit goofy, but the script thread
// currently stores a rooted node for every property that is transitioning.
if let Some(ref mut out) = out {
out.extend(animation_states.iter().flat_map(|(node, state)| {
std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len())
}));
}
}
/// Processes any new animations that were discovered after style recalculation. Also
/// finish any animations that have completed, inserting them into `finished_animations`.
pub fn update_animation_states(
/// Processes any new animations that were discovered after style recalculation and
/// remove animations for any disconnected nodes. Send messages that trigger events
/// for any events that changed state.
pub fn do_post_style_animations_update(
constellation_chan: &IpcSender<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>,
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>,
invalid_nodes: FxHashSet<OpaqueNode>,
pipeline_id: PipelineId,
timer: &Timer,
now: f64,
out: &mut Vec<UntrustedNodeAddress>,
root_flow: &mut dyn Flow,
) {
let had_running_animations = animation_states
.values()
.any(|state| !state.running_animations.is_empty());
// Cancel all animations on any invalid nodes. These entries will later
// be removed from the list of states, because their states will become
// empty.
for node in &invalid_nodes {
if let Some(mut state) = animation_states.remove(node) {
state.cancel_all_animations();
}
}
cancel_animations_for_disconnected_nodes(animation_states, root_flow);
collect_newly_animating_nodes(animation_states, out);
let now = timer.seconds();
let mut have_running_animations = false;
for (node, animation_state) in animation_states.iter_mut() {
update_animation_state(script_chan, animation_state, pipeline_id, now, *node);
@ -89,7 +61,50 @@ pub fn update_animation_states(
.unwrap();
}
pub fn update_animation_state(
/// Collect newly animating nodes, which is used by the script process during
/// forced, synchronous reflows to root DOM nodes for the duration of their
/// animations or transitions.
pub fn collect_newly_animating_nodes(
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
out: &mut Vec<UntrustedNodeAddress>,
) {
// This extends the output vector with an iterator that contains a copy of the node
// address for every new animation. This is a bit goofy, but the script thread
// currently stores a rooted node for every property that is transitioning.
out.extend(animation_states.iter().flat_map(|(node, state)| {
std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len())
}));
}
/// Cancel animations for any nodes which have been removed from the DOM or are display:none.
/// We detect this by looking for nodes that are used in the flow tree.
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
/// This also doesn't yet handles nodes that have been reparented.
pub fn cancel_animations_for_disconnected_nodes(
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>,
root_flow: &mut dyn Flow,
) {
// Assume all nodes have been removed until proven otherwise.
let mut invalid_nodes: FxHashSet<OpaqueNode> = animation_states.keys().cloned().collect();
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<OpaqueNode>) {
flow.mutate_fragments(&mut |fragment| {
invalid_nodes.remove(&fragment.node);
});
for kid in flow.mut_base().children.iter_mut() {
traverse_flow(kid, invalid_nodes)
}
}
traverse_flow(root_flow, &mut invalid_nodes);
// Cancel animations for any nodes that are no longer in the flow tree.
for node in &invalid_nodes {
if let Some(state) = animation_states.get_mut(node) {
state.cancel_all_animations();
}
}
}
fn update_animation_state(
script_channel: &IpcSender<ConstellationControlMsg>,
animation_state: &mut ElementAnimationState,
pipeline_id: PipelineId,
@ -105,13 +120,15 @@ pub 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()
};
@ -122,36 +139,28 @@ pub fn update_animation_state(
/// Walk through the list of running animations and remove all of the ones that
/// have ended.
pub fn handle_running_animations(
fn handle_running_animations(
animation_state: &mut ElementAnimationState,
now: f64,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
) {
if animation_state.running_animations.is_empty() {
return;
}
let mut running_animations =
std::mem::replace(&mut animation_state.running_animations, Vec::new());
for mut running_animation in running_animations.drain(..) {
let still_running = match running_animation {
Animation::Transition(_, started_at, ref property_animation) => {
now < started_at + (property_animation.duration)
},
Animation::Keyframes(_, _, _, ref mut state) => {
// This animation is still running, or we need to keep
// iterating.
now < state.started_at + state.duration || state.tick()
},
};
for running_animation in running_animations.drain(..) {
// If the animation is still running, add it back to the list of running animations.
if still_running {
if !running_animation.has_ended(now) {
animation_state.running_animations.push(running_animation);
} else {
debug!("Finishing transition: {:?}", running_animation);
let (event_type, elapsed_time) = match running_animation {
Animation::Transition(_, _, ref property_animation) => (
TransitionOrAnimationEventType::TransitionEnd,
property_animation.duration,
),
Animation::Keyframes(_, _, _, ref mut state) => (
Animation::Keyframes(_, _, _, ref state) => (
TransitionOrAnimationEventType::AnimationEnd,
state.active_duration(),
),
@ -166,7 +175,7 @@ pub fn handle_running_animations(
/// Send events for cancelled animations. Currently this only handles cancelled
/// transitions, but eventually this should handle cancelled CSS animations as
/// well.
pub fn handle_cancelled_animations(
fn handle_cancelled_animations(
animation_state: &mut ElementAnimationState,
now: f64,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
@ -188,7 +197,7 @@ pub fn handle_cancelled_animations(
}
}
pub fn handle_new_animations(
fn handle_new_animations(
animation_state: &mut ElementAnimationState,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64),
) {
@ -208,55 +217,3 @@ pub fn handle_new_animations(
animation_state.running_animations.push(animation);
}
}
/// Recalculates style for a set of animations. This does *not* run with the DOM
/// lock held. Returns a set of nodes associated with animations that are no longer
/// valid.
pub fn recalc_style_for_animations<E>(
context: &LayoutContext,
flow: &mut dyn Flow,
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
) -> FxHashSet<OpaqueNode>
where
E: TElement,
{
let mut invalid_nodes = animation_states.keys().cloned().collect();
do_recalc_style_for_animations::<E>(context, flow, animation_states, &mut invalid_nodes);
invalid_nodes
}
fn do_recalc_style_for_animations<E>(
context: &LayoutContext,
flow: &mut dyn Flow,
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>,
invalid_nodes: &mut FxHashSet<OpaqueNode>,
) where
E: TElement,
{
let mut damage = RestyleDamage::empty();
flow.mutate_fragments(&mut |fragment| {
let animations = match animation_states.get(&fragment.node) {
Some(state) => &state.running_animations,
None => return,
};
invalid_nodes.remove(&fragment.node);
for animation in animations.iter() {
let old_style = fragment.style.clone();
update_style_for_animation::<E>(
&context.style_context,
animation,
Arc::make_mut(&mut fragment.style),
&ServoMetricsProvider,
);
let difference = RestyleDamage::compute_style_difference(&old_style, &fragment.style);
damage |= difference.damage;
}
});
let base = flow.mut_base();
base.restyle_damage.insert(damage);
for kid in base.children.iter_mut() {
do_recalc_style_for_animations::<E>(context, kid, animation_states, invalid_nodes)
}
}

View file

@ -11,8 +11,9 @@ use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context::FontContext;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use msg::constellation_msg::PipelineId;
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageCacheResult};
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
};
use parking_lot::RwLock;
use script_layout_interface::{PendingImage, PendingImageState};
use script_traits::Painter;
@ -83,20 +84,16 @@ pub struct LayoutContext<'a> {
pub registered_painters: &'a dyn RegisteredPainters,
/// A list of in-progress image loads to be shared with the script thread.
/// A None value means that this layout was not initiated by the script thread.
pub pending_images: Option<Mutex<Vec<PendingImage>>>,
pub pending_images: Mutex<Vec<PendingImage>>,
/// A list of nodes that have just initiated a CSS transition or animation.
/// A None value means that this layout was not initiated by the script thread.
pub newly_animating_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
pub newly_animating_nodes: Mutex<Vec<UntrustedNodeAddress>>,
}
impl<'a> Drop for LayoutContext<'a> {
fn drop(&mut self) {
if !thread::panicking() {
if let Some(ref pending_images) = self.pending_images {
assert!(pending_images.lock().unwrap().is_empty());
}
assert!(self.pending_images.lock().unwrap().is_empty());
}
}
}
@ -113,22 +110,12 @@ impl<'a> LayoutContext<'a> {
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<ImageOrMetadataAvailable> {
//XXXjdm For cases where we do not request an image, we still need to
// ensure the node gets another script-initiated reflow or it
// won't be requested at all.
let can_request = if self.pending_images.is_some() {
CanRequestImages::Yes
} else {
CanRequestImages::No
};
// Check for available image or start tracking.
let cache_result = self.image_cache.get_cached_image_status(
url.clone(),
self.origin.clone(),
None,
use_placeholder,
can_request,
);
match cache_result {
@ -136,18 +123,13 @@ impl<'a> LayoutContext<'a> {
// Image has been requested, is still pending. Return no image for this paint loop.
// When the image loads it will trigger a reflow and/or repaint.
ImageCacheResult::Pending(id) => {
//XXXjdm if self.pending_images is not available, we should make sure that
// this node gets marked dirty again so it gets a script-initiated
// reflow that deals with this properly.
if let Some(ref pending_images) = self.pending_images {
let image = PendingImage {
state: PendingImageState::PendingResponse,
node: node.to_untrusted_node_address(),
id,
origin: self.origin.clone(),
};
pending_images.lock().unwrap().push(image);
}
let image = PendingImage {
state: PendingImageState::PendingResponse,
node: node.to_untrusted_node_address(),
id,
origin: self.origin.clone(),
};
self.pending_images.lock().unwrap().push(image);
None
},
// Not yet requested - request image or metadata from the cache
@ -158,12 +140,7 @@ impl<'a> LayoutContext<'a> {
id,
origin: self.origin.clone(),
};
self.pending_images
.as_ref()
.unwrap()
.lock()
.unwrap()
.push(image);
self.pending_images.lock().unwrap().push(image);
None
},
// Image failed to load, so just return nothing

View file

@ -8,8 +8,9 @@ use fnv::FnvHashMap;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context::FontContext;
use msg::constellation_msg::PipelineId;
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageCacheResult};
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
};
use parking_lot::RwLock;
use script_layout_interface::{PendingImage, PendingImageState};
use servo_url::{ImmutableOrigin, ServoUrl};
@ -33,8 +34,7 @@ pub struct LayoutContext<'a> {
pub image_cache: Arc<dyn ImageCache>,
/// A list of in-progress image loads to be shared with the script thread.
/// A None value means that this layout was not initiated by the script thread.
pub pending_images: Option<Mutex<Vec<PendingImage>>>,
pub pending_images: Mutex<Vec<PendingImage>>,
pub webrender_image_cache:
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
@ -43,9 +43,7 @@ pub struct LayoutContext<'a> {
impl<'a> Drop for LayoutContext<'a> {
fn drop(&mut self) {
if !std::thread::panicking() {
if let Some(ref pending_images) = self.pending_images {
assert!(pending_images.lock().unwrap().is_empty());
}
assert!(self.pending_images.lock().unwrap().is_empty());
}
}
}
@ -62,22 +60,12 @@ impl<'a> LayoutContext<'a> {
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<ImageOrMetadataAvailable> {
//XXXjdm For cases where we do not request an image, we still need to
// ensure the node gets another script-initiated reflow or it
// won't be requested at all.
let can_request = if self.pending_images.is_some() {
CanRequestImages::Yes
} else {
CanRequestImages::No
};
// Check for available image or start tracking.
let cache_result = self.image_cache.get_cached_image_status(
url.clone(),
self.origin.clone(),
None,
use_placeholder,
can_request,
);
match cache_result {
@ -85,18 +73,13 @@ impl<'a> LayoutContext<'a> {
// Image has been requested, is still pending. Return no image for this paint loop.
// When the image loads it will trigger a reflow and/or repaint.
ImageCacheResult::Pending(id) => {
//XXXjdm if self.pending_images is not available, we should make sure that
// this node gets marked dirty again so it gets a script-initiated
// reflow that deals with this properly.
if let Some(ref pending_images) = self.pending_images {
let image = PendingImage {
state: PendingImageState::PendingResponse,
node: node.to_untrusted_node_address(),
id,
origin: self.origin.clone(),
};
pending_images.lock().unwrap().push(image);
}
let image = PendingImage {
state: PendingImageState::PendingResponse,
node: node.to_untrusted_node_address(),
id,
origin: self.origin.clone(),
};
self.pending_images.lock().unwrap().push(image);
None
},
// Not yet requested - request image or metadata from the cache
@ -107,12 +90,7 @@ impl<'a> LayoutContext<'a> {
id,
origin: self.origin.clone(),
};
self.pending_images
.as_ref()
.unwrap()
.lock()
.unwrap()
.push(image);
self.pending_images.lock().unwrap().push(image);
None
},
// Image failed to load, so just return nothing

View file

@ -29,7 +29,7 @@ use crossbeam_channel::{Receiver, Sender};
use embedder_traits::resources::{self, Resource};
use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D};
use fnv::FnvHashMap;
use fxhash::{FxHashMap, FxHashSet};
use fxhash::FxHashMap;
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
@ -87,8 +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_geometry::MaxRect;
use servo_url::{ImmutableOrigin, ServoUrl};
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
@ -118,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;
@ -221,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,
@ -584,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(),
@ -622,9 +610,9 @@ impl LayoutThread {
fn build_layout_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
script_initiated_layout: bool,
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
) -> LayoutContext<'a> {
LayoutContext {
id: self.id,
@ -636,23 +624,15 @@ impl LayoutThread {
visited_styles_enabled: false,
animation_states: self.animation_states.clone(),
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,
},
image_cache: self.image_cache.clone(),
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: if script_initiated_layout {
Some(Mutex::new(vec![]))
} else {
None
},
newly_animating_nodes: if script_initiated_layout {
Some(Mutex::new(vec![]))
} else {
None
},
pending_images: Mutex::new(vec![]),
newly_animating_nodes: Mutex::new(vec![]),
registered_painters: &self.registered_painters,
}
}
@ -664,8 +644,6 @@ impl LayoutThread {
Msg::SetQuirksMode(..) => LayoutHangAnnotation::SetQuirksMode,
Msg::Reflow(..) => LayoutHangAnnotation::Reflow,
Msg::GetRPC(..) => LayoutHangAnnotation::GetRPC,
Msg::TickAnimations(..) => LayoutHangAnnotation::TickAnimations,
Msg::AdvanceClockMs(..) => LayoutHangAnnotation::AdvanceClockMs,
Msg::CollectReports(..) => LayoutHangAnnotation::CollectReports,
Msg::PrepareToExit(..) => LayoutHangAnnotation::PrepareToExit,
Msg::ExitNow => LayoutHangAnnotation::ExitNow,
@ -712,9 +690,6 @@ impl LayoutThread {
Msg::SetScrollStates(new_scroll_states),
possibly_locked_rw_data,
),
Request::FromPipeline(LayoutControlMsg::TickAnimations(origin)) => {
self.handle_request_helper(Msg::TickAnimations(origin), possibly_locked_rw_data)
},
Request::FromPipeline(LayoutControlMsg::GetCurrentEpoch(sender)) => {
self.handle_request_helper(Msg::GetCurrentEpoch(sender), possibly_locked_rw_data)
},
@ -786,9 +761,6 @@ impl LayoutThread {
|| self.handle_reflow(&mut data, possibly_locked_rw_data),
);
},
Msg::TickAnimations(origin) => {
self.tick_all_animations(possibly_locked_rw_data, origin)
},
Msg::SetScrollStates(new_scroll_states) => {
self.set_scroll_states(new_scroll_states, possibly_locked_rw_data);
},
@ -813,9 +785,6 @@ impl LayoutThread {
let _rw_data = possibly_locked_rw_data.lock();
sender.send(self.epoch.get()).unwrap();
},
Msg::AdvanceClockMs(how_many, do_tick, origin) => {
self.handle_advance_clock_ms(how_many, possibly_locked_rw_data, do_tick, origin);
},
Msg::GetWebFontLoadState(sender) => {
let _rw_data = possibly_locked_rw_data.lock();
let outstanding_web_fonts = self.outstanding_web_fonts.load(Ordering::SeqCst);
@ -993,20 +962,6 @@ impl LayoutThread {
}
}
/// Advances the animation clock of the document.
fn handle_advance_clock_ms<'a, 'b>(
&mut self,
how_many_ms: i32,
possibly_locked_rw_data: &mut RwData<'a, 'b>,
tick_animations: bool,
origin: ImmutableOrigin,
) {
self.timer.increment(how_many_ms as f64 / 1000.0);
if tick_animations {
self.tick_all_animations(possibly_locked_rw_data, origin);
}
}
/// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
fn handle_set_quirks_mode<'a, 'b>(&mut self, quirks_mode: QuirksMode) {
self.stylist.set_quirks_mode(quirks_mode);
@ -1458,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(), true, &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 {
@ -1539,7 +1495,6 @@ impl LayoutThread {
Some(&document),
&mut rw_data,
&mut layout_context,
FxHashSet::default(),
);
}
@ -1559,17 +1514,10 @@ impl LayoutThread {
context: &mut LayoutContext,
reflow_result: &mut ReflowComplete,
) {
let pending_images = match context.pending_images {
Some(ref pending) => std::mem::replace(&mut *pending.lock().unwrap(), vec![]),
None => vec![],
};
reflow_result.pending_images = pending_images;
let newly_animating_nodes = match context.newly_animating_nodes {
Some(ref nodes) => std::mem::replace(&mut *nodes.lock().unwrap(), vec![]),
None => vec![],
};
reflow_result.newly_animating_nodes = newly_animating_nodes;
reflow_result.pending_images =
std::mem::replace(&mut *context.pending_images.lock().unwrap(), vec![]);
reflow_result.newly_animating_nodes =
std::mem::replace(&mut *context.newly_animating_nodes.lock().unwrap(), vec![]);
let mut root_flow = match self.root_flow.borrow().clone() {
Some(root_flow) => root_flow,
@ -1681,70 +1629,6 @@ impl LayoutThread {
rw_data.scroll_offsets = layout_scroll_states
}
fn tick_all_animations<'a, 'b>(
&mut self,
possibly_locked_rw_data: &mut RwData<'a, 'b>,
origin: ImmutableOrigin,
) {
let mut rw_data = possibly_locked_rw_data.lock();
self.tick_animations(&mut rw_data, origin);
}
fn tick_animations(&mut self, rw_data: &mut LayoutThreadData, origin: ImmutableOrigin) {
if self.relayout_event {
println!(
"**** pipeline={}\tForDisplay\tSpecial\tAnimationTick",
self.id
);
}
if let Some(mut root_flow) = self.root_flow.borrow().clone() {
let reflow_info = Reflow {
page_clip_rect: Rect::max_rect(),
};
// Unwrap here should not panic since self.root_flow is only ever set to Some(_)
// in handle_reflow() where self.document_shared_lock is as well.
let author_shared_lock = self.document_shared_lock.clone().unwrap();
let author_guard = author_shared_lock.read();
let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let snapshots = SnapshotMap::new();
let mut layout_context = self.build_layout_context(guards, false, &snapshots, origin);
let invalid_nodes = {
// Perform an abbreviated style recalc that operates without access to the DOM.
let animation_states = self.animation_states.read();
profile(
profile_time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
animation::recalc_style_for_animations::<ServoLayoutElement>(
&layout_context,
FlowRef::deref_mut(&mut root_flow),
&animation_states,
)
},
)
};
self.perform_post_style_recalc_layout_passes(
&mut root_flow,
&reflow_info,
&ReflowGoal::TickAnimations,
None,
&mut *rw_data,
&mut layout_context,
invalid_nodes,
);
assert!(layout_context.pending_images.is_none());
assert!(layout_context.newly_animating_nodes.is_none());
}
}
fn perform_post_style_recalc_layout_passes(
&self,
root_flow: &mut FlowRef,
@ -1753,23 +1637,17 @@ impl LayoutThread {
document: Option<&ServoLayoutDocument>,
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext,
invalid_nodes: FxHashSet<OpaqueNode>,
) {
{
let mut newly_animating_nodes = context
.newly_animating_nodes
.as_ref()
.map(|nodes| nodes.lock().unwrap());
let newly_animating_nodes = newly_animating_nodes.as_mut().map(|nodes| &mut **nodes);
let mut animation_states = self.animation_states.write();
animation::collect_newly_animating_nodes(&animation_states, newly_animating_nodes);
animation::update_animation_states(
let mut newly_animating_nodes = context.newly_animating_nodes.lock().unwrap();
animation::do_post_style_animations_update(
&self.constellation_chan,
&self.script_chan,
&mut *animation_states,
invalid_nodes,
&mut *(self.animation_states.write()),
self.id,
&self.timer,
context.style_context.current_time_for_animations,
&mut newly_animating_nodes,
FlowRef::deref_mut(root_flow),
);
}

View file

@ -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,
@ -580,9 +569,9 @@ impl LayoutThread {
fn build_layout_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
script_initiated_layout: bool,
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
) -> LayoutContext<'a> {
LayoutContext {
id: self.id,
@ -594,18 +583,14 @@ 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,
},
image_cache: self.image_cache.clone(),
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: if script_initiated_layout {
Some(Mutex::new(Vec::new()))
} else {
None
},
pending_images: Mutex::new(vec![]),
use_rayon: STYLE_THREAD_POOL.pool().is_some(),
}
}
@ -617,8 +602,6 @@ impl LayoutThread {
Msg::SetQuirksMode(..) => LayoutHangAnnotation::SetQuirksMode,
Msg::Reflow(..) => LayoutHangAnnotation::Reflow,
Msg::GetRPC(..) => LayoutHangAnnotation::GetRPC,
Msg::TickAnimations(..) => LayoutHangAnnotation::TickAnimations,
Msg::AdvanceClockMs(..) => LayoutHangAnnotation::AdvanceClockMs,
Msg::CollectReports(..) => LayoutHangAnnotation::CollectReports,
Msg::PrepareToExit(..) => LayoutHangAnnotation::PrepareToExit,
Msg::ExitNow => LayoutHangAnnotation::ExitNow,
@ -665,9 +648,6 @@ impl LayoutThread {
Msg::SetScrollStates(new_scroll_states),
possibly_locked_rw_data,
),
Request::FromPipeline(LayoutControlMsg::TickAnimations(origin)) => {
self.handle_request_helper(Msg::TickAnimations(origin), possibly_locked_rw_data)
},
Request::FromPipeline(LayoutControlMsg::GetCurrentEpoch(sender)) => {
self.handle_request_helper(Msg::GetCurrentEpoch(sender), possibly_locked_rw_data)
},
@ -739,7 +719,6 @@ impl LayoutThread {
|| self.handle_reflow(&mut data, possibly_locked_rw_data),
);
},
Msg::TickAnimations(origin) => self.tick_all_animations(origin),
Msg::SetScrollStates(new_scroll_states) => {
self.set_scroll_states(new_scroll_states, possibly_locked_rw_data);
},
@ -764,9 +743,6 @@ impl LayoutThread {
let _rw_data = possibly_locked_rw_data.lock();
sender.send(self.epoch.get()).unwrap();
},
Msg::AdvanceClockMs(how_many, do_tick, origin) => {
self.handle_advance_clock_ms(how_many, do_tick, origin);
},
Msg::GetWebFontLoadState(sender) => {
let _rw_data = possibly_locked_rw_data.lock();
let outstanding_web_fonts = self.outstanding_web_fonts.load(Ordering::SeqCst);
@ -900,19 +876,6 @@ impl LayoutThread {
}
}
/// Advances the animation clock of the document.
fn handle_advance_clock_ms<'a, 'b>(
&mut self,
how_many_ms: i32,
tick_animations: bool,
origin: ImmutableOrigin,
) {
self.timer.increment(how_many_ms as f64 / 1000.0);
if tick_animations {
self.tick_all_animations(origin);
}
}
/// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
fn handle_set_quirks_mode<'a, 'b>(&mut self, quirks_mode: QuirksMode) {
self.stylist.set_quirks_mode(quirks_mode);
@ -1103,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(), true, &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 = {
@ -1195,11 +1159,9 @@ impl LayoutThread {
context: &mut LayoutContext,
reflow_result: &mut ReflowComplete,
) {
let pending_images = match &context.pending_images {
Some(pending) => std::mem::take(&mut *pending.lock().unwrap()),
None => Vec::new(),
};
reflow_result.pending_images = pending_images;
reflow_result.pending_images =
std::mem::replace(&mut *context.pending_images.lock().unwrap(), vec![]);
match *reflow_goal {
ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
&QueryMsg::ContentBoxQuery(node) => {
@ -1304,41 +1266,6 @@ impl LayoutThread {
rw_data.scroll_offsets = layout_scroll_states
}
fn tick_all_animations<'a, 'b>(&mut self, origin: ImmutableOrigin) {
self.tick_animations(origin);
}
fn tick_animations(&mut self, origin: ImmutableOrigin) {
if self.relayout_event {
println!(
"**** pipeline={}\tForDisplay\tSpecial\tAnimationTick",
self.id
);
}
if let Some(root) = &*self.fragment_tree_root.borrow() {
// Unwrap here should not panic since self.fragment_tree_root is only ever set to Some(_)
// in handle_reflow() where self.document_shared_lock is as well.
let author_shared_lock = self.document_shared_lock.clone().unwrap();
let author_guard = author_shared_lock.read();
let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let snapshots = SnapshotMap::new();
let mut layout_context = self.build_layout_context(guards, false, &snapshots, origin);
self.perform_post_style_recalc_layout_passes(
root.clone(),
&ReflowGoal::TickAnimations,
None,
&mut layout_context,
);
assert!(layout_context.pending_images.is_none());
}
}
fn perform_post_style_recalc_layout_passes(
&self,
fragment_tree: Arc<FragmentTreeRoot>,

View file

@ -530,8 +530,6 @@ pub enum LayoutHangAnnotation {
SetQuirksMode,
Reflow,
GetRPC,
TickAnimations,
AdvanceClockMs,
CollectReports,
PrepareToExit,
ExitNow,

View file

@ -7,8 +7,7 @@ use immeta::load_from_buf;
use ipc_channel::ipc::IpcSender;
use net_traits::image::base::{load_from_memory, Image, ImageMetadata};
use net_traits::image_cache::{
CanRequestImages, CorsStatus, ImageCache, ImageCacheResult, ImageResponder,
PendingImageResponse,
CorsStatus, ImageCache, ImageCacheResult, ImageResponder, PendingImageResponse,
};
use net_traits::image_cache::{ImageOrMetadataAvailable, ImageResponse};
use net_traits::image_cache::{PendingImageId, UsePlaceholder};
@ -147,7 +146,6 @@ impl AllPendingLoads {
url: ServoUrl,
origin: ImmutableOrigin,
cors_status: Option<CorsSettings>,
can_request: CanRequestImages,
) -> CacheResult<'a> {
match self
.url_to_load_key
@ -158,10 +156,6 @@ impl AllPendingLoads {
CacheResult::Hit(*load_key, self.loads.get_mut(load_key).unwrap())
},
Vacant(url_entry) => {
if can_request == CanRequestImages::No {
return CacheResult::Miss(None);
}
let load_key = self.keygen.next();
url_entry.insert(load_key);
@ -461,7 +455,6 @@ impl ImageCache for ImageCacheImpl {
origin: ImmutableOrigin,
cors_setting: Option<CorsSettings>,
use_placeholder: UsePlaceholder,
can_request: CanRequestImages,
) -> ImageCacheResult {
let mut store = self.store.lock().unwrap();
if let Some(result) = store.get_completed_image_if_available(
@ -485,12 +478,9 @@ impl ImageCache for ImageCacheImpl {
}
let decoded = {
let result = store.pending_loads.get_cached(
url.clone(),
origin.clone(),
cors_setting,
can_request,
);
let result = store
.pending_loads
.get_cached(url.clone(), origin.clone(), cors_setting);
match result {
CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) {
(&Some(Ok(_)), _) => {
@ -539,7 +529,6 @@ impl ImageCache for ImageCacheImpl {
cors_setting: Option<CorsSettings>,
sender: IpcSender<PendingImageResponse>,
use_placeholder: UsePlaceholder,
can_request: CanRequestImages,
) -> ImageCacheResult {
debug!("Track image for {} ({:?})", url, origin);
let cache_result = self.get_cached_image_status(
@ -547,7 +536,6 @@ impl ImageCache for ImageCacheImpl {
origin.clone(),
cors_setting,
use_placeholder,
can_request,
);
match cache_result {

View file

@ -14,15 +14,6 @@ use std::sync::Arc;
// Aux structs and enums.
// ======================================================================
/// Whether a consumer is in a position to request images or not. This can occur
/// when animations are being processed by the layout thread while the script
/// thread is executing in parallel.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum CanRequestImages {
No,
Yes,
}
/// Indicating either entire image or just metadata availability
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum ImageOrMetadataAvailable {
@ -119,7 +110,6 @@ pub trait ImageCache: Sync + Send {
origin: ImmutableOrigin,
cors_setting: Option<CorsSettings>,
use_placeholder: UsePlaceholder,
can_request: CanRequestImages,
) -> ImageCacheResult;
/// Add a listener for the provided pending image id, eventually called by
@ -135,7 +125,6 @@ pub trait ImageCache: Sync + Send {
cors_setting: Option<CorsSettings>,
sender: IpcSender<PendingImageResponse>,
use_placeholder: UsePlaceholder,
can_request: CanRequestImages,
) -> ImageCacheResult;
/// Add a new listener for the given pending image id. If the image is already present,

View file

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

View file

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

View file

@ -60,8 +60,8 @@ use mime::{self, Mime};
use msg::constellation_msg::PipelineId;
use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache::{
CanRequestImages, CorsStatus, ImageCache, ImageCacheResult, ImageOrMetadataAvailable,
ImageResponse, PendingImageId, PendingImageResponse, UsePlaceholder,
CorsStatus, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse,
PendingImageId, PendingImageResponse, UsePlaceholder,
};
use net_traits::request::{CorsSettings, Destination, Initiator, RequestBuilder};
use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError};
@ -326,7 +326,6 @@ impl HTMLImageElement {
cors_setting_for_element(self.upcast()),
sender,
UsePlaceholder::Yes,
CanRequestImages::Yes,
);
match cache_result {
@ -1107,7 +1106,6 @@ impl HTMLImageElement {
cors_setting_for_element(self.upcast()),
sender,
UsePlaceholder::No,
CanRequestImages::Yes,
);
match cache_result {

View file

@ -27,8 +27,8 @@ use html5ever::{LocalName, Prefix};
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use net_traits::image_cache::{
CanRequestImages, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse,
PendingImageId, UsePlaceholder,
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, PendingImageId,
UsePlaceholder,
};
use net_traits::request::{CredentialsMode, Destination, RequestBuilder};
use net_traits::{
@ -163,7 +163,6 @@ impl HTMLVideoElement {
None,
sender,
UsePlaceholder::No,
CanRequestImages::Yes,
);
match cache_result {

View file

@ -1054,8 +1054,8 @@ impl TestBindingMethods for TestBinding {
}
}
fn AdvanceClock(&self, ms: i32, tick: bool) {
self.global().as_window().advance_animation_clock(ms, tick);
fn AdvanceClock(&self, ms: i32) {
self.global().as_window().advance_animation_clock(ms);
}
fn Panic(&self) {

View file

@ -517,7 +517,7 @@ interface TestBinding {
[Pref="dom.testbinding.prefcontrolled.enabled"]
const unsigned short prefControlledConstDisabled = 0;
[Pref="layout.animations.test.enabled"]
void advanceClock(long millis, optional boolean forceLayoutTick = true);
void advanceClock(long millis);
[Pref="dom.testbinding.prefcontrolled2.enabled"]
readonly attribute boolean prefControlledAttributeEnabled;

View file

@ -173,6 +173,7 @@ pub enum ReflowReason {
IFrameLoadEvent,
MissingExplicitReflow,
ElementStateChanged,
PendingReflow,
}
#[dom_struct]
@ -1544,16 +1545,13 @@ impl Window {
)
}
/// Advances the layout animation clock by `delta` milliseconds, and then
/// forces a reflow if `tick` is true.
pub fn advance_animation_clock(&self, delta: i32, tick: bool) {
self.layout_chan
.send(Msg::AdvanceClockMs(
delta,
tick,
self.origin().immutable().clone(),
))
.unwrap();
/// Prepares to tick animations and then does a reflow which also advances the
/// layout animation clock.
pub fn advance_animation_clock(&self, delta: i32) {
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
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
@ -1626,9 +1624,8 @@ impl Window {
// If this reflow is for display, ensure webgl canvases are composited with
// up-to-date contents.
match reflow_goal {
ReflowGoal::Full => document.flush_dirty_canvases(),
ReflowGoal::TickAnimations | ReflowGoal::LayoutQuery(..) => {},
if for_display {
document.flush_dirty_canvases();
}
// Send new document and relevant styles to layout.
@ -1645,6 +1642,7 @@ impl Window {
script_join_chan: join_chan,
dom_count: document.dom_count(),
pending_restyles: document.drain_pending_restyles(),
animation_timeline_value: document.current_animation_timeline_value(),
};
self.layout_chan
@ -2446,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 {
@ -2464,32 +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",
});
println!("{}", debug_msg);
println!("**** pipeline={}\t{}\t{:?}", id, goal_string, reason);
}
impl Window {

View file

@ -47,6 +47,7 @@ extern crate servo_atoms;
#[macro_use]
extern crate style;
mod animation_timeline;
#[warn(deprecated)]
#[macro_use]
mod task;

View file

@ -136,16 +136,17 @@ use script_traits::CompositorEvent::{
WheelEvent,
};
use script_traits::{
CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity,
EventResult, HistoryEntryReplacement, InitialScriptState, JsEvalResult, LayoutMsg, LoadData,
LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType, NewLayoutInfo, Painter,
ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory, ScriptToConstellationChan,
StructuredSerializedData, TimerSchedulerMsg, TouchEventType, TouchId,
TransitionOrAnimationEventType, UntrustedNodeAddress, UpdatePipelineIdReason,
WebrenderIpcSender, WheelDelta, WindowSizeData, WindowSizeType,
AnimationTickType, CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext,
DocumentActivity, EventResult, HistoryEntryReplacement, InitialScriptState, JsEvalResult,
LayoutMsg, LoadData, LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType,
NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory,
ScriptToConstellationChan, StructuredSerializedData, TimerSchedulerMsg, TouchEventType,
TouchId, TransitionOrAnimationEvent, TransitionOrAnimationEventType, UntrustedNodeAddress,
UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, WindowSizeData, WindowSizeType,
};
use servo_atoms::Atom;
use servo_config::opts;
use servo_config::pref;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use std::borrow::Cow;
use std::cell::Cell;
@ -1497,8 +1498,7 @@ impl ScriptThread {
self.handle_set_scroll_state(id, &scroll_state);
})
},
FromConstellation(ConstellationControlMsg::TickAllAnimations(pipeline_id)) => {
// step 7.8
FromConstellation(ConstellationControlMsg::TickAllAnimations(pipeline_id, _)) => {
if !animation_ticks.contains(&pipeline_id) {
animation_ticks.insert(pipeline_id);
sequential.push(event);
@ -1544,6 +1544,9 @@ impl ScriptThread {
}
}
// Step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops.
self.update_animations_and_send_events();
// Process the gathered events.
debug!("Processing events.");
for msg in sequential {
@ -1603,7 +1606,7 @@ impl ScriptThread {
let pending_reflows = window.get_pending_reflow_count();
if pending_reflows > 0 {
window.reflow(ReflowGoal::Full, ReflowReason::ImageLoaded);
window.reflow(ReflowGoal::Full, ReflowReason::PendingReflow);
} else {
// Reflow currently happens when explicitly invoked by code that
// knows the document could have been modified. This should really
@ -1616,6 +1619,19 @@ impl ScriptThread {
true
}
// Perform step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops.
// TODO(mrobinson): This should also update the current animations and send events
// to conform to the HTML specification. This might mean having events for rooting
// DOM nodes and ultimately all animations living in script.
fn update_animations_and_send_events(&self) {
for (_, document) in self.documents.borrow().iter() {
// Only update the time if it isn't being managed by a test.
if !pref!(layout.animations.test.enabled) {
document.update_animation_timeline();
}
}
}
fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
match *msg {
MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg {
@ -1703,7 +1719,7 @@ impl ScriptThread {
RemoveHistoryStates(id, ..) => Some(id),
FocusIFrame(id, ..) => Some(id),
WebDriverScriptCommand(id, ..) => Some(id),
TickAllAnimations(id) => Some(id),
TickAllAnimations(id, ..) => Some(id),
TransitionOrAnimationEvent { .. } => None,
WebFontLoaded(id) => Some(id),
DispatchIFrameLoadEvent {
@ -1902,23 +1918,11 @@ impl ScriptThread {
ConstellationControlMsg::WebDriverScriptCommand(pipeline_id, msg) => {
self.handle_webdriver_msg(pipeline_id, msg)
},
ConstellationControlMsg::TickAllAnimations(pipeline_id) => {
self.handle_tick_all_animations(pipeline_id)
ConstellationControlMsg::TickAllAnimations(pipeline_id, tick_type) => {
self.handle_tick_all_animations(pipeline_id, tick_type)
},
ConstellationControlMsg::TransitionOrAnimationEvent {
pipeline_id,
event_type,
node,
property_or_animation_name,
elapsed_time,
} => {
self.handle_transition_or_animation_event(
pipeline_id,
event_type,
node,
property_or_animation_name,
elapsed_time,
);
ConstellationControlMsg::TransitionOrAnimationEvent(ref event) => {
self.handle_transition_or_animation_event(event);
},
ConstellationControlMsg::WebFontLoaded(pipeline_id) => {
self.handle_web_font_loaded(pipeline_id)
@ -2914,35 +2918,51 @@ impl ScriptThread {
debug!("Exited script thread.");
}
/// Handles animation tick requested during testing.
pub fn handle_tick_all_animations_for_testing(id: PipelineId) {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.get().unwrap() };
script_thread
.handle_tick_all_animations(id, AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
});
}
/// Handles when layout thread finishes all animation in one tick
fn handle_tick_all_animations(&self, id: PipelineId) {
fn handle_tick_all_animations(&self, id: PipelineId, tick_type: AnimationTickType) {
let document = match self.documents.borrow().find_document(id) {
Some(document) => document,
None => return warn!("Message sent to closed pipeline {}.", id),
};
document.run_the_animation_frame_callbacks();
if tick_type.contains(AnimationTickType::REQUEST_ANIMATION_FRAME) {
document.run_the_animation_frame_callbacks();
}
if tick_type.contains(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS) {
match self.animating_nodes.borrow().get(&id) {
Some(nodes) => {
for node in nodes.iter() {
node.dirty(NodeDamage::NodeStyleDamaged);
}
},
None => return,
}
document.window().add_pending_reflow();
}
}
/// Handles firing of transition-related events.
///
/// TODO(mrobinson): Add support for more events.
fn handle_transition_or_animation_event(
&self,
pipeline_id: PipelineId,
event_type: TransitionOrAnimationEventType,
unsafe_node: UntrustedNodeAddress,
property_or_animation_name: String,
elapsed_time: f64,
) {
fn handle_transition_or_animation_event(&self, event: &TransitionOrAnimationEvent) {
let js_runtime = self.js_runtime.rt();
let node = unsafe { from_untrusted_node_address(js_runtime, unsafe_node) };
let node = unsafe { from_untrusted_node_address(js_runtime, event.node) };
// We limit the scope of the borrow here, so that we don't maintain this borrow
// and then incidentally trigger another layout. That might result in a double
// mutable borrow of `animating_nodes`.
{
let mut animating_nodes = self.animating_nodes.borrow_mut();
let nodes = match animating_nodes.get_mut(&pipeline_id) {
let nodes = match animating_nodes.get_mut(&event.pipeline_id) {
Some(nodes) => nodes,
None => {
return warn!(
@ -2964,17 +2984,12 @@ impl ScriptThread {
},
};
if event_type.finalizes_transition_or_animation() {
if event.event_type.finalizes_transition_or_animation() {
nodes.remove(node_index);
}
}
// Not quite the right thing - see #13865.
if event_type.finalizes_transition_or_animation() {
node.dirty(NodeDamage::NodeStyleDamaged);
}
let event_atom = match event_type {
let event_atom = match event.event_type {
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
@ -2986,11 +3001,11 @@ impl ScriptThread {
};
// TODO: Handle pseudo-elements properly
let property_or_animation_name = DOMString::from(property_or_animation_name);
let elapsed_time = Finite::new(elapsed_time as f32).unwrap();
let property_or_animation_name = DOMString::from(event.property_or_animation_name.clone());
let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
let window = window_from_node(&*node);
if event_type.is_transition_event() {
if event.event_type.is_transition_event() {
let event_init = TransitionEventInit {
parent,
propertyName: property_or_animation_name,

View file

@ -47,15 +47,6 @@ pub enum Msg {
/// Get an RPC interface.
GetRPC(Sender<Box<dyn LayoutRPC + Send>>),
/// Requests that the layout thread render the next frame of all animations.
TickAnimations(ImmutableOrigin),
/// Updates layout's timer for animation testing from script.
///
/// The inner field is the number of *milliseconds* to advance, and the bool
/// field is whether animations should be force-ticked.
AdvanceClockMs(i32, bool, ImmutableOrigin),
/// Requests that the layout thread measure its memory usage. The resulting reports are sent back
/// via the supplied channel.
CollectReports(ReportsChan),
@ -216,6 +207,8 @@ pub struct ScriptReflow {
pub origin: ImmutableOrigin,
/// Restyle snapshot map.
pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
/// The current animation timeline value.
pub animation_timeline_value: f64,
}
pub struct LayoutThreadInit {

View file

@ -11,6 +11,7 @@ name = "script_traits"
path = "lib.rs"
[dependencies]
bitflags = "1.0"
bluetooth_traits = {path = "../bluetooth_traits"}
canvas_traits = {path = "../canvas_traits"}
cookie = "0.11"

View file

@ -9,6 +9,8 @@
#![deny(missing_docs)]
#![deny(unsafe_code)]
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate malloc_size_of;
#[macro_use]
@ -122,8 +124,6 @@ pub enum LayoutControlMsg {
ExitNow,
/// Requests the current epoch (layout counter) from this layout.
GetCurrentEpoch(IpcSender<Epoch>),
/// Asks layout to run another step in its animation.
TickAnimations(ImmutableOrigin),
/// Tells layout about the new scrolling offsets of each scrollable stacking context.
SetScrollStates(Vec<ScrollState>),
/// Requests the current load state of Web fonts. `true` is returned if fonts are still loading
@ -318,6 +318,22 @@ impl TransitionOrAnimationEventType {
}
}
#[derive(Deserialize, Serialize)]
/// A transition or animation event.
pub struct TransitionOrAnimationEvent {
/// The pipeline id of the layout task that sent this message.
pub pipeline_id: PipelineId,
/// The type of transition event this should trigger.
pub event_type: TransitionOrAnimationEventType,
/// The address of the node which owns this transition.
pub node: UntrustedNodeAddress,
/// The name of the property that is transitioning (in the case of a transition)
/// or the name of the animation (in the case of an animation).
pub property_or_animation_name: String,
/// The elapsed time property to send with this transition event.
pub elapsed_time: f64,
}
/// Messages sent from the constellation or layout to the script thread.
#[derive(Deserialize, Serialize)]
pub enum ConstellationControlMsg {
@ -403,21 +419,9 @@ pub enum ConstellationControlMsg {
/// Passes a webdriver command to the script thread for execution
WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
/// Notifies script thread that all animations are done
TickAllAnimations(PipelineId),
TickAllAnimations(PipelineId, AnimationTickType),
/// Notifies the script thread that a transition or animation related event should be sent.
TransitionOrAnimationEvent {
/// The pipeline id of the layout task that sent this message.
pipeline_id: PipelineId,
/// The type of transition event this should trigger.
event_type: TransitionOrAnimationEventType,
/// The address of the node which owns this transition.
node: UntrustedNodeAddress,
/// The name of the property that is transitioning (in the case of a transition)
/// or the name of the animation (in the case of an animation).
property_or_animation_name: String,
/// The elapsed time property to send with this transition event.
elapsed_time: f64,
},
TransitionOrAnimationEvent(TransitionOrAnimationEvent),
/// Notifies the script thread that a new Web font has been loaded, and thus the page should be
/// reflowed.
WebFontLoaded(PipelineId),
@ -812,13 +816,15 @@ pub struct IFrameLoadInfoWithData {
pub window_size: WindowSizeData,
}
/// Specifies whether the script or layout thread needs to be ticked for animation.
#[derive(Debug, Deserialize, Serialize)]
pub enum AnimationTickType {
/// The script thread.
Script,
/// The layout thread.
Layout,
bitflags! {
#[derive(Deserialize, Serialize)]
/// Specifies if rAF should be triggered and/or CSS Animations and Transitions.
pub struct AnimationTickType: u8 {
/// Trigger a call to requestAnimationFrame.
const REQUEST_ANIMATION_FRAME = 0b001;
/// Trigger restyles for CSS Animations and Transitions.
const CSS_ANIMATIONS_AND_TRANSITIONS = 0b010;
}
}
/// The scroll state of a stacking context.

View file

@ -18,7 +18,6 @@ use crate::properties::LonghandIdSet;
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
use crate::stylesheets::Origin;
use crate::timer::Timer;
use crate::values::computed::Time;
use crate::values::computed::TimingFunction;
use crate::values::generics::box_::AnimationIterationCount;
@ -77,30 +76,33 @@ pub struct KeyframesAnimationState {
}
impl KeyframesAnimationState {
/// Performs a tick in the animation state, i.e., increments the counter of
/// the current iteration count, updates times and then toggles the
/// direction if appropriate.
///
/// Returns true if the animation should keep running.
pub fn tick(&mut self) -> bool {
debug!("KeyframesAnimationState::tick");
self.started_at += self.duration + self.delay;
/// Given the current time, advances this animation to the next iteration,
/// updates times, and then toggles the direction if appropriate. Otherwise
/// does nothing.
pub fn iterate_if_necessary(&mut self, time: f64) {
if !self.iteration_over(time) {
return;
}
match self.running_state {
// If it's paused, don't update direction or iteration count.
KeyframesRunningState::Paused(_) => return true,
KeyframesRunningState::Paused(_) => return,
KeyframesRunningState::Running => {},
}
if let KeyframesIterationState::Finite(ref mut current, ref max) = self.iteration_state {
*current += 1.0;
if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state {
// If we are already on the final iteration, just exit now.
// NB: This prevent us from updating the direction, which might be
// needed for the correct handling of animation-fill-mode.
if *current >= *max {
return false;
if (max - *current) <= 1.0 {
return;
}
*current += 1.0;
}
// Update the next iteration direction if applicable.
// TODO(mrobinson): The duration might now be wrong for floating point iteration counts.
self.started_at += self.duration + self.delay;
match self.direction {
AnimationDirection::Alternate | AnimationDirection::AlternateReverse => {
self.current_direction = match self.current_direction {
@ -111,8 +113,29 @@ impl KeyframesAnimationState {
},
_ => {},
}
}
true
fn iteration_over(&self, time: f64) -> bool {
time > (self.started_at + self.duration)
}
fn has_ended(&self, time: f64) -> bool {
if !self.iteration_over(time) {
return false;
}
// If we are paused then we haven't ended.
match self.running_state {
KeyframesRunningState::Paused(_) => return false,
KeyframesRunningState::Running => {},
}
// If we have a limited number of iterations and we cannot advance to another
// iteration, then we have ended.
return match self.iteration_state {
KeyframesIterationState::Finite(current, max) if (max - current) <= 1.0 => true,
KeyframesIterationState::Finite(..) | KeyframesIterationState::Infinite => false,
};
}
/// Updates the appropiate state from other animation.
@ -122,7 +145,7 @@ impl KeyframesAnimationState {
///
/// There are some bits of state we can't just replace, over all taking in
/// account times, so here's that logic.
pub fn update_from_other(&mut self, other: &Self, timer: &Timer) {
pub fn update_from_other(&mut self, other: &Self, now: f64) {
use self::KeyframesRunningState::*;
debug!(
@ -148,12 +171,10 @@ impl KeyframesAnimationState {
//
// If we're pausing the animation, compute the progress value.
match (&mut self.running_state, old_running_state) {
(&mut Running, Paused(progress)) => {
new_started_at = timer.seconds() - (self.duration * progress)
},
(&mut Running, Paused(progress)) => new_started_at = now - (self.duration * progress),
(&mut Paused(ref mut new), Paused(old)) => *new = old,
(&mut Paused(ref mut progress), Running) => {
*progress = (timer.seconds() - old_started_at) / old_duration
*progress = (now - old_started_at) / old_duration
},
_ => {},
}
@ -239,6 +260,18 @@ impl Animation {
}
}
/// Whether or not this animation has ended at the provided time. This does
/// not take into account canceling i.e. when an animation or transition is
/// canceled due to changes in the style.
pub fn has_ended(&self, time: f64) -> bool {
match *self {
Animation::Transition(_, started_at, ref property_animation) => {
time >= started_at + (property_animation.duration)
},
Animation::Keyframes(_, _, _, ref state) => state.has_ended(time),
}
}
/// Whether this animation has the same end value as another one.
#[inline]
fn is_transition_with_same_end_value(&self, other_animation: &PropertyAnimation) -> bool {
@ -463,52 +496,26 @@ impl ElementAnimationState {
false
}
pub(crate) fn apply_completed_animations(&mut self, style: &mut Arc<ComputedValues>) {
for animation in self.finished_animations.iter() {
// TODO: Make this a bit more general and add support animation-fill-mode.
// Without support for that property though, animations that have ended should
// not affect property values. This is why we only process transitions here.
if let Animation::Transition(_, _, property_animation) = animation {
property_animation.update(Arc::make_mut(style), 1.0);
pub(crate) fn apply_new_and_running_animations<E>(
&mut self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) where
E: TElement,
{
if !self.running_animations.is_empty() {
let style = Arc::make_mut(style);
for animation in self.running_animations.iter_mut() {
update_style_for_animation::<E>(context, animation, style, font_metrics);
}
}
}
pub(crate) fn apply_running_animations<E>(
&mut self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) where
E: TElement,
{
// Return early so that we don't unnecessarily clone the style when making it mutable.
if self.running_animations.is_empty() {
return;
}
let style = Arc::make_mut(style);
for animation in self.running_animations.iter_mut() {
update_style_for_animation::<E>(context, animation, style, font_metrics);
}
}
pub(crate) fn apply_new_animations<E>(
&mut self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) where
E: TElement,
{
// Return early so that we don't unnecessarily clone the style when making it mutable.
if self.new_animations.is_empty() {
return;
}
let style = Arc::make_mut(style);
for animation in self.new_animations.iter_mut() {
update_style_for_animation::<E>(context, animation, style, font_metrics);
if !self.new_animations.is_empty() {
let style = Arc::make_mut(style);
for animation in self.new_animations.iter_mut() {
update_style_for_animation::<E>(context, animation, style, font_metrics);
}
}
}
@ -525,24 +532,6 @@ impl ElementAnimationState {
self.new_animations.push(animation);
}
fn add_or_update_new_animation(&mut self, timer: &Timer, new_animation: Animation) {
// If the animation was already present in the list for the node,
// just update its state.
if let Animation::Keyframes(_, _, ref new_name, ref new_state) = new_animation {
for existing_animation in self.running_animations.iter_mut() {
match existing_animation {
Animation::Keyframes(_, _, ref name, ref mut state) if *name == *new_name => {
state.update_from_other(&new_state, timer);
return;
},
_ => {},
}
}
}
// Otherwise just add the new running animation.
self.add_new_animation(new_animation);
}
/// Update our animations given a new style, canceling or starting new animations
/// when appropriate.
pub fn update_animations_for_new_style<E>(
@ -572,33 +561,61 @@ impl ElementAnimationState {
}
maybe_start_animations(element, &context, &new_style, self);
self.iterate_running_animations_if_necessary(context.current_time_for_animations);
}
/// Update our transitions given a new style, canceling or starting new animations
/// when appropriate.
pub fn update_transitions_for_new_style(
pub fn update_transitions_for_new_style<E>(
&mut self,
context: &SharedStyleContext,
opaque_node: OpaqueNode,
before_change_style: Option<&Arc<ComputedValues>>,
new_style: &Arc<ComputedValues>,
) {
old_style: Option<&Arc<ComputedValues>>,
after_change_style: &Arc<ComputedValues>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) where
E: TElement,
{
// If this is the first style, we don't trigger any transitions and we assume
// there were no previously triggered transitions.
let before_change_style = match before_change_style {
Some(before_change_style) => before_change_style,
let mut before_change_style = match old_style {
Some(old_style) => Arc::clone(old_style),
None => return,
};
// We convert old values into `before-change-style` here.
// See https://drafts.csswg.org/css-transitions/#starting. We need to clone the
// style because this might still be a reference to the original `old_style` and
// we want to preserve that so that we can later properly calculate restyle damage.
if self.running_animations.is_empty() || self.new_animations.is_empty() {
before_change_style = before_change_style.clone();
self.apply_new_and_running_animations::<E>(
context,
&mut before_change_style,
font_metrics,
);
}
let transitioning_properties = start_transitions_if_applicable(
context,
opaque_node,
before_change_style,
new_style,
&before_change_style,
after_change_style,
self,
);
self.cancel_transitions_with_nontransitioning_properties(&transitioning_properties);
}
/// When necessary, iterate our running animations to the next iteration.
pub fn iterate_running_animations_if_necessary(&mut self, time: f64) {
for animation in self.running_animations.iter_mut() {
match *animation {
Animation::Keyframes(_, _, _, ref mut state) => state.iterate_if_necessary(time),
_ => {},
}
}
}
}
/// Kick off any new transitions for this node and return all of the properties that are
@ -651,8 +668,8 @@ pub fn start_transitions_if_applicable(
// Kick off the animation.
debug!("Kicking off transition of {:?}", property_animation);
let box_style = new_style.get_box();
let now = context.timer.seconds();
let start_time = now + (box_style.transition_delay_mod(transition.index).seconds() as f64);
let start_time = context.current_time_for_animations +
(box_style.transition_delay_mod(transition.index).seconds() as f64);
animation_state.add_new_animation(Animation::Transition(
opaque_node,
start_time,
@ -755,8 +772,7 @@ pub fn maybe_start_animations<E>(
}
let delay = box_style.animation_delay_mod(i).seconds();
let now = context.timer.seconds();
let animation_start = now + delay as f64;
let animation_start = context.current_time_for_animations + delay as f64;
let iteration_state = match box_style.animation_iteration_count_mod(i) {
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite,
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n),
@ -778,24 +794,36 @@ pub fn maybe_start_animations<E>(
AnimationPlayState::Running => KeyframesRunningState::Running,
};
animation_state.add_or_update_new_animation(
&context.timer,
Animation::Keyframes(
element.as_node().opaque(),
anim.clone(),
name.clone(),
KeyframesAnimationState {
started_at: animation_start,
duration: duration as f64,
delay: delay as f64,
iteration_state,
running_state,
direction: animation_direction,
current_direction: initial_direction,
cascade_style: new_style.clone(),
},
),
);
let new_state = KeyframesAnimationState {
started_at: animation_start,
duration: duration as f64,
delay: delay as f64,
iteration_state,
running_state,
direction: animation_direction,
current_direction: initial_direction,
cascade_style: new_style.clone(),
};
// If the animation was already present in the list for the node, just update its state.
for existing_animation in animation_state.running_animations.iter_mut() {
match existing_animation {
Animation::Keyframes(_, _, ref old_name, ref mut old_state)
if *old_name == *name =>
{
old_state.update_from_other(&new_state, context.current_time_for_animations);
return;
}
_ => {},
}
}
animation_state.add_new_animation(Animation::Keyframes(
element.as_node().opaque(),
anim.clone(),
name.clone(),
new_state,
));
}
}
@ -811,7 +839,7 @@ pub fn update_style_for_animation<E>(
debug!("update_style_for_animation: {:?}", animation);
match *animation {
Animation::Transition(_, start_time, ref property_animation) => {
let now = context.timer.seconds();
let now = context.current_time_for_animations;
let progress = (now - start_time) / (property_animation.duration);
let progress = progress.min(1.0);
if progress >= 0.0 {
@ -823,7 +851,7 @@ pub fn update_style_for_animation<E>(
let started_at = state.started_at;
let now = match state.running_state {
KeyframesRunningState::Running => context.timer.seconds(),
KeyframesRunningState::Running => context.current_time_for_animations,
KeyframesRunningState::Paused(progress) => started_at + duration * progress,
};

View file

@ -25,7 +25,6 @@ use crate::shared_lock::StylesheetGuards;
use crate::sharing::StyleSharingCache;
use crate::stylist::Stylist;
use crate::thread_state::{self, ThreadState};
use crate::timer::Timer;
use crate::traversal::DomTraversal;
use crate::traversal_flags::TraversalFlags;
use app_units::Au;
@ -156,9 +155,9 @@ pub struct SharedStyleContext<'a> {
/// Guards for pre-acquired locks
pub guards: StylesheetGuards<'a>,
/// The current timer for transitions and animations. This is needed to test
/// them.
pub timer: Timer,
/// The current time for transitions and animations. This is needed to ensure
/// a consistent sampling time and also to adjust the time for testing.
pub current_time_for_animations: f64,
/// Flags controlling how we traverse the tree.
pub traversal_flags: TraversalFlags,

View file

@ -157,7 +157,6 @@ pub mod stylesheet_set;
pub mod stylesheets;
pub mod stylist;
pub mod thread_state;
pub mod timer;
pub mod traversal;
pub mod traversal_flags;
pub mod use_counters;

View file

@ -442,31 +442,17 @@ trait PrivateMatchMethods: TElement {
let mut animation_states = shared_context.animation_states.write();
let mut animation_state = animation_states.remove(&this_opaque).unwrap_or_default();
if let Some(ref mut old_values) = *old_values {
// We convert old values into `before-change-style` here.
// https://drafts.csswg.org/css-transitions/#starting
animation_state.apply_completed_animations(old_values);
animation_state.apply_running_animations::<Self>(
shared_context,
old_values,
&context.thread_local.font_metrics_provider,
);
}
animation_state.update_animations_for_new_style(*self, &shared_context, &new_values);
animation_state.update_transitions_for_new_style(
animation_state.update_transitions_for_new_style::<Self>(
&shared_context,
this_opaque,
old_values.as_ref(),
new_values,
);
animation_state.apply_running_animations::<Self>(
shared_context,
new_values,
&context.thread_local.font_metrics_provider,
);
animation_state.apply_new_animations::<Self>(
animation_state.apply_new_and_running_animations::<Self>(
shared_context,
new_values,
&context.thread_local.font_metrics_provider,

View file

@ -1,63 +0,0 @@
/* 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 timer module, used to define a `Timer` type, that is controlled by script.
use time;
/// The `TimerMode` is used to determine what time should the `Timer` return.
#[derive(Clone, Debug)]
enum TimerMode {
/// The timer should return a fixed value.
Test(f64),
/// The timer should return the actual time.
Current,
}
/// A `Timer` struct that takes care of giving the current time for animations.
///
/// This is needed to be allowed to hook the time in the animations' test-mode.
#[derive(Clone, Debug)]
pub struct Timer {
mode: TimerMode,
}
impl Timer {
/// Creates a new "normal" timer, i.e., a "Current" mode timer.
#[inline]
pub fn new() -> Self {
Timer {
mode: TimerMode::Current,
}
}
/// Creates a new "test mode" timer, with initial time 0.
#[inline]
pub fn test_mode() -> Self {
Timer {
mode: TimerMode::Test(0.),
}
}
/// Returns the current time, at least from the caller's perspective. In
/// test mode returns whatever the value is.
pub fn seconds(&self) -> f64 {
match self.mode {
TimerMode::Test(test_value) => test_value,
TimerMode::Current => time::precise_time_s(),
}
}
/// Increments the current clock. Panics if the clock is not on test mode.
pub fn increment(&mut self, by: f64) {
match self.mode {
TimerMode::Test(ref mut val) => *val += by,
TimerMode::Current => {
panic!("Timer::increment called for a non-test mode timer. This is a bug.")
},
}
}
}