@@ -17,6 +17,7 @@ let runtime;
17
17
let performance ;
18
18
let cancelCallback ;
19
19
let scheduleCallback ;
20
+ let requestPaint ;
20
21
let NormalPriority ;
21
22
22
23
// The Scheduler implementation uses browser APIs like `MessageChannel` and
@@ -40,6 +41,7 @@ describe('SchedulerBrowser', () => {
40
41
cancelCallback = Scheduler . unstable_cancelCallback ;
41
42
scheduleCallback = Scheduler . unstable_scheduleCallback ;
42
43
NormalPriority = Scheduler . unstable_NormalPriority ;
44
+ requestPaint = Scheduler . unstable_requestPaint ;
43
45
} ) ;
44
46
45
47
afterEach ( ( ) => {
@@ -52,6 +54,9 @@ describe('SchedulerBrowser', () => {
52
54
53
55
function installMockBrowserRuntime ( ) {
54
56
let hasPendingMessageEvent = false ;
57
+ let isFiringMessageEvent = false ;
58
+ let hasPendingDiscreteEvent = false ;
59
+ let hasPendingContinuousEvent = false ;
55
60
56
61
let timerIDCounter = 0 ;
57
62
// let timerIDs = new Map();
@@ -94,6 +99,23 @@ describe('SchedulerBrowser', () => {
94
99
this . port2 = port2 ;
95
100
} ;
96
101
102
+ const scheduling = {
103
+ isInputPending ( options ) {
104
+ if ( this !== scheduling ) {
105
+ throw new Error (
106
+ 'isInputPending called with incorrect `this` context' ,
107
+ ) ;
108
+ }
109
+
110
+ return (
111
+ hasPendingDiscreteEvent ||
112
+ ( options && options . includeContinuous && hasPendingContinuousEvent )
113
+ ) ;
114
+ } ,
115
+ } ;
116
+
117
+ global . navigator = { scheduling} ;
118
+
97
119
function ensureLogIsEmpty ( ) {
98
120
if ( eventLog . length !== 0 ) {
99
121
throw Error ( 'Log is not empty. Call assertLog before continuing.' ) ;
@@ -102,6 +124,9 @@ describe('SchedulerBrowser', () => {
102
124
function advanceTime ( ms ) {
103
125
currentTime += ms ;
104
126
}
127
+ function resetTime ( ) {
128
+ currentTime = 0 ;
129
+ }
105
130
function fireMessageEvent ( ) {
106
131
ensureLogIsEmpty ( ) ;
107
132
if ( ! hasPendingMessageEvent ) {
@@ -110,7 +135,35 @@ describe('SchedulerBrowser', () => {
110
135
hasPendingMessageEvent = false ;
111
136
const onMessage = port1 . onmessage ;
112
137
log ( 'Message Event' ) ;
113
- onMessage ( ) ;
138
+
139
+ isFiringMessageEvent = true ;
140
+ try {
141
+ onMessage ( ) ;
142
+ } finally {
143
+ isFiringMessageEvent = false ;
144
+ if ( hasPendingDiscreteEvent ) {
145
+ log ( 'Discrete Event' ) ;
146
+ hasPendingDiscreteEvent = false ;
147
+ }
148
+ if ( hasPendingContinuousEvent ) {
149
+ log ( 'Continuous Event' ) ;
150
+ hasPendingContinuousEvent = false ;
151
+ }
152
+ }
153
+ }
154
+ function scheduleDiscreteEvent ( ) {
155
+ if ( isFiringMessageEvent ) {
156
+ hasPendingDiscreteEvent = true ;
157
+ } else {
158
+ log ( 'Discrete Event' ) ;
159
+ }
160
+ }
161
+ function scheduleContinuousEvent ( ) {
162
+ if ( isFiringMessageEvent ) {
163
+ hasPendingContinuousEvent = true ;
164
+ } else {
165
+ log ( 'Continuous Event' ) ;
166
+ }
114
167
}
115
168
function log ( val ) {
116
169
eventLog . push ( val ) ;
@@ -125,10 +178,13 @@ describe('SchedulerBrowser', () => {
125
178
}
126
179
return {
127
180
advanceTime,
181
+ resetTime,
128
182
fireMessageEvent,
129
183
log,
130
184
isLogEmpty,
131
185
assertLog,
186
+ scheduleDiscreteEvent,
187
+ scheduleContinuousEvent,
132
188
} ;
133
189
}
134
190
@@ -144,6 +200,8 @@ describe('SchedulerBrowser', () => {
144
200
it ( 'task with continuation' , ( ) => {
145
201
scheduleCallback ( NormalPriority , ( ) => {
146
202
runtime . log ( 'Task' ) ;
203
+ // Request paint so that we yield at the end of the frame interval
204
+ requestPaint ( ) ;
147
205
while ( ! Scheduler . unstable_shouldYield ( ) ) {
148
206
runtime . advanceTime ( 1 ) ;
149
207
}
@@ -259,4 +317,162 @@ describe('SchedulerBrowser', () => {
259
317
runtime . fireMessageEvent ( ) ;
260
318
runtime . assertLog ( [ 'Message Event' , 'B' ] ) ;
261
319
} ) ;
320
+
321
+ it ( 'when isInputPending is available, we can wait longer before yielding' , ( ) => {
322
+ function blockUntilSchedulerAsksToYield ( ) {
323
+ while ( ! Scheduler . unstable_shouldYield ( ) ) {
324
+ runtime . advanceTime ( 1 ) ;
325
+ }
326
+ runtime . log ( `Yield at ${ performance . now ( ) } ms` ) ;
327
+ }
328
+
329
+ // First show what happens when we don't request a paint
330
+ scheduleCallback ( NormalPriority , ( ) => {
331
+ runtime . log ( 'Task with no pending input' ) ;
332
+ blockUntilSchedulerAsksToYield ( ) ;
333
+ } ) ;
334
+ runtime . assertLog ( [ 'Post Message' ] ) ;
335
+
336
+ runtime . fireMessageEvent ( ) ;
337
+ runtime . assertLog ( [
338
+ 'Message Event' ,
339
+ 'Task with no pending input' ,
340
+ // Even though there's no input, eventually Scheduler will yield
341
+ // regardless in case there's a pending main thread task we don't know
342
+ // about, like a network event.
343
+ gate ( flags =>
344
+ flags . enableIsInputPending
345
+ ? 'Yield at 300ms'
346
+ : // When isInputPending is disabled, we always yield quickly
347
+ 'Yield at 5ms' ,
348
+ ) ,
349
+ ] ) ;
350
+
351
+ runtime . resetTime ( ) ;
352
+
353
+ // Now do the same thing, but while the task is running, simulate an
354
+ // input event.
355
+ scheduleCallback ( NormalPriority , ( ) => {
356
+ runtime . log ( 'Task with pending input' ) ;
357
+ runtime . scheduleDiscreteEvent ( ) ;
358
+ blockUntilSchedulerAsksToYield ( ) ;
359
+ } ) ;
360
+ runtime . assertLog ( [ 'Post Message' ] ) ;
361
+
362
+ runtime . fireMessageEvent ( ) ;
363
+ runtime . assertLog ( [
364
+ 'Message Event' ,
365
+ 'Task with pending input' ,
366
+ // This time we yielded quickly to unblock the discrete event.
367
+ 'Yield at 5ms' ,
368
+ 'Discrete Event' ,
369
+ ] ) ;
370
+ } ) ;
371
+
372
+ it (
373
+ 'isInputPending will also check for continuous inputs, but after a ' +
374
+ 'slightly larger threshold' ,
375
+ ( ) => {
376
+ function blockUntilSchedulerAsksToYield ( ) {
377
+ while ( ! Scheduler . unstable_shouldYield ( ) ) {
378
+ runtime . advanceTime ( 1 ) ;
379
+ }
380
+ runtime . log ( `Yield at ${ performance . now ( ) } ms` ) ;
381
+ }
382
+
383
+ // First show what happens when we don't request a paint
384
+ scheduleCallback ( NormalPriority , ( ) => {
385
+ runtime . log ( 'Task with no pending input' ) ;
386
+ blockUntilSchedulerAsksToYield ( ) ;
387
+ } ) ;
388
+ runtime . assertLog ( [ 'Post Message' ] ) ;
389
+
390
+ runtime . fireMessageEvent ( ) ;
391
+ runtime . assertLog ( [
392
+ 'Message Event' ,
393
+ 'Task with no pending input' ,
394
+ // Even though there's no input, eventually Scheduler will yield
395
+ // regardless in case there's a pending main thread task we don't know
396
+ // about, like a network event.
397
+ gate ( flags =>
398
+ flags . enableIsInputPending
399
+ ? 'Yield at 300ms'
400
+ : // When isInputPending is disabled, we always yield quickly
401
+ 'Yield at 5ms' ,
402
+ ) ,
403
+ ] ) ;
404
+
405
+ runtime . resetTime ( ) ;
406
+
407
+ // Now do the same thing, but while the task is running, simulate a
408
+ // continuous input event.
409
+ scheduleCallback ( NormalPriority , ( ) => {
410
+ runtime . log ( 'Task with continuous input' ) ;
411
+ runtime . scheduleContinuousEvent ( ) ;
412
+ blockUntilSchedulerAsksToYield ( ) ;
413
+ } ) ;
414
+ runtime . assertLog ( [ 'Post Message' ] ) ;
415
+
416
+ runtime . fireMessageEvent ( ) ;
417
+ runtime . assertLog ( [
418
+ 'Message Event' ,
419
+ 'Task with continuous input' ,
420
+ // This time we yielded quickly to unblock the continuous event. But not
421
+ // as quickly as for a discrete event.
422
+ gate ( flags =>
423
+ flags . enableIsInputPending
424
+ ? 'Yield at 50ms'
425
+ : // When isInputPending is disabled, we always yield quickly
426
+ 'Yield at 5ms' ,
427
+ ) ,
428
+ 'Continuous Event' ,
429
+ ] ) ;
430
+ } ,
431
+ ) ;
432
+
433
+ it ( 'requestPaint forces a yield at the end of the next frame interval' , ( ) => {
434
+ function blockUntilSchedulerAsksToYield ( ) {
435
+ while ( ! Scheduler . unstable_shouldYield ( ) ) {
436
+ runtime . advanceTime ( 1 ) ;
437
+ }
438
+ runtime . log ( `Yield at ${ performance . now ( ) } ms` ) ;
439
+ }
440
+
441
+ // First show what happens when we don't request a paint
442
+ scheduleCallback ( NormalPriority , ( ) => {
443
+ runtime . log ( 'Task with no paint' ) ;
444
+ blockUntilSchedulerAsksToYield ( ) ;
445
+ } ) ;
446
+ runtime . assertLog ( [ 'Post Message' ] ) ;
447
+
448
+ runtime . fireMessageEvent ( ) ;
449
+ runtime . assertLog ( [
450
+ 'Message Event' ,
451
+ 'Task with no paint' ,
452
+ gate ( flags =>
453
+ flags . enableIsInputPending
454
+ ? 'Yield at 300ms'
455
+ : // When isInputPending is disabled, we always yield quickly
456
+ 'Yield at 5ms' ,
457
+ ) ,
458
+ ] ) ;
459
+
460
+ runtime . resetTime ( ) ;
461
+
462
+ // Now do the same thing, but call requestPaint inside the task
463
+ scheduleCallback ( NormalPriority , ( ) => {
464
+ runtime . log ( 'Task with paint' ) ;
465
+ requestPaint ( ) ;
466
+ blockUntilSchedulerAsksToYield ( ) ;
467
+ } ) ;
468
+ runtime . assertLog ( [ 'Post Message' ] ) ;
469
+
470
+ runtime . fireMessageEvent ( ) ;
471
+ runtime . assertLog ( [
472
+ 'Message Event' ,
473
+ 'Task with paint' ,
474
+ // This time we yielded quickly (5ms) because we requested a paint.
475
+ 'Yield at 5ms' ,
476
+ ] ) ;
477
+ } ) ;
262
478
} ) ;
0 commit comments