script: Allow reflows that do not produce display lists (#37186)

This change has two parts which depend on each other:

1. An early exit in the layout process, which allows for skipping
   display list construction entirely when nothing would change.
2. A simplification and unification of the way that "fake" animation
   frames are triggered. Now this happens on an entire ScriptThread at
   once and is based on whether or not any Pipeline triggered a display
   list update.

   Animations are never canceled in the compositor when the Pipeline
   isn't updating, instead the fake animation frame is triggered far
   enough in the future that an unexpected compositor tick will cancel
   it. This could happen, for instance, if some other Pipeline in some
   other ScriptThread produced a new display list for a tick. This makes
   everything simpler about these ticks.

The goal is that in a future change the ScriptThread-based animation
ticks will be made more generic so that they can throttle the number of
"update the rendering" calls triggered by script.

This should make Servo do a lot less work when moving the cursor over a
page. Before it would constantly produce new display lists.

Fixes: #17029.
Testing: This should not cause any web observable changes. The fact that
all WPT tests keep passing is the test for this change.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-06-12 21:25:04 +02:00 committed by GitHub
parent 29fc878e15
commit 23acb623c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 257 additions and 234 deletions

View file

@ -65,7 +65,7 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim
use script_bindings::interfaces::GlobalScopeHelpers;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use snapshot::Snapshot;
use timers::{TimerEventId, TimerEventRequest, TimerSource};
use timers::{TimerEventRequest, TimerId};
use url::Origin;
use uuid::Uuid;
#[cfg(feature = "webgpu")]
@ -150,6 +150,7 @@ use crate::task_manager::TaskManager;
use crate::task_source::SendableTaskSource;
use crate::timers::{
IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback,
TimerEventId, TimerSource,
};
use crate::unminify::unminified_path;
@ -2483,10 +2484,10 @@ impl GlobalScope {
/// Schedule a [`TimerEventRequest`] on this [`GlobalScope`]'s [`timers::TimerScheduler`].
/// Every Worker has its own scheduler, which handles events in the Worker event loop,
/// but `Window`s use a shared scheduler associated with their [`ScriptThread`].
pub(crate) fn schedule_timer(&self, request: TimerEventRequest) {
pub(crate) fn schedule_timer(&self, request: TimerEventRequest) -> Option<TimerId> {
match self.downcast::<WorkerGlobalScope>() {
Some(worker_global) => worker_global.timer_scheduler().schedule_timer(request),
_ => with_script_thread(|script_thread| script_thread.schedule_timer(request)),
Some(worker_global) => Some(worker_global.timer_scheduler().schedule_timer(request)),
_ => with_script_thread(|script_thread| Some(script_thread.schedule_timer(request))),
}
}