mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
* [Scheduler] requestPaint Signals to Scheduler that the browser needs to paint the screen. React will call it in the commit phase. Scheduler will yield at the end of the current frame, even if there is no pending input. When `isInputPending` is not available, this has no effect, because we yield at the end of every frame regardless. React will call `requestPaint` in the commit phase as long as there's at least one effect. We could choose not to call it if none of the effects are DOM mutations, but this is so rare that it doesn't seem worthwhile to bother checking. * Fall back gracefully if requestPaint is missing
198 lines
6.8 KiB
JavaScript
198 lines
6.8 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.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
// Intentionally not named imports because Rollup would use dynamic dispatch for
|
|
// CommonJS interop named imports.
|
|
import * as Scheduler from 'scheduler';
|
|
import {__interactionsRef} from 'scheduler/tracing';
|
|
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
|
|
import invariant from 'shared/invariant';
|
|
|
|
const {
|
|
unstable_runWithPriority: Scheduler_runWithPriority,
|
|
unstable_scheduleCallback: Scheduler_scheduleCallback,
|
|
unstable_cancelCallback: Scheduler_cancelCallback,
|
|
unstable_shouldYield: Scheduler_shouldYield,
|
|
unstable_requestPaint: Scheduler_requestPaint,
|
|
unstable_now: Scheduler_now,
|
|
unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
|
|
unstable_ImmediatePriority: Scheduler_ImmediatePriority,
|
|
unstable_UserBlockingPriority: Scheduler_UserBlockingPriority,
|
|
unstable_NormalPriority: Scheduler_NormalPriority,
|
|
unstable_LowPriority: Scheduler_LowPriority,
|
|
unstable_IdlePriority: Scheduler_IdlePriority,
|
|
} = Scheduler;
|
|
|
|
if (enableSchedulerTracing) {
|
|
// Provide explicit error message when production+profiling bundle of e.g.
|
|
// react-dom is used with production (non-profiling) bundle of
|
|
// scheduler/tracing
|
|
invariant(
|
|
__interactionsRef != null && __interactionsRef.current != null,
|
|
'It is not supported to run the profiling version of a renderer (for ' +
|
|
'example, `react-dom/profiling`) without also replacing the ' +
|
|
'`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' +
|
|
'bundler might have a setting for aliasing both modules. Learn more at ' +
|
|
'http://fb.me/react-profiling',
|
|
);
|
|
}
|
|
|
|
export opaque type ReactPriorityLevel = 99 | 98 | 97 | 96 | 95 | 90;
|
|
export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;
|
|
|
|
type SchedulerCallbackOptions = {
|
|
timeout?: number,
|
|
};
|
|
|
|
const fakeCallbackNode = {};
|
|
|
|
// Except for NoPriority, these correspond to Scheduler priorities. We use
|
|
// ascending numbers so we can compare them like numbers. They start at 90 to
|
|
// avoid clashing with Scheduler's priorities.
|
|
export const ImmediatePriority: ReactPriorityLevel = 99;
|
|
export const UserBlockingPriority: ReactPriorityLevel = 98;
|
|
export const NormalPriority: ReactPriorityLevel = 97;
|
|
export const LowPriority: ReactPriorityLevel = 96;
|
|
export const IdlePriority: ReactPriorityLevel = 95;
|
|
// NoPriority is the absence of priority. Also React-only.
|
|
export const NoPriority: ReactPriorityLevel = 90;
|
|
|
|
export const shouldYield = Scheduler_shouldYield;
|
|
export const requestPaint =
|
|
// Fall back gracefully if we're running an older verison of Scheduler.
|
|
Scheduler_requestPaint !== undefined ? Scheduler_requestPaint : () => {};
|
|
|
|
let syncQueue: Array<SchedulerCallback> | null = null;
|
|
let immediateQueueCallbackNode: mixed | null = null;
|
|
let isFlushingSyncQueue: boolean = false;
|
|
let initialTimeMs: number = Scheduler_now();
|
|
|
|
// If the initial timestamp is reasonably small, use Scheduler's `now` directly.
|
|
// This will be the case for modern browsers that support `performance.now`. In
|
|
// older browsers, Scheduler falls back to `Date.now`, which returns a Unix
|
|
// timestamp. In that case, subtract the module initialization time to simulate
|
|
// the behavior of performance.now and keep our times small enough to fit
|
|
// within 32 bits.
|
|
// TODO: Consider lifting this into Scheduler.
|
|
export const now =
|
|
initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
|
|
|
|
export function getCurrentPriorityLevel(): ReactPriorityLevel {
|
|
switch (Scheduler_getCurrentPriorityLevel()) {
|
|
case Scheduler_ImmediatePriority:
|
|
return ImmediatePriority;
|
|
case Scheduler_UserBlockingPriority:
|
|
return UserBlockingPriority;
|
|
case Scheduler_NormalPriority:
|
|
return NormalPriority;
|
|
case Scheduler_LowPriority:
|
|
return LowPriority;
|
|
case Scheduler_IdlePriority:
|
|
return IdlePriority;
|
|
default:
|
|
invariant(false, 'Unknown priority level.');
|
|
}
|
|
}
|
|
|
|
function reactPriorityToSchedulerPriority(reactPriorityLevel) {
|
|
switch (reactPriorityLevel) {
|
|
case ImmediatePriority:
|
|
return Scheduler_ImmediatePriority;
|
|
case UserBlockingPriority:
|
|
return Scheduler_UserBlockingPriority;
|
|
case NormalPriority:
|
|
return Scheduler_NormalPriority;
|
|
case LowPriority:
|
|
return Scheduler_LowPriority;
|
|
case IdlePriority:
|
|
return Scheduler_IdlePriority;
|
|
default:
|
|
invariant(false, 'Unknown priority level.');
|
|
}
|
|
}
|
|
|
|
export function runWithPriority<T>(
|
|
reactPriorityLevel: ReactPriorityLevel,
|
|
fn: () => T,
|
|
): T {
|
|
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
|
|
return Scheduler_runWithPriority(priorityLevel, fn);
|
|
}
|
|
|
|
export function scheduleCallback(
|
|
reactPriorityLevel: ReactPriorityLevel,
|
|
callback: SchedulerCallback,
|
|
options: SchedulerCallbackOptions | void | null,
|
|
) {
|
|
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
|
|
return Scheduler_scheduleCallback(priorityLevel, callback, options);
|
|
}
|
|
|
|
export function scheduleSyncCallback(callback: SchedulerCallback) {
|
|
// Push this callback into an internal queue. We'll flush these either in
|
|
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
|
|
if (syncQueue === null) {
|
|
syncQueue = [callback];
|
|
// Flush the queue in the next tick, at the earliest.
|
|
immediateQueueCallbackNode = Scheduler_scheduleCallback(
|
|
Scheduler_ImmediatePriority,
|
|
flushSyncCallbackQueueImpl,
|
|
);
|
|
} else {
|
|
// Push onto existing queue. Don't need to schedule a callback because
|
|
// we already scheduled one when we created the queue.
|
|
syncQueue.push(callback);
|
|
}
|
|
return fakeCallbackNode;
|
|
}
|
|
|
|
export function cancelCallback(callbackNode: mixed) {
|
|
if (callbackNode !== fakeCallbackNode) {
|
|
Scheduler_cancelCallback(callbackNode);
|
|
}
|
|
}
|
|
|
|
export function flushSyncCallbackQueue() {
|
|
if (immediateQueueCallbackNode !== null) {
|
|
Scheduler_cancelCallback(immediateQueueCallbackNode);
|
|
}
|
|
flushSyncCallbackQueueImpl();
|
|
}
|
|
|
|
function flushSyncCallbackQueueImpl() {
|
|
if (!isFlushingSyncQueue && syncQueue !== null) {
|
|
// Prevent re-entrancy.
|
|
isFlushingSyncQueue = true;
|
|
let i = 0;
|
|
try {
|
|
const isSync = true;
|
|
for (; i < syncQueue.length; i++) {
|
|
let callback = syncQueue[i];
|
|
do {
|
|
callback = callback(isSync);
|
|
} while (callback !== null);
|
|
}
|
|
syncQueue = null;
|
|
} catch (error) {
|
|
// If something throws, leave the remaining callbacks on the queue.
|
|
if (syncQueue !== null) {
|
|
syncQueue = syncQueue.slice(i + 1);
|
|
}
|
|
// Resume flushing in the next tick
|
|
Scheduler_scheduleCallback(
|
|
Scheduler_ImmediatePriority,
|
|
flushSyncCallbackQueue,
|
|
);
|
|
throw error;
|
|
} finally {
|
|
isFlushingSyncQueue = false;
|
|
}
|
|
}
|
|
}
|