@@ -211,6 +211,12 @@ mod feat_hydration {
211211pub ( crate ) use feat_hydration:: * ;
212212
213213/// Execute any pending [Runnable]s
214+ #[ cfg( any(
215+ not( target_arch = "wasm32" ) ,
216+ target_os = "wasi" ,
217+ feature = "not_browser_env" ,
218+ test
219+ ) ) ]
214220pub ( crate ) fn start_now ( ) {
215221 #[ tracing:: instrument( level = tracing:: Level :: DEBUG ) ]
216222 fn scheduler_loop ( ) {
@@ -270,7 +276,7 @@ mod arch {
270276 check_scheduled ( )
271277 }
272278
273- const YIELD_DEADLINE_MS : f64 = 50 .0;
279+ const YIELD_DEADLINE_MS : f64 = 16 .0;
274280
275281 #[ wasm_bindgen]
276282 extern "C" {
@@ -279,7 +285,7 @@ mod arch {
279285 }
280286
281287 fn run_scheduler ( mut queue : Vec < super :: QueueEntry > ) {
282- let mut deadline = js_sys:: Date :: now ( ) + YIELD_DEADLINE_MS ;
288+ let deadline = js_sys:: Date :: now ( ) + YIELD_DEADLINE_MS ;
283289
284290 loop {
285291 super :: with ( |s| s. fill_queue ( & mut queue) ) ;
@@ -289,20 +295,27 @@ mod arch {
289295 for r in queue. drain ( ..) {
290296 r. task . run ( ) ;
291297 }
292- let now = js_sys:: Date :: now ( ) ;
293- if now >= deadline {
294- let cb = Closure :: once_into_js ( move || run_scheduler ( queue) ) ;
295- set_timeout ( cb. unchecked_ref ( ) , 0 ) ;
296- return ;
298+ if js_sys:: Date :: now ( ) >= deadline {
299+ // Only yield when no DOM-mutating work is pending, so event
300+ // handlers that fire during the yield see a consistent DOM.
301+ let can_yield = super :: with ( |s| s. can_yield ( ) ) ;
302+ if can_yield {
303+ let cb = Closure :: once_into_js ( move || run_scheduler ( queue) ) ;
304+ set_timeout ( cb. unchecked_ref ( ) , 0 ) ;
305+ return ;
306+ }
297307 }
298308 }
299309
300310 set_scheduled ( false ) ;
311+ #[ cfg( any( test, feature = "test" ) ) ]
312+ super :: flush_wakers:: wake_all ( ) ;
301313 }
302314
303315 /// We delay the start of the scheduler to the end of the micro task queue.
304316 /// So any messages that needs to be queued can be queued.
305- /// Once running, we yield to the browser every ~50ms to avoid long tasks.
317+ /// Once running, we yield to the browser every ~16ms, but only at points
318+ /// where the DOM is in a consistent state (no pending renders/destroys).
306319 pub ( crate ) fn start ( ) {
307320 if check_scheduled ( ) {
308321 return ;
@@ -378,6 +391,21 @@ pub async fn flush() {
378391}
379392
380393impl Scheduler {
394+ /// Returns true when no DOM-mutating work is pending, meaning it's safe to
395+ /// yield to the browser without leaving the DOM in an inconsistent state.
396+ #[ cfg( all(
397+ target_arch = "wasm32" ,
398+ not( target_os = "wasi" ) ,
399+ not( feature = "not_browser_env" )
400+ ) ) ]
401+ fn can_yield ( & self ) -> bool {
402+ self . destroy . inner . is_empty ( )
403+ && self . create . inner . is_empty ( )
404+ && self . render_first . inner . is_empty ( )
405+ && self . render . inner . is_empty ( )
406+ && self . render_priority . inner . is_empty ( )
407+ }
408+
381409 /// Fill vector with tasks to be executed according to Runnable type execution priority
382410 ///
383411 /// This method is optimized for typical usage, where possible, but does not break on
0 commit comments