script: Update animations once per-Document during update_the_rendering() (#34489)

Before, `update_the_rendering()` would update all animations for all
Documents once per-Document. Apart from being generally wrong, the
specification says this should be done once per-Document. This
theoretically means that `update_the_rendering()` is just doing less
work every time it runs.

In addition:
 - Don't redirty animations nodes when running rAF callbacks. They
   should already be dirty when animations are updated.
 - Perform a microtask checkpoint while updating animations as dictacted
   by the specification.
 - Update comments to reflect the specification text.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2024-12-05 17:37:59 +01:00 committed by GitHub
parent 1591a3b506
commit 54761b4f32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 60 deletions

View file

@ -457,7 +457,12 @@ impl Animations {
});
}
/// An implementation of the final steps of
/// <https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events>.
pub(crate) fn send_pending_events(&self, window: &Window, can_gc: CanGc) {
// > 4. Let events to dispatch be a copy of docs pending animation event queue.
// > 5. Clear docs pending animation event queue.
//
// Take all of the events here, in case sending one of these events
// triggers adding new events by forcing a layout.
let events = std::mem::take(&mut *self.pending_events.borrow_mut());
@ -465,6 +470,18 @@ impl Animations {
return;
}
// > 6. Perform a stable sort of the animation events in events to dispatch as follows:
// > 1. Sort the events by their scheduled event time such that events that were
// > scheduled to occur earlier sort before events scheduled to occur later, and
// > events whose scheduled event time is unresolved sort before events with a
// > resolved scheduled event time.
// > 2. Within events with equal scheduled event times, sort by their composite
// > order.
//
// TODO: Sorting of animation events isn't done yet.
// 7. Dispatch each of the events in events to dispatch at their corresponding
// target using the order established in the previous step.
for event in events.into_iter() {
// We root the node here to ensure that sending this event doesn't
// unroot it as a side-effect.

View file

@ -182,7 +182,7 @@ use crate::dom::window::{ReflowReason, Window};
use crate::dom::windowproxy::WindowProxy;
use crate::fetch::FetchCanceller;
use crate::network_listener::{NetworkListener, PreInvoke};
use crate::realms::{AlreadyInRealm, InRealm};
use crate::realms::{enter_realm, AlreadyInRealm, InRealm};
use crate::script_runtime::{CanGc, CommonScriptMsg, ScriptThreadEventCategory};
use crate::script_thread::{MainThreadScriptMsg, ScriptThread};
use crate::stylesheet_set::StylesheetSetRef;
@ -2054,7 +2054,7 @@ impl Document {
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe>
pub fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 {
pub(crate) fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 {
let ident = self.animation_frame_ident.get() + 1;
self.animation_frame_ident.set(ident);
@ -2089,7 +2089,7 @@ impl Document {
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe>
pub fn cancel_animation_frame(&self, ident: u32) {
pub(crate) fn cancel_animation_frame(&self, ident: u32) {
let mut list = self.animation_frame_list.borrow_mut();
if let Some(pair) = list.iter_mut().find(|pair| pair.0 == ident) {
pair.1 = None;
@ -2097,7 +2097,7 @@ impl Document {
}
/// <https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks>
pub fn run_the_animation_frame_callbacks(&self, can_gc: CanGc) {
pub(crate) fn run_the_animation_frame_callbacks(&self, can_gc: CanGc) {
rooted_vec!(let mut animation_frame_list);
mem::swap(
&mut *animation_frame_list,
@ -3450,29 +3450,6 @@ impl Document {
}
}
/// Note a pending animation tick, to be processed at the next `update_the_rendering` task.
pub fn note_pending_animation_tick(&self, tick_type: AnimationTickType) {
self.pending_animation_ticks.borrow_mut().extend(tick_type);
}
/// Whether this document has received an animation tick for rafs.
pub fn has_received_raf_tick(&self) -> bool {
self.pending_animation_ticks
.borrow()
.contains(AnimationTickType::REQUEST_ANIMATION_FRAME)
}
/// As part of a `update_the_rendering` task, tick all pending animations.
pub fn tick_all_animations(&self, should_run_rafs: bool, can_gc: CanGc) {
let tick_type = mem::take(&mut *self.pending_animation_ticks.borrow_mut());
if should_run_rafs {
self.run_the_animation_frame_callbacks(can_gc);
}
if tick_type.contains(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS) {
self.maybe_mark_animating_nodes_as_dirty();
}
}
/// Note a pending compositor event, to be processed at the next `update_the_rendering` task.
pub fn note_pending_compositor_event(&self, event: CompositorEvent) {
let mut pending_compositor_events = self.pending_compositor_events.borrow_mut();
@ -4162,22 +4139,20 @@ impl Document {
.collect()
}
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) {
self.animation_timeline.borrow_mut().advance_specific(delta);
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow()
.update_for_new_timeline_value(&self.window, current_timeline_value);
/// Note a pending animation tick, to be processed at the next `update_the_rendering` task.
pub(crate) fn note_pending_animation_tick(&self, tick_type: AnimationTickType) {
self.pending_animation_ticks.borrow_mut().extend(tick_type);
}
pub(crate) fn update_animation_timeline(&self) {
// Only update the time if it isn't being managed by a test.
if !pref!(layout.animations.test.enabled) {
self.animation_timeline.borrow_mut().update();
}
/// Whether this document has received an animation tick for rafs.
pub(crate) fn has_received_raf_tick(&self) -> bool {
self.pending_animation_ticks
.borrow()
.contains(AnimationTickType::REQUEST_ANIMATION_FRAME)
}
// We still want to update the animations, because our timeline
// value might have been advanced previously via the TestBinding.
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) {
self.animation_timeline.borrow_mut().advance_specific(delta);
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow()
@ -4214,6 +4189,35 @@ impl Document {
self.animations.borrow().cancel_animations_for_node(node);
}
/// An implementation of <https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events>.
pub(crate) fn update_animations_and_send_events(&self, can_gc: CanGc) {
// Only update the time if it isn't being managed by a test.
if !pref!(layout.animations.test.enabled) {
self.animation_timeline.borrow_mut().update();
}
// > 1. Update the current time of all timelines associated with doc passing now
// > as the timestamp.
// > 2. Remove replaced animations for doc.
//
// We still want to update the animations, because our timeline
// value might have been advanced previously via the TestBinding.
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow()
.update_for_new_timeline_value(&self.window, current_timeline_value);
self.maybe_mark_animating_nodes_as_dirty();
// > 3. Perform a microtask checkpoint.
self.window()
.upcast::<GlobalScope>()
.perform_a_microtask_checkpoint(can_gc);
// Steps 4 through 7 occur inside `send_pending_events().`
let _realm = enter_realm(self);
self.animations().send_pending_events(self.window(), can_gc);
}
pub(crate) fn will_declaratively_refresh(&self) -> bool {
self.declarative_refresh.borrow().is_some()
}

View file

@ -1665,8 +1665,10 @@ impl ScriptThread {
document.react_to_environment_changes()
}
// Update animations and send events.
self.update_animations_and_send_events(can_gc);
// > 11. For each doc of docs, update animations and send events for doc, passing
// > in relative high resolution time given frameTimestamp and doc's relevant
// > global object as the timestamp [WEBANIMATIONS]
document.update_animations_and_send_events(can_gc);
// TODO(#31866): Implement "run the fullscreen steps" from
// https://fullscreen.spec.whatwg.org/multipage/#run-the-fullscreen-steps.
@ -1674,8 +1676,12 @@ impl ScriptThread {
// TODO(#31868): Implement the "context lost steps" from
// https://html.spec.whatwg.org/multipage/#context-lost-steps.
// Run the animation frame callbacks.
document.tick_all_animations(should_run_rafs, can_gc);
// > 14. For each doc of docs, run the animation frame callbacks for doc, passing
// > in the relative high resolution time given frameTimestamp and doc's
// > relevant global object as the timestamp.
if should_run_rafs {
document.run_the_animation_frame_callbacks(can_gc);
}
// Run the resize observer steps.
let _realm = enter_realm(&*document);
@ -2050,22 +2056,6 @@ impl ScriptThread {
true
}
// Perform step 7.10 from https://html.spec.whatwg.org/multipage/#event-loop-processing-model.
// Described at: https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
fn update_animations_and_send_events(&self, can_gc: CanGc) {
for (_, document) in self.documents.borrow().iter() {
document.update_animation_timeline();
document.maybe_mark_animating_nodes_as_dirty();
}
for (_, document) in self.documents.borrow().iter() {
let _realm = enter_realm(&*document);
document
.animations()
.send_pending_events(document.window(), can_gc);
}
}
fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
match *msg {
MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg {