From bbad1f656a5e556f07daecc7c33a1decd34234d8 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Sun, 3 Aug 2025 18:17:30 +0200 Subject: [PATCH] Simplify scheduling of post-message loop "update the rendering" --- components/script/dom/document.rs | 3 +- components/script/script_thread.rs | 99 ++++++++++++++---------------- 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 88325f10654..f391564dc4a 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -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; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 3a527f7ef5c..4e3456c11c8 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -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)| { - 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 + 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()) - { + .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()) + }); + + // 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