mirror of
https://github.com/servo/servo.git
synced 2025-07-24 07:40:27 +01:00
Cancel animations that affect nodes that do not participate in layout.
This commit is contained in:
parent
8fb1b567fa
commit
ed74b898a5
9 changed files with 89 additions and 10 deletions
|
@ -9,7 +9,7 @@ use crate::display_list::items::OpaqueNode;
|
||||||
use crate::flow::{Flow, GetBaseFlow};
|
use crate::flow::{Flow, GetBaseFlow};
|
||||||
use crate::opaque_node::OpaqueNodeMethods;
|
use crate::opaque_node::OpaqueNodeMethods;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
use script_traits::UntrustedNodeAddress;
|
use script_traits::UntrustedNodeAddress;
|
||||||
|
@ -28,6 +28,7 @@ pub fn update_animation_state<E>(
|
||||||
script_chan: &IpcSender<ConstellationControlMsg>,
|
script_chan: &IpcSender<ConstellationControlMsg>,
|
||||||
running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
|
running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||||
expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
|
expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||||
|
mut keys_to_remove: FxHashSet<OpaqueNode>,
|
||||||
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
|
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
|
||||||
new_animations_receiver: &Receiver<Animation>,
|
new_animations_receiver: &Receiver<Animation>,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
|
@ -74,7 +75,6 @@ pub fn update_animation_state<E>(
|
||||||
// TODO: Do not expunge Keyframes animations, since we need that state if
|
// TODO: Do not expunge Keyframes animations, since we need that state if
|
||||||
// the animation gets re-triggered. Probably worth splitting in two
|
// the animation gets re-triggered. Probably worth splitting in two
|
||||||
// different maps, or at least using a linked list?
|
// different maps, or at least using a linked list?
|
||||||
let mut keys_to_remove = vec![];
|
|
||||||
for (key, running_animations) in running_animations.iter_mut() {
|
for (key, running_animations) in running_animations.iter_mut() {
|
||||||
let mut animations_still_running = vec![];
|
let mut animations_still_running = vec![];
|
||||||
for mut running_animation in running_animations.drain(..) {
|
for mut running_animation in running_animations.drain(..) {
|
||||||
|
@ -116,7 +116,7 @@ pub fn update_animation_state<E>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if animations_still_running.is_empty() {
|
if animations_still_running.is_empty() {
|
||||||
keys_to_remove.push(*key);
|
keys_to_remove.insert(*key);
|
||||||
} else {
|
} else {
|
||||||
*running_animations = animations_still_running
|
*running_animations = animations_still_running
|
||||||
}
|
}
|
||||||
|
@ -160,17 +160,33 @@ pub fn update_animation_state<E>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recalculates style for a set of animations. This does *not* run with the DOM
|
/// Recalculates style for a set of animations. This does *not* run with the DOM
|
||||||
/// lock held.
|
/// lock held. Returns a set of nodes associated with animations that are no longer
|
||||||
|
/// valid.
|
||||||
pub fn recalc_style_for_animations<E>(
|
pub fn recalc_style_for_animations<E>(
|
||||||
context: &LayoutContext,
|
context: &LayoutContext,
|
||||||
flow: &mut dyn Flow,
|
flow: &mut dyn Flow,
|
||||||
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
|
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||||
|
) -> FxHashSet<OpaqueNode>
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
{
|
||||||
|
let mut invalid_nodes = animations.keys().cloned().collect();
|
||||||
|
do_recalc_style_for_animations::<E>(context, flow, animations, &mut invalid_nodes);
|
||||||
|
invalid_nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_recalc_style_for_animations<E>(
|
||||||
|
context: &LayoutContext,
|
||||||
|
flow: &mut dyn Flow,
|
||||||
|
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||||
|
invalid_nodes: &mut FxHashSet<OpaqueNode>,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
{
|
{
|
||||||
let mut damage = RestyleDamage::empty();
|
let mut damage = RestyleDamage::empty();
|
||||||
flow.mutate_fragments(&mut |fragment| {
|
flow.mutate_fragments(&mut |fragment| {
|
||||||
if let Some(ref animations) = animations.get(&fragment.node) {
|
if let Some(ref animations) = animations.get(&fragment.node) {
|
||||||
|
invalid_nodes.remove(&fragment.node);
|
||||||
for animation in animations.iter() {
|
for animation in animations.iter() {
|
||||||
let old_style = fragment.style.clone();
|
let old_style = fragment.style.clone();
|
||||||
update_style_for_animation::<E>(
|
update_style_for_animation::<E>(
|
||||||
|
@ -189,6 +205,6 @@ pub fn recalc_style_for_animations<E>(
|
||||||
let base = flow.mut_base();
|
let base = flow.mut_base();
|
||||||
base.restyle_damage.insert(damage);
|
base.restyle_damage.insert(damage);
|
||||||
for kid in base.children.iter_mut() {
|
for kid in base.children.iter_mut() {
|
||||||
recalc_style_for_animations::<E>(context, kid, animations)
|
do_recalc_style_for_animations::<E>(context, kid, animations, invalid_nodes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use embedder_traits::resources::{self, Resource};
|
use embedder_traits::resources::{self, Resource};
|
||||||
use euclid::{Point2D, Rect, Size2D, TypedScale, TypedSize2D};
|
use euclid::{Point2D, Rect, Size2D, TypedScale, TypedSize2D};
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use gfx::font;
|
use gfx::font;
|
||||||
use gfx::font_cache_thread::FontCacheThread;
|
use gfx::font_cache_thread::FontCacheThread;
|
||||||
use gfx::font_context;
|
use gfx::font_context;
|
||||||
|
@ -647,6 +647,7 @@ impl LayoutThread {
|
||||||
},
|
},
|
||||||
Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint,
|
Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint,
|
||||||
Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart,
|
Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart,
|
||||||
|
Msg::GetRunningAnimations(..) => LayoutHangAnnotation::GetRunningAnimations,
|
||||||
};
|
};
|
||||||
self.background_hang_monitor
|
self.background_hang_monitor
|
||||||
.notify_activity(HangAnnotation::Layout(hang_annotation));
|
.notify_activity(HangAnnotation::Layout(hang_annotation));
|
||||||
|
@ -818,6 +819,9 @@ impl LayoutThread {
|
||||||
Msg::SetNavigationStart(time) => {
|
Msg::SetNavigationStart(time) => {
|
||||||
self.paint_time_metrics.set_navigation_start(time);
|
self.paint_time_metrics.set_navigation_start(time);
|
||||||
},
|
},
|
||||||
|
Msg::GetRunningAnimations(sender) => {
|
||||||
|
let _ = sender.send(self.running_animations.read().len());
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -1447,6 +1451,7 @@ impl LayoutThread {
|
||||||
Some(&document),
|
Some(&document),
|
||||||
&mut rw_data,
|
&mut rw_data,
|
||||||
&mut layout_context,
|
&mut layout_context,
|
||||||
|
FxHashSet::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1624,7 +1629,7 @@ impl LayoutThread {
|
||||||
let snapshots = SnapshotMap::new();
|
let snapshots = SnapshotMap::new();
|
||||||
let mut layout_context = self.build_layout_context(guards, false, &snapshots);
|
let mut layout_context = self.build_layout_context(guards, false, &snapshots);
|
||||||
|
|
||||||
{
|
let invalid_nodes = {
|
||||||
// Perform an abbreviated style recalc that operates without access to the DOM.
|
// Perform an abbreviated style recalc that operates without access to the DOM.
|
||||||
let animations = self.running_animations.read();
|
let animations = self.running_animations.read();
|
||||||
profile(
|
profile(
|
||||||
|
@ -1638,8 +1643,8 @@ impl LayoutThread {
|
||||||
&animations,
|
&animations,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
};
|
||||||
self.perform_post_style_recalc_layout_passes(
|
self.perform_post_style_recalc_layout_passes(
|
||||||
&mut root_flow,
|
&mut root_flow,
|
||||||
&reflow_info,
|
&reflow_info,
|
||||||
|
@ -1647,6 +1652,7 @@ impl LayoutThread {
|
||||||
None,
|
None,
|
||||||
&mut *rw_data,
|
&mut *rw_data,
|
||||||
&mut layout_context,
|
&mut layout_context,
|
||||||
|
invalid_nodes,
|
||||||
);
|
);
|
||||||
assert!(layout_context.pending_images.is_none());
|
assert!(layout_context.pending_images.is_none());
|
||||||
assert!(layout_context.newly_transitioning_nodes.is_none());
|
assert!(layout_context.newly_transitioning_nodes.is_none());
|
||||||
|
@ -1661,6 +1667,7 @@ impl LayoutThread {
|
||||||
document: Option<&ServoLayoutDocument>,
|
document: Option<&ServoLayoutDocument>,
|
||||||
rw_data: &mut LayoutThreadData,
|
rw_data: &mut LayoutThreadData,
|
||||||
context: &mut LayoutContext,
|
context: &mut LayoutContext,
|
||||||
|
invalid_nodes: FxHashSet<OpaqueNode>,
|
||||||
) {
|
) {
|
||||||
{
|
{
|
||||||
let mut newly_transitioning_nodes = context
|
let mut newly_transitioning_nodes = context
|
||||||
|
@ -1675,6 +1682,7 @@ impl LayoutThread {
|
||||||
&self.script_chan,
|
&self.script_chan,
|
||||||
&mut *self.running_animations.write(),
|
&mut *self.running_animations.write(),
|
||||||
&mut *self.expired_animations.write(),
|
&mut *self.expired_animations.write(),
|
||||||
|
invalid_nodes,
|
||||||
newly_transitioning_nodes,
|
newly_transitioning_nodes,
|
||||||
&self.new_animations_receiver,
|
&self.new_animations_receiver,
|
||||||
self.id,
|
self.id,
|
||||||
|
|
|
@ -310,6 +310,7 @@ pub enum LayoutHangAnnotation {
|
||||||
UpdateScrollStateFromScript,
|
UpdateScrollStateFromScript,
|
||||||
RegisterPaint,
|
RegisterPaint,
|
||||||
SetNavigationStart,
|
SetNavigationStart,
|
||||||
|
GetRunningAnimations,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
|
|
@ -173,3 +173,8 @@ partial interface Window {
|
||||||
readonly attribute TestRunner testRunner;
|
readonly attribute TestRunner testRunner;
|
||||||
//readonly attribute EventSender eventSender;
|
//readonly attribute EventSender eventSender;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
partial interface Window {
|
||||||
|
[Pref="css.animations.testing.enabled"]
|
||||||
|
readonly attribute unsigned long runningAnimationCount;
|
||||||
|
};
|
||||||
|
|
|
@ -74,7 +74,7 @@ use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarker
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use embedder_traits::EmbedderMsg;
|
use embedder_traits::EmbedderMsg;
|
||||||
use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedScale, TypedSize2D, Vector2D};
|
use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedScale, TypedSize2D, Vector2D};
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::{channel, IpcSender};
|
||||||
use ipc_channel::router::ROUTER;
|
use ipc_channel::router::ROUTER;
|
||||||
use js::jsapi::JSAutoCompartment;
|
use js::jsapi::JSAutoCompartment;
|
||||||
use js::jsapi::JSContext;
|
use js::jsapi::JSContext;
|
||||||
|
@ -1144,6 +1144,12 @@ impl WindowMethods for Window {
|
||||||
self.test_runner.or_init(|| TestRunner::new(self.upcast()))
|
self.test_runner.or_init(|| TestRunner::new(self.upcast()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn RunningAnimationCount(&self) -> u32 {
|
||||||
|
let (sender, receiver) = channel().unwrap();
|
||||||
|
let _ = self.layout_chan.send(Msg::GetRunningAnimations(sender));
|
||||||
|
receiver.recv().unwrap_or(0) as u32
|
||||||
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-name
|
// https://html.spec.whatwg.org/multipage/#dom-name
|
||||||
fn SetName(&self, name: DOMString) {
|
fn SetName(&self, name: DOMString) {
|
||||||
self.window_proxy().set_name(name);
|
self.window_proxy().set_name(name);
|
||||||
|
|
|
@ -98,6 +98,9 @@ pub enum Msg {
|
||||||
|
|
||||||
/// Send to layout the precise time when the navigation started.
|
/// Send to layout the precise time when the navigation started.
|
||||||
SetNavigationStart(u64),
|
SetNavigationStart(u64),
|
||||||
|
|
||||||
|
/// Request the current number of animations that are running.
|
||||||
|
GetRunningAnimations(IpcSender<usize>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
|
|
@ -13458,6 +13458,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"mozilla/animation-removed-node.html": [
|
||||||
|
[
|
||||||
|
"/_mozilla/mozilla/animation-removed-node.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"mozilla/binding_keyword.html": [
|
"mozilla/binding_keyword.html": [
|
||||||
[
|
[
|
||||||
"/_mozilla/mozilla/binding_keyword.html",
|
"/_mozilla/mozilla/binding_keyword.html",
|
||||||
|
@ -26613,6 +26619,10 @@
|
||||||
"81de5b389c922067c61effe03208ea740ba8e067",
|
"81de5b389c922067c61effe03208ea740ba8e067",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
|
"mozilla/animation-removed-node.html": [
|
||||||
|
"6ba0318ea1e07b42ef444f838753adbefe9633d6",
|
||||||
|
"testharness"
|
||||||
|
],
|
||||||
"mozilla/binding_keyword.html": [
|
"mozilla/binding_keyword.html": [
|
||||||
"818d2aa29471026c1b4215dfcd1b9939a052b1ea",
|
"818d2aa29471026c1b4215dfcd1b9939a052b1ea",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[animation-removed-node.html]
|
||||||
|
prefs: [css.animations.testing.enabled:true]
|
28
tests/wpt/mozilla/tests/mozilla/animation-removed-node.html
Normal file
28
tests/wpt/mozilla/tests/mozilla/animation-removed-node.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<style>
|
||||||
|
@keyframes boo {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
div.test { animation: boo 1s infinite; }
|
||||||
|
</style>
|
||||||
|
<div class="test" id="first">hi there!</div>
|
||||||
|
<div class="test" id="second">hi again!</div>
|
||||||
|
<script>
|
||||||
|
async_test(function(t) {
|
||||||
|
window.onload = t.step_func(function() {
|
||||||
|
// Verify that there are the expected animations active.
|
||||||
|
assert_equals(window.runningAnimationCount, 2);
|
||||||
|
// Cause the animating nodes to become uninvolved with layout.
|
||||||
|
document.getElementById('first').remove();
|
||||||
|
document.getElementById('second').style.display = 'none';
|
||||||
|
// Ensure that we wait until the next layout is complete.
|
||||||
|
requestAnimationFrame(t.step_func(function() {
|
||||||
|
// Verify that the previous animations are no longer considered active.
|
||||||
|
assert_equals(window.runningAnimationCount, 0);
|
||||||
|
t.done();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}, "Animations are no longer active when a node can't be animated.");
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue