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 /// 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 { pub(crate) fn needs_rendering_update(&self) -> bool {
if !self.is_fully_active() { if !self.is_fully_active() {
return false; 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 /// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually
/// updated. /// 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.last_render_opportunity_time.set(Some(Instant::now()));
self.cancel_scheduled_update_the_rendering(); self.cancel_scheduled_update_the_rendering();
self.needs_rendering_update.store(false, Ordering::Relaxed); self.needs_rendering_update.store(false, Ordering::Relaxed);
if !self.can_continue_running_inner() { if !self.can_continue_running_inner() {
return; return false;
} }
// TODO: The specification says to filter out non-renderable documents, // 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* // 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. // should be run in a task and a microtask checkpoint is always done when running tasks.
self.perform_a_microtask_checkpoint(can_gc); self.perform_a_microtask_checkpoint(can_gc);
saw_any_reflows
self.maybe_schedule_rendering_opportunity_after_rendering_update(saw_any_reflows);
} }
fn maybe_schedule_rendering_opportunity_after_rendering_update(&self, saw_any_reflows: bool) { fn maybe_schedule_rendering_opportunity_after_ipc_message(
// If there are any pending reflows and we are not having rendering opportunities &self,
// immediately after running "update the rendering," run it one more time. built_any_display_lists: bool,
if self.documents.borrow().iter().any(|(_, document)| { ) {
document.needs_rendering_update() || let needs_rendering_update = self
(saw_any_reflows && document.has_resize_observers()) .documents
}) { .borrow()
self.cancel_scheduled_update_the_rendering(); .iter()
self.schedule_update_the_rendering_timer_if_necessary(Duration::ZERO); .any(|(_, document)| document.needs_rendering_update());
} let running_animations = self.documents.borrow().iter().any(|(_, document)| {
if !saw_any_reflows &&
self.documents.borrow().iter().any(|(_, document)| {
document.is_fully_active() && document.is_fully_active() &&
!document.window().throttled() && !document.window().throttled() &&
(document.animations().running_animation_count() != 0 || (document.animations().running_animation_count() != 0 ||
document.has_active_request_animation_frame_callbacks()) 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 we are not running animations and no rendering update is
// If no document needs a rendering update, exit early to avoid doing more work. // necessary, just exit early and schedule the next rendering update
if !self // when it becomes necessary.
.documents if !needs_rendering_update && !running_animations {
.borrow()
.iter()
.any(|(_, document)| document.needs_rendering_update())
{
return; return;
} }
// Wait 20 milliseconds between frames triggered by the script thread itself. This // If animations are running and a reflow in this event loop iteration
// should, in theory, allow compositor-based ticks to arrive sooner. // produced a display list, rely on the renderer to inform us of the
const SCRIPT_THREAD_ANIMATION_TICK_DELAY: Duration = Duration::from_millis(20); // 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 let time_since_last_rendering_opportunity = self
.last_render_opportunity_time .last_render_opportunity_time
.get() .get()
.map(|last_render_opportunity_time| Instant::now() - last_render_opportunity_time) .map(|last_render_opportunity_time| Instant::now() - last_render_opportunity_time)
.unwrap_or(Duration::MAX) .unwrap_or(Duration::MAX)
.min(SCRIPT_THREAD_ANIMATION_TICK_DELAY); .min(animation_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;
//}
self.schedule_update_the_rendering_timer_if_necessary( 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(); 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.update_the_rendering(can_gc);
}
self.maybe_fulfill_font_ready_promises(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(); self.maybe_send_document_state_messages();
true true