Simplify scheduling of post-message loop "update the rendering"

This commit is contained in:
Martin Robinson 2025-08-03 18:17:30 +02:00
parent 48e14be7ff
commit bbad1f656a
2 changed files with 48 additions and 54 deletions

View file

@ -3657,7 +3657,8 @@ impl Document {
}
/// Whether or not this [`Document`] needs a rendering update, due to changed
/// contents or pending events.
/// contents or pending events. This is used to decide whether or not to schedule
/// a call to the "update the rendering" algorithm.
pub(crate) fn needs_rendering_update(&self) -> bool {
if !self.is_fully_active() {
return false;

View file

@ -1243,13 +1243,13 @@ impl ScriptThread {
///
/// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually
/// updated.
pub(crate) fn update_the_rendering(&self, can_gc: CanGc) {
pub(crate) fn update_the_rendering(&self, can_gc: CanGc) -> bool {
self.last_render_opportunity_time.set(Some(Instant::now()));
self.cancel_scheduled_update_the_rendering();
self.needs_rendering_update.store(false, Ordering::Relaxed);
if !self.can_continue_running_inner() {
return;
return false;
}
// TODO: The specification says to filter out non-renderable documents,
@ -1371,68 +1371,62 @@ impl ScriptThread {
// Perform a microtask checkpoint as the specifications says that *update the rendering*
// should be run in a task and a microtask checkpoint is always done when running tasks.
self.perform_a_microtask_checkpoint(can_gc);
self.maybe_schedule_rendering_opportunity_after_rendering_update(saw_any_reflows);
saw_any_reflows
}
fn maybe_schedule_rendering_opportunity_after_rendering_update(&self, saw_any_reflows: bool) {
// If there are any pending reflows and we are not having rendering opportunities
// immediately after running "update the rendering," run it one more time.
if self.documents.borrow().iter().any(|(_, document)| {
document.needs_rendering_update() ||
(saw_any_reflows && document.has_resize_observers())
}) {
self.cancel_scheduled_update_the_rendering();
self.schedule_update_the_rendering_timer_if_necessary(Duration::ZERO);
}
if !saw_any_reflows &&
self.documents.borrow().iter().any(|(_, document)| {
fn maybe_schedule_rendering_opportunity_after_ipc_message(
&self,
built_any_display_lists: bool,
) {
let needs_rendering_update = self
.documents
.borrow()
.iter()
.any(|(_, document)| document.needs_rendering_update());
let running_animations = self.documents.borrow().iter().any(|(_, document)| {
document.is_fully_active() &&
!document.window().throttled() &&
(document.animations().running_animation_count() != 0 ||
document.has_active_request_animation_frame_callbacks())
})
{
const SCRIPT_THREAD_ANIMATION_TICK_DELAY: Duration = Duration::from_millis(30);
self.schedule_update_the_rendering_timer_if_necessary(
SCRIPT_THREAD_ANIMATION_TICK_DELAY,
);
}
}
});
fn maybe_schedule_rendering_opportunity_after_ipc_message(&self) {
// If no document needs a rendering update, exit early to avoid doing more work.
if !self
.documents
.borrow()
.iter()
.any(|(_, document)| document.needs_rendering_update())
{
// If we are not running animations and no rendering update is
// necessary, just exit early and schedule the next rendering update
// when it becomes necessary.
if !needs_rendering_update && !running_animations {
return;
}
// Wait 20 milliseconds between frames triggered by the script thread itself. This
// should, in theory, allow compositor-based ticks to arrive sooner.
const SCRIPT_THREAD_ANIMATION_TICK_DELAY: Duration = Duration::from_millis(20);
// If animations are running and a reflow in this event loop iteration
// produced a display list, rely on the renderer to inform us of the
// next animation tick / rendering opportunity.
if running_animations && built_any_display_lists {
return;
}
// There are two possibilities: rendering needs to be updated or we are
// scheduling a new animation tick because animations are running, but
// not changing the DOM. In the later case we can wait a bit longer
// until the next "update the rendering" call as it's more efficient to
// slow down rAFs that don't change the DOM.
let animation_delay = if running_animations && !needs_rendering_update {
Duration::from_millis(30)
} else {
// 20 milliseconds is used here in order to allow any renderer-based
// animation ticks to arrive first.
//
// TODO: Should this be faster to reduce update latency?
Duration::from_millis(20)
};
let time_since_last_rendering_opportunity = self
.last_render_opportunity_time
.get()
.map(|last_render_opportunity_time| Instant::now() - last_render_opportunity_time)
.unwrap_or(Duration::MAX)
.min(SCRIPT_THREAD_ANIMATION_TICK_DELAY);
//eprintln!(" - scheduling update\n");
//// If it's been more than the time of a single frame the last rendering opportunity,
//// just run it now.
//if time_since_last_rendering_opportunity > SCRIPT_THREAD_ANIMATION_TICK_DELAY {
// println!(" - running immediately");
// self.update_the_rendering(can_gc);
// return;
//}
.min(animation_delay);
self.schedule_update_the_rendering_timer_if_necessary(
SCRIPT_THREAD_ANIMATION_TICK_DELAY - time_since_last_rendering_opportunity,
animation_delay - time_since_last_rendering_opportunity,
);
}
@ -1667,12 +1661,11 @@ impl ScriptThread {
docs.clear();
}
if self.needs_rendering_update.load(Ordering::Relaxed) {
let built_any_display_lists = self.needs_rendering_update.load(Ordering::Relaxed) &&
self.update_the_rendering(can_gc);
}
self.maybe_fulfill_font_ready_promises(can_gc);
self.maybe_schedule_rendering_opportunity_after_ipc_message();
self.maybe_schedule_rendering_opportunity_after_ipc_message(built_any_display_lists);
self.maybe_send_document_state_messages();
true