clean up isInputPending in Scheduler (#28444)
## Summary `isInputPending` is not in use. This PR cleans up the flags controlling its gating and parameters to simplify Scheduler. Makes `frameYieldMs` feature flag static, set to 10ms in www, which we found built on the wins provided by a broader yield interval via `isInputPending`. Flag remains set to 5ms in OSS builds. ## How did you test this change? `yarn test Scheduler`
Noah Lemen committed
Feb 27, 2024 at 10:39 UTC
3bcd2de01b5716202eabe8faa338f51bdc59ce26
7 files changed
+7
-265
packages/scheduler/src/SchedulerFeatureFlags.js
-4
@@ -8,12 +8,8 @@
8
*/
9
10
export const enableSchedulerDebugging = false;
11
-export const enableIsInputPending = false;
11
export const enableProfiling = false;
13
-export const enableIsInputPendingContinuous = false;
12
export const frameYieldMs = 5;
15
-export const continuousYieldMs = 50;
16
-export const maxYieldMs = 300;
13
14
export const userBlockingPriorityTimeout = 250;
15
export const normalPriorityTimeout = 5000;
packages/scheduler/src/__tests__/Scheduler-test.js
+1
-176
@@ -101,23 +101,6 @@ describe('SchedulerBrowser', () => {
101
this.port2 = port2;
102
};
103
104
- const scheduling = {
105
- isInputPending(options) {
106
- if (this !== scheduling) {
107
- throw new Error(
108
- 'isInputPending called with incorrect `this` context',
109
- );
110
- }
111
-
112
- return (
113
- hasPendingDiscreteEvent ||
114
- (options && options.includeContinuous && hasPendingContinuousEvent)
115
- );
116
- },
117
- };
118
-
119
- global.navigator = {scheduling};
120
-
104
function ensureLogIsEmpty() {
105
if (eventLog.length !== 0) {
106
throw Error('Log is not empty. Call assertLog before continuing.');
@@ -218,7 +201,7 @@ describe('SchedulerBrowser', () => {
201
runtime.assertLog([
202
'Message Event',
203
'Task',
221
- 'Yield at 5ms',
204
+ gate(flags => (flags.www ? 'Yield at 10ms' : 'Yield at 5ms')),
205
'Post Message',
206
]);
207
@@ -320,164 +303,6 @@ describe('SchedulerBrowser', () => {
303
runtime.assertLog(['Message Event', 'B']);
304
});
305
323
- it('when isInputPending is available, we can wait longer before yielding', () => {
324
- function blockUntilSchedulerAsksToYield() {
325
- while (!Scheduler.unstable_shouldYield()) {
326
- runtime.advanceTime(1);
327
- }
328
- runtime.log(`Yield at ${performance.now()}ms`);
329
- }
330
-
331
- // First show what happens when we don't request a paint
332
- scheduleCallback(NormalPriority, () => {
333
- runtime.log('Task with no pending input');
334
- blockUntilSchedulerAsksToYield();
335
- });
336
- runtime.assertLog(['Post Message']);
337
-
338
- runtime.fireMessageEvent();
339
- runtime.assertLog([
340
- 'Message Event',
341
- 'Task with no pending input',
342
- // Even though there's no input, eventually Scheduler will yield
343
- // regardless in case there's a pending main thread task we don't know
344
- // about, like a network event.
345
- gate(flags =>
346
- flags.enableIsInputPending
347
- ? 'Yield at 10ms'
348
- : // When isInputPending is disabled, we always yield quickly
349
- 'Yield at 5ms',
350
- ),
351
- ]);
352
-
353
- runtime.resetTime();
354
-
355
- // Now do the same thing, but while the task is running, simulate an
356
- // input event.
357
- scheduleCallback(NormalPriority, () => {
358
- runtime.log('Task with pending input');
359
- runtime.scheduleDiscreteEvent();
360
- blockUntilSchedulerAsksToYield();
361
- });
362
- runtime.assertLog(['Post Message']);
363
-
364
- runtime.fireMessageEvent();
365
- runtime.assertLog([
366
- 'Message Event',
367
- 'Task with pending input',
368
- // This time we yielded quickly to unblock the discrete event.
369
- 'Yield at 5ms',
370
- 'Discrete Event',
371
- ]);
372
- });
373
-
374
- it(
375
- 'isInputPending will also check for continuous inputs, but after a ' +
376
- 'slightly larger threshold',
377
- () => {
378
- function blockUntilSchedulerAsksToYield() {
379
- while (!Scheduler.unstable_shouldYield()) {
380
- runtime.advanceTime(1);
381
- }
382
- runtime.log(`Yield at ${performance.now()}ms`);
383
- }
384
-
385
- // First show what happens when we don't request a paint
386
- scheduleCallback(NormalPriority, () => {
387
- runtime.log('Task with no pending input');
388
- blockUntilSchedulerAsksToYield();
389
- });
390
- runtime.assertLog(['Post Message']);
391
-
392
- runtime.fireMessageEvent();
393
- runtime.assertLog([
394
- 'Message Event',
395
- 'Task with no pending input',
396
- // Even though there's no input, eventually Scheduler will yield
397
- // regardless in case there's a pending main thread task we don't know
398
- // about, like a network event.
399
- gate(flags =>
400
- flags.enableIsInputPending
401
- ? 'Yield at 10ms'
402
- : // When isInputPending is disabled, we always yield quickly
403
- 'Yield at 5ms',
404
- ),
405
- ]);
406
-
407
- runtime.resetTime();
408
-
409
- // Now do the same thing, but while the task is running, simulate a
410
- // continuous input event.
411
- scheduleCallback(NormalPriority, () => {
412
- runtime.log('Task with continuous input');
413
- runtime.scheduleContinuousEvent();
414
- blockUntilSchedulerAsksToYield();
415
- });
416
- runtime.assertLog(['Post Message']);
417
-
418
- runtime.fireMessageEvent();
419
- runtime.assertLog([
420
- 'Message Event',
421
- 'Task with continuous input',
422
- // This time we yielded quickly to unblock the continuous event. But not
423
- // as quickly as for a discrete event.
424
- gate(flags =>
425
- flags.enableIsInputPending
426
- ? 'Yield at 10ms'
427
- : // When isInputPending is disabled, we always yield quickly
428
- 'Yield at 5ms',
429
- ),
430
- 'Continuous Event',
431
- ]);
432
- },
433
- );
434
-
435
- it('requestPaint forces a yield at the end of the next frame interval', () => {
436
- function blockUntilSchedulerAsksToYield() {
437
- while (!Scheduler.unstable_shouldYield()) {
438
- runtime.advanceTime(1);
439
- }
440
- runtime.log(`Yield at ${performance.now()}ms`);
441
- }
442
-
443
- // First show what happens when we don't request a paint
444
- scheduleCallback(NormalPriority, () => {
445
- runtime.log('Task with no paint');
446
- blockUntilSchedulerAsksToYield();
447
- });
448
- runtime.assertLog(['Post Message']);
449
-
450
- runtime.fireMessageEvent();
451
- runtime.assertLog([
452
- 'Message Event',
453
- 'Task with no paint',
454
- gate(flags =>
455
- flags.enableIsInputPending
456
- ? 'Yield at 10ms'
457
- : // When isInputPending is disabled, we always yield quickly
458
- 'Yield at 5ms',
459
- ),
460
- ]);
461
-
462
- runtime.resetTime();
463
-
464
- // Now do the same thing, but call requestPaint inside the task
465
- scheduleCallback(NormalPriority, () => {
466
- runtime.log('Task with paint');
467
- requestPaint();
468
- blockUntilSchedulerAsksToYield();
469
- });
470
- runtime.assertLog(['Post Message']);
471
-
472
- runtime.fireMessageEvent();
473
- runtime.assertLog([
474
- 'Message Event',
475
- 'Task with paint',
476
- // This time we yielded quickly (5ms) because we requested a paint.
477
- 'Yield at 5ms',
478
- ]);
479
- });
480
-
306
it('yielding continues in a new task regardless of how much time is remaining', () => {
307
scheduleCallback(NormalPriority, () => {
308
runtime.log('Original Task');
packages/scheduler/src/__tests__/SchedulerSetImmediate-test.js
+1
-1
@@ -173,7 +173,7 @@ describe('SchedulerDOMSetImmediate', () => {
173
runtime.assertLog([
174
'setImmediate Callback',
175
'Task',
176
- 'Yield at 5ms',
176
+ gate(flags => (flags.www ? 'Yield at 10ms' : 'Yield at 5ms')),
177
'Set Immediate',
178
]);
179
packages/scheduler/src/forks/Scheduler.js
+2
-71
@@ -14,11 +14,7 @@ import type {PriorityLevel} from '../SchedulerPriorities';
14
import {
15
enableSchedulerDebugging,
16
enableProfiling,
17
- enableIsInputPending,
18
- enableIsInputPendingContinuous,
17
frameYieldMs,
20
- continuousYieldMs,
21
- maxYieldMs,
18
userBlockingPriorityTimeout,
19
lowPriorityTimeout,
20
normalPriorityTimeout,
@@ -104,17 +100,6 @@ const localClearTimeout =
100
const localSetImmediate =
101
typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
102
107
-const isInputPending =
108
- typeof navigator !== 'undefined' &&
109
- // $FlowFixMe[prop-missing]
110
- navigator.scheduling !== undefined &&
111
- // $FlowFixMe[incompatible-type]
112
- navigator.scheduling.isInputPending !== undefined
113
- ? navigator.scheduling.isInputPending.bind(navigator.scheduling)
114
- : null;
115
-
116
-const continuousOptions = {includeContinuous: enableIsInputPendingContinuous};
117
-
103
function advanceTimers(currentTime: number) {
104
// Check for tasks that are no longer delayed and add them to the queue.
105
let timer = peek(timerQueue);
@@ -468,12 +453,8 @@ let taskTimeoutID: TimeoutID = (-1: any);
453
// It does not attempt to align with frame boundaries, since most tasks don't
454
// need to be frame aligned; for those that do, use requestAnimationFrame.
455
let frameInterval = frameYieldMs;
471
-const continuousInputInterval = continuousYieldMs;
472
-const maxInterval = maxYieldMs;
456
let startTime = -1;
457
475
-let needsPaint = false;
476
-
458
function shouldYieldToHost(): boolean {
459
const timeElapsed = getCurrentTime() - startTime;
460
if (timeElapsed < frameInterval) {
@@ -481,58 +462,11 @@ function shouldYieldToHost(): boolean {
462
// smaller than a single frame. Don't yield yet.
463
return false;
464
}
484
-
485
- // The main thread has been blocked for a non-negligible amount of time. We
486
- // may want to yield control of the main thread, so the browser can perform
487
- // high priority tasks. The main ones are painting and user input. If there's
488
- // a pending paint or a pending input, then we should yield. But if there's
489
- // neither, then we can yield less often while remaining responsive. We'll
490
- // eventually yield regardless, since there could be a pending paint that
491
- // wasn't accompanied by a call to `requestPaint`, or other main thread tasks
492
- // like network events.
493
- if (enableIsInputPending) {
494
- if (needsPaint) {
495
- // There's a pending paint (signaled by `requestPaint`). Yield now.
496
- return true;
497
- }
498
- if (timeElapsed < continuousInputInterval) {
499
- // We haven't blocked the thread for that long. Only yield if there's a
500
- // pending discrete input (e.g. click). It's OK if there's pending
501
- // continuous input (e.g. mouseover).
502
- if (isInputPending !== null) {
503
- return isInputPending();
504
- }
505
- } else if (timeElapsed < maxInterval) {
506
- // Yield if there's either a pending discrete or continuous input.
507
- if (isInputPending !== null) {
508
- return isInputPending(continuousOptions);
509
- }
510
- } else {
511
- // We've blocked the thread for a long time. Even if there's no pending
512
- // input, there may be some other scheduled work that we don't know about,
513
- // like a network event. Yield now.
514
- return true;
515
- }
516
- }
517
-
518
- // `isInputPending` isn't available. Yield now.
465
+ // Yield now.
466
return true;
467
}
468
522
-function requestPaint() {
523
- if (
524
- enableIsInputPending &&
525
- navigator !== undefined &&
526
- // $FlowFixMe[prop-missing]
527
- navigator.scheduling !== undefined &&
528
- // $FlowFixMe[incompatible-type]
529
- navigator.scheduling.isInputPending !== undefined
530
- ) {
531
- needsPaint = true;
532
- }
533
-
534
- // Since we yield every frame regardless, `requestPaint` has no effect.
535
-}
469
+function requestPaint() {}
470
471
function forceFrameRate(fps: number) {
472
if (fps < 0 || fps > 125) {
@@ -577,9 +511,6 @@ const performWorkUntilDeadline = () => {
511
}
512
}
513
}
580
- // Yielding to the browser will give it a chance to paint, so we can
581
- // reset this.
582
- needsPaint = false;
514
};
515
516
let schedulePerformWorkUntilDeadline;
packages/scheduler/src/forks/SchedulerFeatureFlags.www-dynamic.js
-6
@@ -13,12 +13,6 @@
13
14
export const enableProfiling = __VARIANT__;
15
16
-export const enableIsInputPending = __VARIANT__;
17
-export const enableIsInputPendingContinuous = __VARIANT__;
18
-export const frameYieldMs = 5;
19
-export const continuousYieldMs = 10;
20
-export const maxYieldMs = 10;
21
-
16
export const userBlockingPriorityTimeout = 250;
17
export const normalPriorityTimeout = 5000;
18
export const lowPriorityTimeout = 10000;
packages/scheduler/src/forks/SchedulerFeatureFlags.www.js
+2
-5
@@ -16,12 +16,9 @@ export const {
16
userBlockingPriorityTimeout,
17
normalPriorityTimeout,
18
lowPriorityTimeout,
19
- enableIsInputPending,
20
- enableIsInputPendingContinuous,
21
- frameYieldMs,
22
- continuousYieldMs,
23
- maxYieldMs,
19
} = dynamicFeatureFlags;
20
+
21
+export const frameYieldMs = 10;
22
export const enableSchedulerDebugging = true;
23
export const enableProfiling: boolean =
24
__PROFILE__ && enableProfilingFeatureFlag;
packages/scheduler/src/forks/SchedulerPostTask.js
+1
-2
@@ -57,8 +57,7 @@ let deadline = 0;
57
58
let currentPriorityLevel_DEPRECATED = NormalPriority;
59
60
-// `isInputPending` is not available. Since we have no way of knowing if
61
-// there's pending input, always yield at the end of the frame.
60
+// Always yield at the end of the frame.
61
export function unstable_shouldYield(): boolean {
62
return getCurrentTime() >= deadline;
63
}