91
const localSetImmediate =
92
typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
93
94
+const isInputPending =
95
+ typeof navigator !== 'undefined' &&
96
+ navigator.scheduling !== undefined &&
97
+ navigator.scheduling.isInputPending !== undefined
98
+ ? navigator.scheduling.isInputPending.bind(navigator.scheduling)
99
+ : null;
100
+
101
+const continuousOptions = {includeContinuous: true};
102
+
103
function advanceTimers(currentTime) {
104
// Check for tasks that are no longer delayed and add them to the queue.
105
let timer = peek(timerQueue);
427
// thread, like user events. By default, it yields multiple times per frame.
428
// It does not attempt to align with frame boundaries, since most tasks don't
429
// need to be frame aligned; for those that do, use requestAnimationFrame.
421
-let yieldInterval = 5;
422
-let deadline = 0;
430
+// TODO: Make these configurable
431
+let frameInterval = 5;
432
+const continuousInputInterval = 50;
433
+const maxInterval = 300;
434
+let startTime = -1;
435
424
-// TODO: Make this configurable
425
-// TODO: Adjust this based on priority?
426
-const maxYieldInterval = 300;
436
let needsPaint = false;
437
438
function shouldYieldToHost() {
430
- if (
431
- enableIsInputPending &&
432
- navigator !== undefined &&
433
- navigator.scheduling !== undefined &&
434
- navigator.scheduling.isInputPending !== undefined
435
- ) {
436
- const scheduling = navigator.scheduling;
437
- const currentTime = getCurrentTime();
438
- if (currentTime >= deadline) {
439
- // There's no time left. We may want to yield control of the main
440
- // thread, so the browser can perform high priority tasks. The main ones
441
- // are painting and user input. If there's a pending paint or a pending
442
- // input, then we should yield. But if there's neither, then we can
443
- // yield less often while remaining responsive. We'll eventually yield
444
- // regardless, since there could be a pending paint that wasn't
445
- // accompanied by a call to `requestPaint`, or other main thread tasks
446
- // like network events.
447
- if (needsPaint || scheduling.isInputPending()) {
448
- // There is either a pending paint or a pending input.
449
- return true;
439
+ const timeElapsed = getCurrentTime() - startTime;
440
+ if (timeElapsed < frameInterval) {
441
+ // The main thread has only been blocked for a really short amount of time;
442
+ // smaller than a single frame. Don't yield yet.
443
+ return false;
444
+ }
445
+
446
+ // The main thread has been blocked for a non-negligible amount of time. We
447
+ // may want to yield control of the main thread, so the browser can perform
448
+ // high priority tasks. The main ones are painting and user input. If there's
449
+ // a pending paint or a pending input, then we should yield. But if there's
450
+ // neither, then we can yield less often while remaining responsive. We'll
451
+ // eventually yield regardless, since there could be a pending paint that
452
+ // wasn't accompanied by a call to `requestPaint`, or other main thread tasks
453
+ // like network events.
454
+ if (enableIsInputPending) {
455
+ if (needsPaint) {
456
+ // There's a pending paint (signaled by `requestPaint`). Yield now.
457
+ return true;
458
+ }
459
+ if (timeElapsed < continuousInputInterval) {
460
+ // We haven't blocked the thread for that long. Only yield if there's a
461
+ // pending discrete input (e.g. click). It's OK if there's pending
462
+ // continuous input (e.g. mouseover).
463
+ if (isInputPending !== null) {
464
+ return isInputPending();
465
+ }
466
+ } else if (timeElapsed < maxInterval) {
467
+ // Yield if there's either a pending discrete or continuous input.
468
+ if (isInputPending !== null) {
469
+ return isInputPending(continuousOptions);
470
}
451
- // There's no pending input. Only yield if we've reached the max
452
- // yield interval.
453
- const timeElapsed = currentTime - (deadline - yieldInterval);
454
- return timeElapsed >= maxYieldInterval;
471
} else {
456
- // There's still time left in the frame.
457
- return false;
472
+ // We've blocked the thread for a long time. Even if there's no pending
473
+ // input, there may be some other scheduled work that we don't know about,
474
+ // like a network event. Yield now.
475
+ return true;
476
}
459
- } else {
460
- // `isInputPending` is not available. Since we have no way of knowing if
461
- // there's pending input, always yield at the end of the frame.
462
- return getCurrentTime() >= deadline;
477
}
478
+
479
+ // `isInputPending` isn't available. Yield now.
480
+ return true;
481
}
482
483
function requestPaint() {
503
return;
504
}
505
if (fps > 0) {
489
- yieldInterval = Math.floor(1000 / fps);
506
+ frameInterval = Math.floor(1000 / fps);
507
} else {
508
// reset the framerate
492
- yieldInterval = 5;
509
+ frameInterval = 5;
510
}
511
}
512
513
const performWorkUntilDeadline = () => {
514
if (scheduledHostCallback !== null) {
515
const currentTime = getCurrentTime();
499
- // Yield after `yieldInterval` ms, regardless of where we are in the vsync
500
- // cycle. This means there's always time remaining at the beginning of
501
- // the message event.
502
- deadline = currentTime + yieldInterval;
516
+ // Keep track of the start time so we can measure how long the main thread
517
+ // has been blocked.
518
+ startTime = currentTime;
519
const hasTimeRemaining = true;
520
521
// If a scheduler task throws, exit the current browser task so the