mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:
commit
b290ad95c1
76 changed files with 1389 additions and 3701 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -530,8 +530,6 @@ pub enum LayoutHangAnnotation {
|
|||
SetQuirksMode,
|
||||
Reflow,
|
||||
GetRPC,
|
||||
TickAnimations,
|
||||
AdvanceClockMs,
|
||||
CollectReports,
|
||||
PrepareToExit,
|
||||
ExitNow,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
49
components/script/animation_timeline.rs
Normal file
49
components/script/animation_timeline.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -47,6 +47,7 @@ extern crate servo_atoms;
|
|||
#[macro_use]
|
||||
extern crate style;
|
||||
|
||||
mod animation_timeline;
|
||||
#[warn(deprecated)]
|
||||
#[macro_use]
|
||||
mod task;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue