mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
No one has been using this data so there's no reason to collect it. Event log has been maintained and tests have been updated.
428 lines
11 KiB
JavaScript
428 lines
11 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*/
|
|
|
|
/* eslint-disable no-var */
|
|
|
|
import {
|
|
enableSchedulerDebugging,
|
|
enableProfiling,
|
|
} from './SchedulerFeatureFlags';
|
|
import {
|
|
requestHostCallback,
|
|
requestHostTimeout,
|
|
cancelHostTimeout,
|
|
shouldYieldToHost,
|
|
getCurrentTime,
|
|
forceFrameRate,
|
|
requestPaint,
|
|
} from './SchedulerHostConfig';
|
|
import {push, pop, peek} from './SchedulerMinHeap';
|
|
|
|
// TODO: Use symbols?
|
|
import {
|
|
ImmediatePriority,
|
|
UserBlockingPriority,
|
|
NormalPriority,
|
|
LowPriority,
|
|
IdlePriority,
|
|
} from './SchedulerPriorities';
|
|
import {
|
|
markTaskRun,
|
|
markTaskYield,
|
|
markTaskCompleted,
|
|
markTaskCanceled,
|
|
markTaskErrored,
|
|
markSchedulerSuspended,
|
|
markSchedulerUnsuspended,
|
|
markTaskStart,
|
|
stopLoggingProfilingEvents,
|
|
startLoggingProfilingEvents,
|
|
} from './SchedulerProfiling';
|
|
|
|
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
|
|
// Math.pow(2, 30) - 1
|
|
// 0b111111111111111111111111111111
|
|
var maxSigned31BitInt = 1073741823;
|
|
|
|
// Times out immediately
|
|
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
|
|
// Eventually times out
|
|
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
|
|
var NORMAL_PRIORITY_TIMEOUT = 5000;
|
|
var LOW_PRIORITY_TIMEOUT = 10000;
|
|
// Never times out
|
|
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
|
|
|
|
// Tasks are stored on a min heap
|
|
var taskQueue = [];
|
|
var timerQueue = [];
|
|
|
|
// Incrementing id counter. Used to maintain insertion order.
|
|
var taskIdCounter = 1;
|
|
|
|
// Pausing the scheduler is useful for debugging.
|
|
var isSchedulerPaused = false;
|
|
|
|
var currentTask = null;
|
|
var currentPriorityLevel = NormalPriority;
|
|
|
|
// This is set while performing work, to prevent re-entrancy.
|
|
var isPerformingWork = false;
|
|
|
|
var isHostCallbackScheduled = false;
|
|
var isHostTimeoutScheduled = false;
|
|
|
|
function advanceTimers(currentTime) {
|
|
// Check for tasks that are no longer delayed and add them to the queue.
|
|
let timer = peek(timerQueue);
|
|
while (timer !== null) {
|
|
if (timer.callback === null) {
|
|
// Timer was cancelled.
|
|
pop(timerQueue);
|
|
} else if (timer.startTime <= currentTime) {
|
|
// Timer fired. Transfer to the task queue.
|
|
pop(timerQueue);
|
|
timer.sortIndex = timer.expirationTime;
|
|
push(taskQueue, timer);
|
|
if (enableProfiling) {
|
|
markTaskStart(timer, currentTime);
|
|
timer.isQueued = true;
|
|
}
|
|
} else {
|
|
// Remaining timers are pending.
|
|
return;
|
|
}
|
|
timer = peek(timerQueue);
|
|
}
|
|
}
|
|
|
|
function handleTimeout(currentTime) {
|
|
isHostTimeoutScheduled = false;
|
|
advanceTimers(currentTime);
|
|
|
|
if (!isHostCallbackScheduled) {
|
|
if (peek(taskQueue) !== null) {
|
|
isHostCallbackScheduled = true;
|
|
requestHostCallback(flushWork);
|
|
} else {
|
|
const firstTimer = peek(timerQueue);
|
|
if (firstTimer !== null) {
|
|
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function flushWork(hasTimeRemaining, initialTime) {
|
|
if (enableProfiling) {
|
|
markSchedulerUnsuspended(initialTime);
|
|
}
|
|
|
|
// We'll need a host callback the next time work is scheduled.
|
|
isHostCallbackScheduled = false;
|
|
if (isHostTimeoutScheduled) {
|
|
// We scheduled a timeout but it's no longer needed. Cancel it.
|
|
isHostTimeoutScheduled = false;
|
|
cancelHostTimeout();
|
|
}
|
|
|
|
isPerformingWork = true;
|
|
const previousPriorityLevel = currentPriorityLevel;
|
|
try {
|
|
if (enableProfiling) {
|
|
try {
|
|
return workLoop(hasTimeRemaining, initialTime);
|
|
} catch (error) {
|
|
if (currentTask !== null) {
|
|
const currentTime = getCurrentTime();
|
|
markTaskErrored(currentTask, currentTime);
|
|
currentTask.isQueued = false;
|
|
}
|
|
throw error;
|
|
}
|
|
} else {
|
|
// No catch in prod code path.
|
|
return workLoop(hasTimeRemaining, initialTime);
|
|
}
|
|
} finally {
|
|
currentTask = null;
|
|
currentPriorityLevel = previousPriorityLevel;
|
|
isPerformingWork = false;
|
|
if (enableProfiling) {
|
|
const currentTime = getCurrentTime();
|
|
markSchedulerSuspended(currentTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
function workLoop(hasTimeRemaining, initialTime) {
|
|
let currentTime = initialTime;
|
|
advanceTimers(currentTime);
|
|
currentTask = peek(taskQueue);
|
|
while (
|
|
currentTask !== null &&
|
|
!(enableSchedulerDebugging && isSchedulerPaused)
|
|
) {
|
|
if (
|
|
currentTask.expirationTime > currentTime &&
|
|
(!hasTimeRemaining || shouldYieldToHost())
|
|
) {
|
|
// This currentTask hasn't expired, and we've reached the deadline.
|
|
break;
|
|
}
|
|
const callback = currentTask.callback;
|
|
if (typeof callback === 'function') {
|
|
currentTask.callback = null;
|
|
currentPriorityLevel = currentTask.priorityLevel;
|
|
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
|
|
if (enableProfiling) {
|
|
markTaskRun(currentTask, currentTime);
|
|
}
|
|
const continuationCallback = callback(didUserCallbackTimeout);
|
|
currentTime = getCurrentTime();
|
|
if (typeof continuationCallback === 'function') {
|
|
currentTask.callback = continuationCallback;
|
|
if (enableProfiling) {
|
|
markTaskYield(currentTask, currentTime);
|
|
}
|
|
} else {
|
|
if (enableProfiling) {
|
|
markTaskCompleted(currentTask, currentTime);
|
|
currentTask.isQueued = false;
|
|
}
|
|
if (currentTask === peek(taskQueue)) {
|
|
pop(taskQueue);
|
|
}
|
|
}
|
|
advanceTimers(currentTime);
|
|
} else {
|
|
pop(taskQueue);
|
|
}
|
|
currentTask = peek(taskQueue);
|
|
}
|
|
// Return whether there's additional work
|
|
if (currentTask !== null) {
|
|
return true;
|
|
} else {
|
|
const firstTimer = peek(timerQueue);
|
|
if (firstTimer !== null) {
|
|
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function unstable_runWithPriority(priorityLevel, eventHandler) {
|
|
switch (priorityLevel) {
|
|
case ImmediatePriority:
|
|
case UserBlockingPriority:
|
|
case NormalPriority:
|
|
case LowPriority:
|
|
case IdlePriority:
|
|
break;
|
|
default:
|
|
priorityLevel = NormalPriority;
|
|
}
|
|
|
|
var previousPriorityLevel = currentPriorityLevel;
|
|
currentPriorityLevel = priorityLevel;
|
|
|
|
try {
|
|
return eventHandler();
|
|
} finally {
|
|
currentPriorityLevel = previousPriorityLevel;
|
|
}
|
|
}
|
|
|
|
function unstable_next(eventHandler) {
|
|
var priorityLevel;
|
|
switch (currentPriorityLevel) {
|
|
case ImmediatePriority:
|
|
case UserBlockingPriority:
|
|
case NormalPriority:
|
|
// Shift down to normal priority
|
|
priorityLevel = NormalPriority;
|
|
break;
|
|
default:
|
|
// Anything lower than normal priority should remain at the current level.
|
|
priorityLevel = currentPriorityLevel;
|
|
break;
|
|
}
|
|
|
|
var previousPriorityLevel = currentPriorityLevel;
|
|
currentPriorityLevel = priorityLevel;
|
|
|
|
try {
|
|
return eventHandler();
|
|
} finally {
|
|
currentPriorityLevel = previousPriorityLevel;
|
|
}
|
|
}
|
|
|
|
function unstable_wrapCallback(callback) {
|
|
var parentPriorityLevel = currentPriorityLevel;
|
|
return function() {
|
|
// This is a fork of runWithPriority, inlined for performance.
|
|
var previousPriorityLevel = currentPriorityLevel;
|
|
currentPriorityLevel = parentPriorityLevel;
|
|
|
|
try {
|
|
return callback.apply(this, arguments);
|
|
} finally {
|
|
currentPriorityLevel = previousPriorityLevel;
|
|
}
|
|
};
|
|
}
|
|
|
|
function unstable_scheduleCallback(priorityLevel, callback, options) {
|
|
var currentTime = getCurrentTime();
|
|
|
|
var startTime;
|
|
if (typeof options === 'object' && options !== null) {
|
|
var delay = options.delay;
|
|
if (typeof delay === 'number' && delay > 0) {
|
|
startTime = currentTime + delay;
|
|
} else {
|
|
startTime = currentTime;
|
|
}
|
|
} else {
|
|
startTime = currentTime;
|
|
}
|
|
|
|
var timeout;
|
|
switch (priorityLevel) {
|
|
case ImmediatePriority:
|
|
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
|
|
break;
|
|
case UserBlockingPriority:
|
|
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
|
|
break;
|
|
case IdlePriority:
|
|
timeout = IDLE_PRIORITY_TIMEOUT;
|
|
break;
|
|
case LowPriority:
|
|
timeout = LOW_PRIORITY_TIMEOUT;
|
|
break;
|
|
case NormalPriority:
|
|
default:
|
|
timeout = NORMAL_PRIORITY_TIMEOUT;
|
|
break;
|
|
}
|
|
|
|
var expirationTime = startTime + timeout;
|
|
|
|
var newTask = {
|
|
id: taskIdCounter++,
|
|
callback,
|
|
priorityLevel,
|
|
startTime,
|
|
expirationTime,
|
|
sortIndex: -1,
|
|
};
|
|
if (enableProfiling) {
|
|
newTask.isQueued = false;
|
|
}
|
|
|
|
if (startTime > currentTime) {
|
|
// This is a delayed task.
|
|
newTask.sortIndex = startTime;
|
|
push(timerQueue, newTask);
|
|
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
|
|
// All tasks are delayed, and this is the task with the earliest delay.
|
|
if (isHostTimeoutScheduled) {
|
|
// Cancel an existing timeout.
|
|
cancelHostTimeout();
|
|
} else {
|
|
isHostTimeoutScheduled = true;
|
|
}
|
|
// Schedule a timeout.
|
|
requestHostTimeout(handleTimeout, startTime - currentTime);
|
|
}
|
|
} else {
|
|
newTask.sortIndex = expirationTime;
|
|
push(taskQueue, newTask);
|
|
if (enableProfiling) {
|
|
markTaskStart(newTask, currentTime);
|
|
newTask.isQueued = true;
|
|
}
|
|
// Schedule a host callback, if needed. If we're already performing work,
|
|
// wait until the next time we yield.
|
|
if (!isHostCallbackScheduled && !isPerformingWork) {
|
|
isHostCallbackScheduled = true;
|
|
requestHostCallback(flushWork);
|
|
}
|
|
}
|
|
|
|
return newTask;
|
|
}
|
|
|
|
function unstable_pauseExecution() {
|
|
isSchedulerPaused = true;
|
|
}
|
|
|
|
function unstable_continueExecution() {
|
|
isSchedulerPaused = false;
|
|
if (!isHostCallbackScheduled && !isPerformingWork) {
|
|
isHostCallbackScheduled = true;
|
|
requestHostCallback(flushWork);
|
|
}
|
|
}
|
|
|
|
function unstable_getFirstCallbackNode() {
|
|
return peek(taskQueue);
|
|
}
|
|
|
|
function unstable_cancelCallback(task) {
|
|
if (enableProfiling) {
|
|
if (task.isQueued) {
|
|
const currentTime = getCurrentTime();
|
|
markTaskCanceled(task, currentTime);
|
|
task.isQueued = false;
|
|
}
|
|
}
|
|
|
|
// Null out the callback to indicate the task has been canceled. (Can't
|
|
// remove from the queue because you can't remove arbitrary nodes from an
|
|
// array based heap, only the first one.)
|
|
task.callback = null;
|
|
}
|
|
|
|
function unstable_getCurrentPriorityLevel() {
|
|
return currentPriorityLevel;
|
|
}
|
|
|
|
const unstable_requestPaint = requestPaint;
|
|
|
|
export {
|
|
ImmediatePriority as unstable_ImmediatePriority,
|
|
UserBlockingPriority as unstable_UserBlockingPriority,
|
|
NormalPriority as unstable_NormalPriority,
|
|
IdlePriority as unstable_IdlePriority,
|
|
LowPriority as unstable_LowPriority,
|
|
unstable_runWithPriority,
|
|
unstable_next,
|
|
unstable_scheduleCallback,
|
|
unstable_cancelCallback,
|
|
unstable_wrapCallback,
|
|
unstable_getCurrentPriorityLevel,
|
|
shouldYieldToHost as unstable_shouldYield,
|
|
unstable_requestPaint,
|
|
unstable_continueExecution,
|
|
unstable_pauseExecution,
|
|
unstable_getFirstCallbackNode,
|
|
getCurrentTime as unstable_now,
|
|
forceFrameRate as unstable_forceFrameRate,
|
|
};
|
|
|
|
export const unstable_Profiling = enableProfiling
|
|
? {
|
|
startLoggingProfilingEvents,
|
|
stopLoggingProfilingEvents,
|
|
}
|
|
: null;
|