diff --git a/package.json b/package.json
index 92beff532f100..5824c7298a9de 100644
--- a/package.json
+++ b/package.json
@@ -102,7 +102,6 @@
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
"test-persistent": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-persistent.js",
"test-fire": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-fire.js",
- "test-new-scheduler": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-new-scheduler.js",
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
"test-fire-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source-fire.js",
"test-prod-build": "yarn test-build-prod",
diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
similarity index 79%
rename from packages/react-dom/src/__tests__/ReactDOMHooks-test.internal.js
rename to packages/react-dom/src/__tests__/ReactDOMHooks-test.js
index 7d58d22f41bac..360cfa9f9a392 100644
--- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
@@ -9,8 +9,6 @@
'use strict';
-let ReactFeatureFlags;
-let enableNewScheduler;
let React;
let ReactDOM;
let Scheduler;
@@ -21,8 +19,6 @@ describe('ReactDOMHooks', () => {
beforeEach(() => {
jest.resetModules();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
@@ -101,30 +97,15 @@ describe('ReactDOMHooks', () => {
}
ReactDOM.render(, container);
-
- if (enableNewScheduler) {
- // The old behavior was accidental; in the new scheduler, flushing passive
- // effects also flushes synchronous work, even inside batchedUpdates.
- ReactDOM.unstable_batchedUpdates(() => {
- _set(0); // Forces the effect to be flushed
- expect(otherContainer.textContent).toBe('A');
- ReactDOM.render(, otherContainer);
- expect(otherContainer.textContent).toBe('A');
- });
- expect(otherContainer.textContent).toBe('B');
- expect(calledA).toBe(true);
- expect(calledB).toBe(true);
- } else {
- ReactDOM.unstable_batchedUpdates(() => {
- _set(0); // Forces the effect to be flushed
- expect(otherContainer.textContent).toBe('');
- ReactDOM.render(, otherContainer);
- expect(otherContainer.textContent).toBe('');
- });
- expect(otherContainer.textContent).toBe('B');
- expect(calledA).toBe(false); // It was in a batch
- expect(calledB).toBe(true);
- }
+ ReactDOM.unstable_batchedUpdates(() => {
+ _set(0); // Forces the effect to be flushed
+ expect(otherContainer.textContent).toBe('A');
+ ReactDOM.render(, otherContainer);
+ expect(otherContainer.textContent).toBe('A');
+ });
+ expect(otherContainer.textContent).toBe('B');
+ expect(calledA).toBe(true);
+ expect(calledB).toBe(true);
});
it('should not bail out when an update is scheduled from within an event handler', () => {
diff --git a/packages/react-dom/unstable-new-scheduler.fb.js b/packages/react-dom/unstable-new-scheduler.fb.js
deleted file mode 100644
index ea901748d6949..0000000000000
--- a/packages/react-dom/unstable-new-scheduler.fb.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * 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.
- */
-
-'use strict';
-
-const ReactDOMFB = require('./src/client/ReactDOMFB');
-
-// TODO: decide on the top-level export form.
-// This is hacky but makes it work with both Rollup and Jest.
-module.exports = ReactDOMFB.default || ReactDOMFB;
diff --git a/packages/react-dom/unstable-new-scheduler.js b/packages/react-dom/unstable-new-scheduler.js
deleted file mode 100644
index 2a016ba16e9db..0000000000000
--- a/packages/react-dom/unstable-new-scheduler.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * 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
- */
-
-'use strict';
-
-const ReactDOM = require('./src/client/ReactDOM');
-
-// TODO: decide on the top-level export form.
-// This is hacky but makes it work with both Rollup and Jest.
-module.exports = ReactDOM.default || ReactDOM;
diff --git a/packages/react-reconciler/src/ReactFiberPendingPriority.js b/packages/react-reconciler/src/ReactFiberPendingPriority.js
deleted file mode 100644
index 43e241d3a2683..0000000000000
--- a/packages/react-reconciler/src/ReactFiberPendingPriority.js
+++ /dev/null
@@ -1,265 +0,0 @@
-/**
- * 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
- */
-
-import type {FiberRoot} from './ReactFiberRoot';
-import type {ExpirationTime} from './ReactFiberExpirationTime';
-
-import {NoWork} from './ReactFiberExpirationTime';
-
-// TODO: Offscreen updates should never suspend. However, a promise that
-// suspended inside an offscreen subtree should be able to ping at the priority
-// of the outer render.
-
-export function markPendingPriorityLevel(
- root: FiberRoot,
- expirationTime: ExpirationTime,
-): void {
- // If there's a gap between completing a failed root and retrying it,
- // additional updates may be scheduled. Clear `didError`, in case the update
- // is sufficient to fix the error.
- root.didError = false;
-
- // Update the latest and earliest pending times
- const earliestPendingTime = root.earliestPendingTime;
- if (earliestPendingTime === NoWork) {
- // No other pending updates.
- root.earliestPendingTime = root.latestPendingTime = expirationTime;
- } else {
- if (earliestPendingTime < expirationTime) {
- // This is the earliest pending update.
- root.earliestPendingTime = expirationTime;
- } else {
- const latestPendingTime = root.latestPendingTime;
- if (latestPendingTime > expirationTime) {
- // This is the latest pending update
- root.latestPendingTime = expirationTime;
- }
- }
- }
- findNextExpirationTimeToWorkOn(expirationTime, root);
-}
-
-export function markCommittedPriorityLevels(
- root: FiberRoot,
- earliestRemainingTime: ExpirationTime,
-): void {
- root.didError = false;
-
- if (earliestRemainingTime === NoWork) {
- // Fast path. There's no remaining work. Clear everything.
- root.earliestPendingTime = NoWork;
- root.latestPendingTime = NoWork;
- root.earliestSuspendedTime = NoWork;
- root.latestSuspendedTime = NoWork;
- root.latestPingedTime = NoWork;
- findNextExpirationTimeToWorkOn(NoWork, root);
- return;
- }
-
- if (earliestRemainingTime < root.latestPingedTime) {
- root.latestPingedTime = NoWork;
- }
-
- // Let's see if the previous latest known pending level was just flushed.
- const latestPendingTime = root.latestPendingTime;
- if (latestPendingTime !== NoWork) {
- if (latestPendingTime > earliestRemainingTime) {
- // We've flushed all the known pending levels.
- root.earliestPendingTime = root.latestPendingTime = NoWork;
- } else {
- const earliestPendingTime = root.earliestPendingTime;
- if (earliestPendingTime > earliestRemainingTime) {
- // We've flushed the earliest known pending level. Set this to the
- // latest pending time.
- root.earliestPendingTime = root.latestPendingTime;
- }
- }
- }
-
- // Now let's handle the earliest remaining level in the whole tree. We need to
- // decide whether to treat it as a pending level or as suspended. Check
- // it falls within the range of known suspended levels.
-
- const earliestSuspendedTime = root.earliestSuspendedTime;
- if (earliestSuspendedTime === NoWork) {
- // There's no suspended work. Treat the earliest remaining level as a
- // pending level.
- markPendingPriorityLevel(root, earliestRemainingTime);
- findNextExpirationTimeToWorkOn(NoWork, root);
- return;
- }
-
- const latestSuspendedTime = root.latestSuspendedTime;
- if (earliestRemainingTime < latestSuspendedTime) {
- // The earliest remaining level is later than all the suspended work. That
- // means we've flushed all the suspended work.
- root.earliestSuspendedTime = NoWork;
- root.latestSuspendedTime = NoWork;
- root.latestPingedTime = NoWork;
-
- // There's no suspended work. Treat the earliest remaining level as a
- // pending level.
- markPendingPriorityLevel(root, earliestRemainingTime);
- findNextExpirationTimeToWorkOn(NoWork, root);
- return;
- }
-
- if (earliestRemainingTime > earliestSuspendedTime) {
- // The earliest remaining time is earlier than all the suspended work.
- // Treat it as a pending update.
- markPendingPriorityLevel(root, earliestRemainingTime);
- findNextExpirationTimeToWorkOn(NoWork, root);
- return;
- }
-
- // The earliest remaining time falls within the range of known suspended
- // levels. We should treat this as suspended work.
- findNextExpirationTimeToWorkOn(NoWork, root);
-}
-
-export function hasLowerPriorityWork(
- root: FiberRoot,
- erroredExpirationTime: ExpirationTime,
-): boolean {
- const latestPendingTime = root.latestPendingTime;
- const latestSuspendedTime = root.latestSuspendedTime;
- const latestPingedTime = root.latestPingedTime;
- return (
- (latestPendingTime !== NoWork &&
- latestPendingTime < erroredExpirationTime) ||
- (latestSuspendedTime !== NoWork &&
- latestSuspendedTime < erroredExpirationTime) ||
- (latestPingedTime !== NoWork && latestPingedTime < erroredExpirationTime)
- );
-}
-
-export function isPriorityLevelSuspended(
- root: FiberRoot,
- expirationTime: ExpirationTime,
-): boolean {
- const earliestSuspendedTime = root.earliestSuspendedTime;
- const latestSuspendedTime = root.latestSuspendedTime;
- return (
- earliestSuspendedTime !== NoWork &&
- expirationTime <= earliestSuspendedTime &&
- expirationTime >= latestSuspendedTime
- );
-}
-
-export function markSuspendedPriorityLevel(
- root: FiberRoot,
- suspendedTime: ExpirationTime,
-): void {
- root.didError = false;
- clearPing(root, suspendedTime);
-
- // First, check the known pending levels and update them if needed.
- const earliestPendingTime = root.earliestPendingTime;
- const latestPendingTime = root.latestPendingTime;
- if (earliestPendingTime === suspendedTime) {
- if (latestPendingTime === suspendedTime) {
- // Both known pending levels were suspended. Clear them.
- root.earliestPendingTime = root.latestPendingTime = NoWork;
- } else {
- // The earliest pending level was suspended. Clear by setting it to the
- // latest pending level.
- root.earliestPendingTime = latestPendingTime;
- }
- } else if (latestPendingTime === suspendedTime) {
- // The latest pending level was suspended. Clear by setting it to the
- // latest pending level.
- root.latestPendingTime = earliestPendingTime;
- }
-
- // Finally, update the known suspended levels.
- const earliestSuspendedTime = root.earliestSuspendedTime;
- const latestSuspendedTime = root.latestSuspendedTime;
- if (earliestSuspendedTime === NoWork) {
- // No other suspended levels.
- root.earliestSuspendedTime = root.latestSuspendedTime = suspendedTime;
- } else {
- if (earliestSuspendedTime < suspendedTime) {
- // This is the earliest suspended level.
- root.earliestSuspendedTime = suspendedTime;
- } else if (latestSuspendedTime > suspendedTime) {
- // This is the latest suspended level
- root.latestSuspendedTime = suspendedTime;
- }
- }
-
- findNextExpirationTimeToWorkOn(suspendedTime, root);
-}
-
-export function markPingedPriorityLevel(
- root: FiberRoot,
- pingedTime: ExpirationTime,
-): void {
- root.didError = false;
-
- // TODO: When we add back resuming, we need to ensure the progressed work
- // is thrown out and not reused during the restarted render. One way to
- // invalidate the progressed work is to restart at expirationTime + 1.
- const latestPingedTime = root.latestPingedTime;
- if (latestPingedTime === NoWork || latestPingedTime > pingedTime) {
- root.latestPingedTime = pingedTime;
- }
- findNextExpirationTimeToWorkOn(pingedTime, root);
-}
-
-function clearPing(root, completedTime) {
- const latestPingedTime = root.latestPingedTime;
- if (latestPingedTime >= completedTime) {
- root.latestPingedTime = NoWork;
- }
-}
-
-export function didExpireAtExpirationTime(
- root: FiberRoot,
- currentTime: ExpirationTime,
-): void {
- const expirationTime = root.expirationTime;
- if (expirationTime !== NoWork && currentTime <= expirationTime) {
- // The root has expired. Flush all work up to the current time.
- root.nextExpirationTimeToWorkOn = currentTime;
- }
-}
-
-function findNextExpirationTimeToWorkOn(completedExpirationTime, root) {
- const earliestSuspendedTime = root.earliestSuspendedTime;
- const latestSuspendedTime = root.latestSuspendedTime;
- const earliestPendingTime = root.earliestPendingTime;
- const latestPingedTime = root.latestPingedTime;
-
- // Work on the earliest pending time. Failing that, work on the latest
- // pinged time.
- let nextExpirationTimeToWorkOn =
- earliestPendingTime !== NoWork ? earliestPendingTime : latestPingedTime;
-
- // If there is no pending or pinged work, check if there's suspended work
- // that's lower priority than what we just completed.
- if (
- nextExpirationTimeToWorkOn === NoWork &&
- (completedExpirationTime === NoWork ||
- latestSuspendedTime < completedExpirationTime)
- ) {
- // The lowest priority suspended work is the work most likely to be
- // committed next. Let's start rendering it again, so that if it times out,
- // it's ready to commit.
- nextExpirationTimeToWorkOn = latestSuspendedTime;
- }
-
- let expirationTime = nextExpirationTimeToWorkOn;
- if (expirationTime !== NoWork && earliestSuspendedTime > expirationTime) {
- // Expire using the earliest known expiration time.
- expirationTime = earliestSuspendedTime;
- }
-
- root.nextExpirationTimeToWorkOn = nextExpirationTimeToWorkOn;
- root.expirationTime = expirationTime;
-}
diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js
index 2b085604ce808..38563ae2533bd 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.js
@@ -16,10 +16,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
import {noTimeout} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber';
import {NoWork} from './ReactFiberExpirationTime';
-import {
- enableSchedulerTracing,
- enableNewScheduler,
-} from 'shared/ReactFeatureFlags';
+import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
// TODO: This should be lifted into the renderer.
@@ -40,31 +37,11 @@ type BaseFiberRootProperties = {|
// The currently active root fiber. This is the mutable root of the tree.
current: Fiber,
- // The following priority levels are used to distinguish between 1)
- // uncommitted work, 2) uncommitted work that is suspended, and 3) uncommitted
- // work that may be unsuspended. We choose not to track each individual
- // pending level, trading granularity for performance.
- //
- // The earliest and latest priority levels that are suspended from committing.
- earliestSuspendedTime: ExpirationTime,
- latestSuspendedTime: ExpirationTime,
- // The earliest and latest priority levels that are not known to be suspended.
- earliestPendingTime: ExpirationTime,
- latestPendingTime: ExpirationTime,
- // The latest priority level that was pinged by a resolved promise and can
- // be retried.
- latestPingedTime: ExpirationTime,
-
pingCache:
| WeakMap>
| Map>
| null,
- // If an error is thrown, and there are no more updates in the queue, we try
- // rendering from the root one more time, synchronously, before handling
- // the error.
- didError: boolean,
-
pendingCommitExpirationTime: ExpirationTime,
// A finished work-in-progress HostRoot that's ready to be committed.
finishedWork: Fiber | null,
@@ -76,22 +53,19 @@ type BaseFiberRootProperties = {|
pendingContext: Object | null,
// Determines if we should attempt to hydrate on the initial mount
+hydrate: boolean,
- // Remaining expiration time on this root.
- // TODO: Lift this into the renderer
- nextExpirationTimeToWorkOn: ExpirationTime,
- expirationTime: ExpirationTime,
// List of top-level batches. This list indicates whether a commit should be
// deferred. Also contains completion callbacks.
// TODO: Lift this into the renderer
firstBatch: Batch | null,
- // Linked-list of roots
- nextScheduledRoot: FiberRoot | null,
-
- // New Scheduler fields
+ // Node returned by Scheduler.scheduleCallback
callbackNode: *,
+ // Expiration of the callback associated with this root
callbackExpirationTime: ExpirationTime,
+ // The earliest pending expiration time that exists in the tree
firstPendingTime: ExpirationTime,
+ // The latest pending expiration time that exists in the tree
lastPendingTime: ExpirationTime,
+ // The time at which a suspended component pinged the root to render again
pingTime: ExpirationTime,
|};
@@ -127,24 +101,11 @@ function FiberRootNode(containerInfo, hydrate) {
this.pendingContext = null;
this.hydrate = hydrate;
this.firstBatch = null;
-
- if (enableNewScheduler) {
- this.callbackNode = null;
- this.callbackExpirationTime = NoWork;
- this.firstPendingTime = NoWork;
- this.lastPendingTime = NoWork;
- this.pingTime = NoWork;
- } else {
- this.earliestPendingTime = NoWork;
- this.latestPendingTime = NoWork;
- this.earliestSuspendedTime = NoWork;
- this.latestSuspendedTime = NoWork;
- this.latestPingedTime = NoWork;
- this.didError = false;
- this.nextExpirationTimeToWorkOn = NoWork;
- this.expirationTime = NoWork;
- this.nextScheduledRoot = null;
- }
+ this.callbackNode = null;
+ this.callbackExpirationTime = NoWork;
+ this.firstPendingTime = NoWork;
+ this.lastPendingTime = NoWork;
+ this.pingTime = NoWork;
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index f58aaa7913c9a..9c1065a568781 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -7,136 +7,2238 @@
* @flow
*/
-import {enableNewScheduler} from 'shared/ReactFeatureFlags';
+import type {Fiber} from './ReactFiber';
+import type {FiberRoot} from './ReactFiberRoot';
+import type {ExpirationTime} from './ReactFiberExpirationTime';
+import type {
+ ReactPriorityLevel,
+ SchedulerCallback,
+} from './SchedulerWithReactIntegration';
+import type {Interaction} from 'scheduler/src/Tracing';
import {
- requestCurrentTime as requestCurrentTime_old,
- computeExpirationForFiber as computeExpirationForFiber_old,
- captureCommitPhaseError as captureCommitPhaseError_old,
- onUncaughtError as onUncaughtError_old,
- markRenderEventTime as markRenderEventTime_old,
- renderDidSuspend as renderDidSuspend_old,
- renderDidError as renderDidError_old,
- pingSuspendedRoot as pingSuspendedRoot_old,
- retryTimedOutBoundary as retryTimedOutBoundary_old,
- resolveRetryThenable as resolveRetryThenable_old,
- markLegacyErrorBoundaryAsFailed as markLegacyErrorBoundaryAsFailed_old,
- isAlreadyFailedLegacyErrorBoundary as isAlreadyFailedLegacyErrorBoundary_old,
- scheduleWork as scheduleWork_old,
- flushRoot as flushRoot_old,
- batchedUpdates as batchedUpdates_old,
- unbatchedUpdates as unbatchedUpdates_old,
- flushSync as flushSync_old,
- flushControlled as flushControlled_old,
- deferredUpdates as deferredUpdates_old,
- syncUpdates as syncUpdates_old,
- interactiveUpdates as interactiveUpdates_old,
- flushInteractiveUpdates as flushInteractiveUpdates_old,
- computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_old,
- flushPassiveEffects as flushPassiveEffects_old,
- warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_old,
-} from './ReactFiberScheduler.old';
+ warnAboutDeprecatedLifecycles,
+ enableUserTimingAPI,
+ enableSuspenseServerRenderer,
+ replayFailedUnitOfWorkWithInvokeGuardedCallback,
+ enableProfilerTimer,
+ disableYielding,
+ enableSchedulerTracing,
+} from 'shared/ReactFeatureFlags';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import invariant from 'shared/invariant';
+import warning from 'shared/warning';
import {
- requestCurrentTime as requestCurrentTime_new,
- computeExpirationForFiber as computeExpirationForFiber_new,
- captureCommitPhaseError as captureCommitPhaseError_new,
- onUncaughtError as onUncaughtError_new,
- markRenderEventTime as markRenderEventTime_new,
- renderDidSuspend as renderDidSuspend_new,
- renderDidError as renderDidError_new,
- pingSuspendedRoot as pingSuspendedRoot_new,
- retryTimedOutBoundary as retryTimedOutBoundary_new,
- resolveRetryThenable as resolveRetryThenable_new,
- markLegacyErrorBoundaryAsFailed as markLegacyErrorBoundaryAsFailed_new,
- isAlreadyFailedLegacyErrorBoundary as isAlreadyFailedLegacyErrorBoundary_new,
- scheduleWork as scheduleWork_new,
- flushRoot as flushRoot_new,
- batchedUpdates as batchedUpdates_new,
- unbatchedUpdates as unbatchedUpdates_new,
- flushSync as flushSync_new,
- flushControlled as flushControlled_new,
- deferredUpdates as deferredUpdates_new,
- syncUpdates as syncUpdates_new,
- interactiveUpdates as interactiveUpdates_new,
- flushInteractiveUpdates as flushInteractiveUpdates_new,
- computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_new,
- flushPassiveEffects as flushPassiveEffects_new,
- warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_new,
-} from './ReactFiberScheduler.new';
-
-export const requestCurrentTime = enableNewScheduler
- ? requestCurrentTime_new
- : requestCurrentTime_old;
-export const computeExpirationForFiber = enableNewScheduler
- ? computeExpirationForFiber_new
- : computeExpirationForFiber_old;
-export const captureCommitPhaseError = enableNewScheduler
- ? captureCommitPhaseError_new
- : captureCommitPhaseError_old;
-export const onUncaughtError = enableNewScheduler
- ? onUncaughtError_new
- : onUncaughtError_old;
-export const markRenderEventTime = enableNewScheduler
- ? markRenderEventTime_new
- : markRenderEventTime_old;
-export const renderDidSuspend = enableNewScheduler
- ? renderDidSuspend_new
- : renderDidSuspend_old;
-export const renderDidError = enableNewScheduler
- ? renderDidError_new
- : renderDidError_old;
-export const pingSuspendedRoot = enableNewScheduler
- ? pingSuspendedRoot_new
- : pingSuspendedRoot_old;
-export const retryTimedOutBoundary = enableNewScheduler
- ? retryTimedOutBoundary_new
- : retryTimedOutBoundary_old;
-export const resolveRetryThenable = enableNewScheduler
- ? resolveRetryThenable_new
- : resolveRetryThenable_old;
-export const markLegacyErrorBoundaryAsFailed = enableNewScheduler
- ? markLegacyErrorBoundaryAsFailed_new
- : markLegacyErrorBoundaryAsFailed_old;
-export const isAlreadyFailedLegacyErrorBoundary = enableNewScheduler
- ? isAlreadyFailedLegacyErrorBoundary_new
- : isAlreadyFailedLegacyErrorBoundary_old;
-export const scheduleWork = enableNewScheduler
- ? scheduleWork_new
- : scheduleWork_old;
-export const flushRoot = enableNewScheduler ? flushRoot_new : flushRoot_old;
-export const batchedUpdates = enableNewScheduler
- ? batchedUpdates_new
- : batchedUpdates_old;
-export const unbatchedUpdates = enableNewScheduler
- ? unbatchedUpdates_new
- : unbatchedUpdates_old;
-export const flushSync = enableNewScheduler ? flushSync_new : flushSync_old;
-export const flushControlled = enableNewScheduler
- ? flushControlled_new
- : flushControlled_old;
-export const deferredUpdates = enableNewScheduler
- ? deferredUpdates_new
- : deferredUpdates_old;
-export const syncUpdates = enableNewScheduler
- ? syncUpdates_new
- : syncUpdates_old;
-export const interactiveUpdates = enableNewScheduler
- ? interactiveUpdates_new
- : interactiveUpdates_old;
-export const flushInteractiveUpdates = enableNewScheduler
- ? flushInteractiveUpdates_new
- : flushInteractiveUpdates_old;
-export const computeUniqueAsyncExpiration = enableNewScheduler
- ? computeUniqueAsyncExpiration_new
- : computeUniqueAsyncExpiration_old;
-export const flushPassiveEffects = enableNewScheduler
- ? flushPassiveEffects_new
- : flushPassiveEffects_old;
-export const warnIfNotCurrentlyActingUpdatesInDev = enableNewScheduler
- ? warnIfNotCurrentlyActingUpdatesInDev_new
- : warnIfNotCurrentlyActingUpdatesInDev_old;
+ scheduleCallback,
+ cancelCallback,
+ getCurrentPriorityLevel,
+ runWithPriority,
+ shouldYield,
+ now,
+ ImmediatePriority,
+ UserBlockingPriority,
+ NormalPriority,
+ LowPriority,
+ IdlePriority,
+ flushImmediateQueue,
+} from './SchedulerWithReactIntegration';
+
+import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
+
+import {
+ prepareForCommit,
+ resetAfterCommit,
+ scheduleTimeout,
+ cancelTimeout,
+ noTimeout,
+} from './ReactFiberHostConfig';
+
+import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
+import {NoContext, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
+import {
+ HostRoot,
+ ClassComponent,
+ SuspenseComponent,
+ DehydratedSuspenseComponent,
+ FunctionComponent,
+ ForwardRef,
+ MemoComponent,
+ SimpleMemoComponent,
+} from 'shared/ReactWorkTags';
+import {
+ NoEffect,
+ PerformedWork,
+ Placement,
+ Update,
+ PlacementAndUpdate,
+ Deletion,
+ Ref,
+ ContentReset,
+ Snapshot,
+ Callback,
+ Passive,
+ Incomplete,
+ HostEffectMask,
+} from 'shared/ReactSideEffectTags';
+import {
+ NoWork,
+ Sync,
+ Never,
+ msToExpirationTime,
+ expirationTimeToMs,
+ computeInteractiveExpiration,
+ computeAsyncExpiration,
+ inferPriorityFromExpirationTime,
+ LOW_PRIORITY_EXPIRATION,
+} from './ReactFiberExpirationTime';
+import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
+import {completeWork} from './ReactFiberCompleteWork';
+import {
+ throwException,
+ unwindWork,
+ unwindInterruptedWork,
+ createRootErrorUpdate,
+ createClassErrorUpdate,
+} from './ReactFiberUnwindWork';
+import {
+ commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
+ commitLifeCycles as commitLayoutEffectOnFiber,
+ commitPassiveHookEffects,
+ commitPlacement,
+ commitWork,
+ commitDeletion,
+ commitDetachRef,
+ commitAttachRef,
+ commitResetTextContent,
+} from './ReactFiberCommitWork';
+import {enqueueUpdate} from './ReactUpdateQueue';
+// TODO: Ahaha Andrew is bad at spellling
+import {resetContextDependences as resetContextDependencies} from './ReactFiberNewContext';
+import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
+import {createCapturedValue} from './ReactCapturedValue';
+
+import {
+ recordCommitTime,
+ startProfilerTimer,
+ stopProfilerTimerIfRunningAndRecordDelta,
+} from './ReactProfilerTimer';
+
+// DEV stuff
+import warningWithoutStack from 'shared/warningWithoutStack';
+import getComponentName from 'shared/getComponentName';
+import ReactStrictModeWarnings from './ReactStrictModeWarnings';
+import {
+ phase as ReactCurrentDebugFiberPhaseInDEV,
+ resetCurrentFiber as resetCurrentDebugFiberInDEV,
+ setCurrentFiber as setCurrentDebugFiberInDEV,
+ getStackByFiberInDevAndProd,
+} from './ReactCurrentFiber';
+import {
+ recordEffect,
+ recordScheduleUpdate,
+ startRequestCallbackTimer,
+ stopRequestCallbackTimer,
+ startWorkTimer,
+ stopWorkTimer,
+ stopFailedWorkTimer,
+ startWorkLoopTimer,
+ stopWorkLoopTimer,
+ startCommitTimer,
+ stopCommitTimer,
+ startCommitSnapshotEffectsTimer,
+ stopCommitSnapshotEffectsTimer,
+ startCommitHostEffectsTimer,
+ stopCommitHostEffectsTimer,
+ startCommitLifeCyclesTimer,
+ stopCommitLifeCyclesTimer,
+} from './ReactDebugFiberPerf';
+import {
+ invokeGuardedCallback,
+ hasCaughtError,
+ clearCaughtError,
+} from 'shared/ReactErrorUtils';
+import {onCommitRoot} from './ReactFiberDevToolsHook';
+
+const ceil = Math.ceil;
+
+const {
+ ReactCurrentDispatcher,
+ ReactCurrentOwner,
+ ReactShouldWarnActingUpdates,
+} = ReactSharedInternals;
+
+type WorkPhase = 0 | 1 | 2 | 3 | 4 | 5;
+const NotWorking = 0;
+const BatchedPhase = 1;
+const LegacyUnbatchedPhase = 2;
+const FlushSyncPhase = 3;
+const RenderPhase = 4;
+const CommitPhase = 5;
+
+type RootExitStatus = 0 | 1 | 2 | 3;
+const RootIncomplete = 0;
+const RootErrored = 1;
+const RootSuspended = 2;
+const RootCompleted = 3;
export type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
+ then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
};
+
+// The phase of work we're currently in
+let workPhase: WorkPhase = NotWorking;
+// The root we're working on
+let workInProgressRoot: FiberRoot | null = null;
+// The fiber we're working on
+let workInProgress: Fiber | null = null;
+// The expiration time we're rendering
+let renderExpirationTime: ExpirationTime = NoWork;
+// Whether to root completed, errored, suspended, etc.
+let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
+// Most recent event time among processed updates during this render.
+// This is conceptually a time stamp but expressed in terms of an ExpirationTime
+// because we deal mostly with expiration times in the hot path, so this avoids
+// the conversion happening in the hot path.
+let workInProgressRootMostRecentEventTime: ExpirationTime = Sync;
+
+let nextEffect: Fiber | null = null;
+let hasUncaughtError = false;
+let firstUncaughtError = null;
+let legacyErrorBoundariesThatAlreadyFailed: Set | null = null;
+
+let rootDoesHavePassiveEffects: boolean = false;
+let rootWithPendingPassiveEffects: FiberRoot | null = null;
+let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
+
+let rootsWithPendingDiscreteUpdates: Map<
+ FiberRoot,
+ ExpirationTime,
+> | null = null;
+
+// Use these to prevent an infinite loop of nested updates
+const NESTED_UPDATE_LIMIT = 50;
+let nestedUpdateCount: number = 0;
+let rootWithNestedUpdates: FiberRoot | null = null;
+
+const NESTED_PASSIVE_UPDATE_LIMIT = 50;
+let nestedPassiveUpdateCount: number = 0;
+
+let interruptedBy: Fiber | null = null;
+
+// Expiration times are computed by adding to the current time (the start
+// time). However, if two updates are scheduled within the same event, we
+// should treat their start times as simultaneous, even if the actual clock
+// time has advanced between the first and second call.
+
+// In other words, because expiration times determine how updates are batched,
+// we want all updates of like priority that occur within the same event to
+// receive the same expiration time. Otherwise we get tearing.
+let initialTimeMs: number = now();
+let currentEventTime: ExpirationTime = NoWork;
+
+export function requestCurrentTime() {
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
+ // We're inside React, so it's fine to read the actual time.
+ return msToExpirationTime(now() - initialTimeMs);
+ }
+ // We're not inside React, so we may be in the middle of a browser event.
+ if (currentEventTime !== NoWork) {
+ // Use the same start time for all updates until we enter React again.
+ return currentEventTime;
+ }
+ // This is the first update since React yielded. Compute a new start time.
+ currentEventTime = msToExpirationTime(now() - initialTimeMs);
+ return currentEventTime;
+}
+
+export function computeExpirationForFiber(
+ currentTime: ExpirationTime,
+ fiber: Fiber,
+): ExpirationTime {
+ if ((fiber.mode & ConcurrentMode) === NoContext) {
+ return Sync;
+ }
+
+ if (workPhase === RenderPhase) {
+ // Use whatever time we're already rendering
+ return renderExpirationTime;
+ }
+
+ // Compute an expiration time based on the Scheduler priority.
+ let expirationTime;
+ const priorityLevel = getCurrentPriorityLevel();
+ switch (priorityLevel) {
+ case ImmediatePriority:
+ expirationTime = Sync;
+ break;
+ case UserBlockingPriority:
+ // TODO: Rename this to computeUserBlockingExpiration
+ expirationTime = computeInteractiveExpiration(currentTime);
+ break;
+ case NormalPriority:
+ case LowPriority: // TODO: Handle LowPriority
+ // TODO: Rename this to... something better.
+ expirationTime = computeAsyncExpiration(currentTime);
+ break;
+ case IdlePriority:
+ expirationTime = Never;
+ break;
+ default:
+ invariant(false, 'Expected a valid priority level');
+ }
+
+ // If we're in the middle of rendering a tree, do not update at the same
+ // expiration time that is already rendering.
+ if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
+ // This is a trick to move this update into a separate batch
+ expirationTime -= 1;
+ }
+
+ return expirationTime;
+}
+
+let lastUniqueAsyncExpiration = NoWork;
+export function computeUniqueAsyncExpiration(): ExpirationTime {
+ const currentTime = requestCurrentTime();
+ let result = computeAsyncExpiration(currentTime);
+ if (result <= lastUniqueAsyncExpiration) {
+ // Since we assume the current time monotonically increases, we only hit
+ // this branch when computeUniqueAsyncExpiration is fired multiple times
+ // within a 200ms window (or whatever the async bucket size is).
+ result -= 1;
+ }
+ lastUniqueAsyncExpiration = result;
+ return result;
+}
+
+export function scheduleUpdateOnFiber(
+ fiber: Fiber,
+ expirationTime: ExpirationTime,
+) {
+ checkForNestedUpdates();
+ warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
+
+ const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
+ if (root === null) {
+ warnAboutUpdateOnUnmountedFiberInDEV(fiber);
+ return;
+ }
+
+ root.pingTime = NoWork;
+
+ checkForInterruption(fiber, expirationTime);
+ recordScheduleUpdate();
+
+ if (expirationTime === Sync) {
+ if (workPhase === LegacyUnbatchedPhase) {
+ // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
+ // root inside of batchedUpdates should be synchronous, but layout updates
+ // should be deferred until the end of the batch.
+ let callback = renderRoot(root, Sync, true);
+ while (callback !== null) {
+ callback = callback(true);
+ }
+ } else {
+ scheduleCallbackForRoot(root, ImmediatePriority, Sync);
+ if (workPhase === NotWorking) {
+ // Flush the synchronous work now, wnless we're already working or inside
+ // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
+ // scheduleCallbackForFiber to preserve the ability to schedule a callback
+ // without immediately flushing it. We only do this for user-initated
+ // updates, to preserve historical behavior of sync mode.
+ flushImmediateQueue();
+ }
+ }
+ } else {
+ // TODO: computeExpirationForFiber also reads the priority. Pass the
+ // priority as an argument to that function and this one.
+ const priorityLevel = getCurrentPriorityLevel();
+ if (priorityLevel === UserBlockingPriority) {
+ // This is the result of a discrete event. Track the lowest priority
+ // discrete update per root so we can flush them early, if needed.
+ if (rootsWithPendingDiscreteUpdates === null) {
+ rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
+ } else {
+ const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
+ if (
+ lastDiscreteTime === undefined ||
+ lastDiscreteTime > expirationTime
+ ) {
+ rootsWithPendingDiscreteUpdates.set(root, expirationTime);
+ }
+ }
+ }
+ scheduleCallbackForRoot(root, priorityLevel, expirationTime);
+ }
+}
+export const scheduleWork = scheduleUpdateOnFiber;
+
+// This is split into a separate function so we can mark a fiber with pending
+// work without treating it as a typical update that originates from an event;
+// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
+// on a fiber.
+function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
+ // Update the source fiber's expiration time
+ if (fiber.expirationTime < expirationTime) {
+ fiber.expirationTime = expirationTime;
+ }
+ let alternate = fiber.alternate;
+ if (alternate !== null && alternate.expirationTime < expirationTime) {
+ alternate.expirationTime = expirationTime;
+ }
+ // Walk the parent path to the root and update the child expiration time.
+ let node = fiber.return;
+ let root = null;
+ if (node === null && fiber.tag === HostRoot) {
+ root = fiber.stateNode;
+ } else {
+ while (node !== null) {
+ alternate = node.alternate;
+ if (node.childExpirationTime < expirationTime) {
+ node.childExpirationTime = expirationTime;
+ if (
+ alternate !== null &&
+ alternate.childExpirationTime < expirationTime
+ ) {
+ alternate.childExpirationTime = expirationTime;
+ }
+ } else if (
+ alternate !== null &&
+ alternate.childExpirationTime < expirationTime
+ ) {
+ alternate.childExpirationTime = expirationTime;
+ }
+ if (node.return === null && node.tag === HostRoot) {
+ root = node.stateNode;
+ break;
+ }
+ node = node.return;
+ }
+ }
+
+ if (root !== null) {
+ // Update the first and last pending expiration times in this root
+ const firstPendingTime = root.firstPendingTime;
+ if (expirationTime > firstPendingTime) {
+ root.firstPendingTime = expirationTime;
+ }
+ const lastPendingTime = root.lastPendingTime;
+ if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
+ root.lastPendingTime = expirationTime;
+ }
+ }
+
+ return root;
+}
+
+// Use this function, along with runRootCallback, to ensure that only a single
+// callback per root is scheduled. It's still possible to call renderRoot
+// directly, but scheduling via this function helps avoid excessive callbacks.
+// It works by storing the callback node and expiration time on the root. When a
+// new callback comes in, it compares the expiration time to determine if it
+// should cancel the previous one. It also relies on commitRoot scheduling a
+// callback to render the next level, because that means we don't need a
+// separate callback per expiration time.
+function scheduleCallbackForRoot(
+ root: FiberRoot,
+ priorityLevel: ReactPriorityLevel,
+ expirationTime: ExpirationTime,
+) {
+ const existingCallbackExpirationTime = root.callbackExpirationTime;
+ if (existingCallbackExpirationTime < expirationTime) {
+ // New callback has higher priority than the existing one.
+ const existingCallbackNode = root.callbackNode;
+ if (existingCallbackNode !== null) {
+ cancelCallback(existingCallbackNode);
+ }
+ root.callbackExpirationTime = expirationTime;
+ const options =
+ expirationTime === Sync
+ ? null
+ : {timeout: expirationTimeToMs(expirationTime)};
+ root.callbackNode = scheduleCallback(
+ priorityLevel,
+ runRootCallback.bind(
+ null,
+ root,
+ renderRoot.bind(null, root, expirationTime),
+ ),
+ options,
+ );
+ if (
+ enableUserTimingAPI &&
+ expirationTime !== Sync &&
+ workPhase !== RenderPhase &&
+ workPhase !== CommitPhase
+ ) {
+ // Scheduled an async callback, and we're not already working. Add an
+ // entry to the flamegraph that shows we're waiting for a callback
+ // to fire.
+ startRequestCallbackTimer();
+ }
+ }
+
+ const timeoutHandle = root.timeoutHandle;
+ if (timeoutHandle !== noTimeout) {
+ // The root previous suspended and scheduled a timeout to commit a fallback
+ // state. Now that we have additional work, cancel the timeout.
+ root.timeoutHandle = noTimeout;
+ // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
+ cancelTimeout(timeoutHandle);
+ }
+
+ // Add the current set of interactions to the pending set associated with
+ // this root.
+ schedulePendingInteraction(root, expirationTime);
+}
+
+function runRootCallback(root, callback, isSync) {
+ const prevCallbackNode = root.callbackNode;
+ let continuation = null;
+ try {
+ continuation = callback(isSync);
+ if (continuation !== null) {
+ return runRootCallback.bind(null, root, continuation);
+ } else {
+ return null;
+ }
+ } finally {
+ // If the callback exits without returning a continuation, remove the
+ // corresponding callback node from the root. Unless the callback node
+ // has changed, which implies that it was already cancelled by a high
+ // priority update.
+ if (continuation === null && prevCallbackNode === root.callbackNode) {
+ root.callbackNode = null;
+ root.callbackExpirationTime = NoWork;
+ }
+ }
+}
+
+export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
+ invariant(
+ false,
+ 'work.commit(): Cannot commit while already rendering. This likely ' +
+ 'means you attempted to commit from inside a lifecycle method.',
+ );
+ }
+ scheduleCallback(
+ ImmediatePriority,
+ renderRoot.bind(null, root, expirationTime),
+ );
+ flushImmediateQueue();
+}
+
+export function flushInteractiveUpdates() {
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
+ // Can't synchronously flush interactive updates if React is already
+ // working. This is currently a no-op.
+ // TODO: Should we fire a warning? This happens if you synchronously invoke
+ // an input event inside an effect, like with `element.click()`.
+ return;
+ }
+ flushPendingDiscreteUpdates();
+}
+
+function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {
+ const firstBatch = root.firstBatch;
+ if (
+ firstBatch !== null &&
+ firstBatch._defer &&
+ firstBatch._expirationTime >= expirationTime
+ ) {
+ root.finishedWork = root.current.alternate;
+ root.pendingCommitExpirationTime = expirationTime;
+ scheduleCallback(NormalPriority, () => {
+ firstBatch._onComplete();
+ return null;
+ });
+ return true;
+ } else {
+ return false;
+ }
+}
+
+export function deferredUpdates(fn: () => A): A {
+ // TODO: Remove in favor of Scheduler.next
+ return runWithPriority(NormalPriority, fn);
+}
+
+export function interactiveUpdates(
+ fn: (A, B, C) => R,
+ a: A,
+ b: B,
+ c: C,
+): R {
+ if (workPhase === NotWorking) {
+ // TODO: Remove this call. Instead of doing this automatically, the caller
+ // should explicitly call flushInteractiveUpdates.
+ flushPendingDiscreteUpdates();
+ }
+ return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));
+}
+
+export function syncUpdates(
+ fn: (A, B, C) => R,
+ a: A,
+ b: B,
+ c: C,
+): R {
+ return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
+}
+
+function flushPendingDiscreteUpdates() {
+ if (rootsWithPendingDiscreteUpdates !== null) {
+ // For each root with pending discrete updates, schedule a callback to
+ // immediately flush them.
+ const roots = rootsWithPendingDiscreteUpdates;
+ rootsWithPendingDiscreteUpdates = null;
+ roots.forEach((expirationTime, root) => {
+ scheduleCallback(
+ ImmediatePriority,
+ renderRoot.bind(null, root, expirationTime),
+ );
+ });
+ // Now flush the immediate queue.
+ flushImmediateQueue();
+ }
+}
+
+export function batchedUpdates(fn: A => R, a: A): R {
+ if (workPhase !== NotWorking) {
+ // We're already working, or inside a batch, so batchedUpdates is a no-op.
+ return fn(a);
+ }
+ workPhase = BatchedPhase;
+ try {
+ return fn(a);
+ } finally {
+ workPhase = NotWorking;
+ // Flush the immediate callbacks that were scheduled during this batch
+ flushImmediateQueue();
+ }
+}
+
+export function unbatchedUpdates(fn: (a: A) => R, a: A): R {
+ if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {
+ // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is
+ // a no-op.
+ return fn(a);
+ }
+ const prevWorkPhase = workPhase;
+ workPhase = LegacyUnbatchedPhase;
+ try {
+ return fn(a);
+ } finally {
+ workPhase = prevWorkPhase;
+ }
+}
+
+export function flushSync(fn: A => R, a: A): R {
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
+ invariant(
+ false,
+ 'flushSync was called from inside a lifecycle method. It cannot be ' +
+ 'called when React is already rendering.',
+ );
+ }
+ const prevWorkPhase = workPhase;
+ workPhase = FlushSyncPhase;
+ try {
+ return runWithPriority(ImmediatePriority, fn.bind(null, a));
+ } finally {
+ workPhase = prevWorkPhase;
+ // Flush the immediate callbacks that were scheduled during this batch.
+ // Note that this will happen even if batchedUpdates is higher up
+ // the stack.
+ flushImmediateQueue();
+ }
+}
+
+export function flushControlled(fn: () => mixed): void {
+ const prevWorkPhase = workPhase;
+ workPhase = BatchedPhase;
+ try {
+ runWithPriority(ImmediatePriority, fn);
+ } finally {
+ workPhase = prevWorkPhase;
+ if (workPhase === NotWorking) {
+ // Flush the immediate callbacks that were scheduled during this batch
+ flushImmediateQueue();
+ }
+ }
+}
+
+function prepareFreshStack(root, expirationTime) {
+ root.pendingCommitExpirationTime = NoWork;
+
+ if (workInProgress !== null) {
+ let interruptedWork = workInProgress.return;
+ while (interruptedWork !== null) {
+ unwindInterruptedWork(interruptedWork);
+ interruptedWork = interruptedWork.return;
+ }
+ }
+ workInProgressRoot = root;
+ workInProgress = createWorkInProgress(root.current, null, expirationTime);
+ renderExpirationTime = expirationTime;
+ workInProgressRootExitStatus = RootIncomplete;
+ workInProgressRootMostRecentEventTime = Sync;
+
+ if (__DEV__) {
+ ReactStrictModeWarnings.discardPendingWarnings();
+ }
+}
+
+function renderRoot(
+ root: FiberRoot,
+ expirationTime: ExpirationTime,
+ isSync: boolean,
+): SchedulerCallback | null {
+ invariant(
+ workPhase !== RenderPhase && workPhase !== CommitPhase,
+ 'Should not already be working.',
+ );
+
+ if (enableUserTimingAPI && expirationTime !== Sync) {
+ const didExpire = isSync;
+ const timeoutMs = expirationTimeToMs(expirationTime);
+ stopRequestCallbackTimer(didExpire, timeoutMs);
+ }
+
+ if (root.firstPendingTime < expirationTime) {
+ // If there's no work left at this expiration time, exit immediately. This
+ // happens when multiple callbacks are scheduled for a single root, but an
+ // earlier callback flushes the work of a later one.
+ return null;
+ }
+
+ if (root.pendingCommitExpirationTime === expirationTime) {
+ // There's already a pending commit at this expiration time.
+ root.pendingCommitExpirationTime = NoWork;
+ return commitRoot.bind(null, root, expirationTime);
+ }
+
+ flushPassiveEffects();
+
+ // If the root or expiration time have changed, throw out the existing stack
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
+ if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
+ prepareFreshStack(root, expirationTime);
+ startWorkOnPendingInteraction(root, expirationTime);
+ }
+
+ // If we have a work-in-progress fiber, it means there's still work to do
+ // in this root.
+ if (workInProgress !== null) {
+ const prevWorkPhase = workPhase;
+ workPhase = RenderPhase;
+ let prevDispatcher = ReactCurrentDispatcher.current;
+ if (prevDispatcher === null) {
+ // The React isomorphic package does not include a default dispatcher.
+ // Instead the first renderer will lazily attach one, in order to give
+ // nicer error messages.
+ prevDispatcher = ContextOnlyDispatcher;
+ }
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
+ let prevInteractions: Set | null = null;
+ if (enableSchedulerTracing) {
+ prevInteractions = __interactionsRef.current;
+ __interactionsRef.current = root.memoizedInteractions;
+ }
+
+ startWorkLoopTimer(workInProgress);
+
+ // TODO: Fork renderRoot into renderRootSync and renderRootAsync
+ if (isSync) {
+ if (expirationTime !== Sync) {
+ // An async update expired. There may be other expired updates on
+ // this root. We should render all the expired work in a
+ // single batch.
+ const currentTime = requestCurrentTime();
+ if (currentTime < expirationTime) {
+ // Restart at the current time.
+ workPhase = prevWorkPhase;
+ resetContextDependencies();
+ ReactCurrentDispatcher.current = prevDispatcher;
+ if (enableSchedulerTracing) {
+ __interactionsRef.current = ((prevInteractions: any): Set<
+ Interaction,
+ >);
+ }
+ return renderRoot.bind(null, root, currentTime);
+ }
+ }
+ } else {
+ // Since we know we're in a React event, we can clear the current
+ // event time. The next update will compute a new event time.
+ currentEventTime = NoWork;
+ }
+
+ do {
+ try {
+ if (isSync) {
+ workLoopSync();
+ } else {
+ workLoop();
+ }
+ break;
+ } catch (thrownValue) {
+ // Reset module-level state that was set during the render phase.
+ resetContextDependencies();
+ resetHooks();
+
+ const sourceFiber = workInProgress;
+ if (sourceFiber === null || sourceFiber.return === null) {
+ // Expected to be working on a non-root fiber. This is a fatal error
+ // because there's no ancestor that can handle it; the root is
+ // supposed to capture all errors that weren't caught by an error
+ // boundary.
+ prepareFreshStack(root, expirationTime);
+ workPhase = prevWorkPhase;
+ throw thrownValue;
+ }
+
+ if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
+ // Record the time spent rendering before an error was thrown. This
+ // avoids inaccurate Profiler durations in the case of a
+ // suspended render.
+ stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
+ }
+
+ const returnFiber = sourceFiber.return;
+ throwException(
+ root,
+ returnFiber,
+ sourceFiber,
+ thrownValue,
+ renderExpirationTime,
+ );
+ workInProgress = completeUnitOfWork(sourceFiber);
+ }
+ } while (true);
+
+ workPhase = prevWorkPhase;
+ resetContextDependencies();
+ ReactCurrentDispatcher.current = prevDispatcher;
+ if (enableSchedulerTracing) {
+ __interactionsRef.current = ((prevInteractions: any): Set);
+ }
+
+ if (workInProgress !== null) {
+ // There's still work left over. Return a continuation.
+ stopInterruptedWorkLoopTimer();
+ if (expirationTime !== Sync) {
+ startRequestCallbackTimer();
+ }
+ return renderRoot.bind(null, root, expirationTime);
+ }
+ }
+
+ // We now have a consistent tree. The next step is either to commit it, or, if
+ // something suspended, wait to commit it after a timeout.
+ stopFinishedWorkLoopTimer();
+
+ const isLocked = resolveLocksOnRoot(root, expirationTime);
+ if (isLocked) {
+ // This root has a lock that prevents it from committing. Exit. If we begin
+ // work on the root again, without any intervening updates, it will finish
+ // without doing additional work.
+ return null;
+ }
+
+ // Set this to null to indicate there's no in-progress render.
+ workInProgressRoot = null;
+
+ switch (workInProgressRootExitStatus) {
+ case RootIncomplete: {
+ invariant(false, 'Should have a work-in-progress.');
+ }
+ // Flow knows about invariant, so it compains if I add a break statement,
+ // but eslint doesn't know about invariant, so it complains if I do.
+ // eslint-disable-next-line no-fallthrough
+ case RootErrored: {
+ // An error was thrown. First check if there is lower priority work
+ // scheduled on this root.
+ const lastPendingTime = root.lastPendingTime;
+ if (root.lastPendingTime < expirationTime) {
+ // There's lower priority work. Before raising the error, try rendering
+ // at the lower priority to see if it fixes it. Use a continuation to
+ // maintain the existing priority and position in the queue.
+ return renderRoot.bind(null, root, lastPendingTime);
+ }
+ if (!isSync) {
+ // If we're rendering asynchronously, it's possible the error was
+ // caused by tearing due to a mutation during an event. Try rendering
+ // one more time without yiedling to events.
+ prepareFreshStack(root, expirationTime);
+ scheduleCallback(
+ ImmediatePriority,
+ renderRoot.bind(null, root, expirationTime),
+ );
+ return null;
+ }
+ // If we're already rendering synchronously, commit the root in its
+ // errored state.
+ return commitRoot.bind(null, root, expirationTime);
+ }
+ case RootSuspended: {
+ if (!isSync) {
+ const lastPendingTime = root.lastPendingTime;
+ if (root.lastPendingTime < expirationTime) {
+ // There's lower priority work. It might be unsuspended. Try rendering
+ // at that level.
+ return renderRoot.bind(null, root, lastPendingTime);
+ }
+ // If workInProgressRootMostRecentEventTime is Sync, that means we didn't
+ // track any event times. That can happen if we retried but nothing switched
+ // from fallback to content. There's no reason to delay doing no work.
+ if (workInProgressRootMostRecentEventTime !== Sync) {
+ let msUntilTimeout = computeMsUntilTimeout(
+ workInProgressRootMostRecentEventTime,
+ expirationTime,
+ );
+ // Don't bother with a very short suspense time.
+ if (msUntilTimeout > 10) {
+ // The render is suspended, it hasn't timed out, and there's no lower
+ // priority work to do. Instead of committing the fallback
+ // immediately, wait for more data to arrive.
+ root.timeoutHandle = scheduleTimeout(
+ commitRoot.bind(null, root, expirationTime),
+ msUntilTimeout,
+ );
+ return null;
+ }
+ }
+ }
+ // The work expired. Commit immediately.
+ return commitRoot.bind(null, root, expirationTime);
+ }
+ case RootCompleted: {
+ // The work completed. Ready to commit.
+ return commitRoot.bind(null, root, expirationTime);
+ }
+ default: {
+ invariant(false, 'Unknown root exit status.');
+ }
+ }
+}
+
+export function markRenderEventTime(expirationTime: ExpirationTime): void {
+ if (expirationTime < workInProgressRootMostRecentEventTime) {
+ workInProgressRootMostRecentEventTime = expirationTime;
+ }
+}
+
+export function renderDidSuspend(): void {
+ if (workInProgressRootExitStatus === RootIncomplete) {
+ workInProgressRootExitStatus = RootSuspended;
+ }
+}
+
+export function renderDidError() {
+ if (
+ workInProgressRootExitStatus === RootIncomplete ||
+ workInProgressRootExitStatus === RootSuspended
+ ) {
+ workInProgressRootExitStatus = RootErrored;
+ }
+}
+
+function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {
+ // We don't know exactly when the update was scheduled, but we can infer an
+ // approximate start time from the expiration time.
+ const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
+ return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION + initialTimeMs;
+}
+
+function workLoopSync() {
+ // Already timed out, so perform work without checking if we need to yield.
+ while (workInProgress !== null) {
+ workInProgress = performUnitOfWork(workInProgress);
+ }
+}
+
+function workLoop() {
+ // Perform work until Scheduler asks us to yield
+ while (workInProgress !== null && !shouldYield()) {
+ workInProgress = performUnitOfWork(workInProgress);
+ }
+}
+
+function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
+ // The current, flushed, state of this fiber is the alternate. Ideally
+ // nothing should rely on this, but relying on it here means that we don't
+ // need an additional field on the work in progress.
+ const current = unitOfWork.alternate;
+
+ startWorkTimer(unitOfWork);
+ setCurrentDebugFiberInDEV(unitOfWork);
+
+ let next;
+ if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {
+ startProfilerTimer(unitOfWork);
+ next = beginWork(current, unitOfWork, renderExpirationTime);
+ stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
+ } else {
+ next = beginWork(current, unitOfWork, renderExpirationTime);
+ }
+
+ resetCurrentDebugFiberInDEV();
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
+ if (next === null) {
+ // If this doesn't spawn new work, complete the current work.
+ next = completeUnitOfWork(unitOfWork);
+ }
+
+ ReactCurrentOwner.current = null;
+ return next;
+}
+
+function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
+ // Attempt to complete the current unit of work, then move to the next
+ // sibling. If there are no more siblings, return to the parent fiber.
+ workInProgress = unitOfWork;
+ do {
+ // The current, flushed, state of this fiber is the alternate. Ideally
+ // nothing should rely on this, but relying on it here means that we don't
+ // need an additional field on the work in progress.
+ const current = workInProgress.alternate;
+ const returnFiber = workInProgress.return;
+
+ // Check if the work completed or if something threw.
+ if ((workInProgress.effectTag & Incomplete) === NoEffect) {
+ setCurrentDebugFiberInDEV(workInProgress);
+ let next;
+ if (
+ !enableProfilerTimer ||
+ (workInProgress.mode & ProfileMode) === NoContext
+ ) {
+ next = completeWork(current, workInProgress, renderExpirationTime);
+ } else {
+ startProfilerTimer(workInProgress);
+ next = completeWork(current, workInProgress, renderExpirationTime);
+ // Update render duration assuming we didn't error.
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
+ }
+ stopWorkTimer(workInProgress);
+ resetCurrentDebugFiberInDEV();
+ resetChildExpirationTime(workInProgress);
+
+ if (next !== null) {
+ // Completing this fiber spawned new work. Work on that next.
+ return next;
+ }
+
+ if (
+ returnFiber !== null &&
+ // Do not append effects to parents if a sibling failed to complete
+ (returnFiber.effectTag & Incomplete) === NoEffect
+ ) {
+ // Append all the effects of the subtree and this fiber onto the effect
+ // list of the parent. The completion order of the children affects the
+ // side-effect order.
+ if (returnFiber.firstEffect === null) {
+ returnFiber.firstEffect = workInProgress.firstEffect;
+ }
+ if (workInProgress.lastEffect !== null) {
+ if (returnFiber.lastEffect !== null) {
+ returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
+ }
+ returnFiber.lastEffect = workInProgress.lastEffect;
+ }
+
+ // If this fiber had side-effects, we append it AFTER the children's
+ // side-effects. We can perform certain side-effects earlier if needed,
+ // by doing multiple passes over the effect list. We don't want to
+ // schedule our own side-effect on our own list because if end up
+ // reusing children we'll schedule this effect onto itself since we're
+ // at the end.
+ const effectTag = workInProgress.effectTag;
+
+ // Skip both NoWork and PerformedWork tags when creating the effect
+ // list. PerformedWork effect is read by React DevTools but shouldn't be
+ // committed.
+ if (effectTag > PerformedWork) {
+ if (returnFiber.lastEffect !== null) {
+ returnFiber.lastEffect.nextEffect = workInProgress;
+ } else {
+ returnFiber.firstEffect = workInProgress;
+ }
+ returnFiber.lastEffect = workInProgress;
+ }
+ }
+ } else {
+ // This fiber did not complete because something threw. Pop values off
+ // the stack without entering the complete phase. If this is a boundary,
+ // capture values if possible.
+ const next = unwindWork(workInProgress, renderExpirationTime);
+
+ // Because this fiber did not complete, don't reset its expiration time.
+
+ if (
+ enableProfilerTimer &&
+ (workInProgress.mode & ProfileMode) !== NoContext
+ ) {
+ // Record the render duration for the fiber that errored.
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
+
+ // Include the time spent working on failed children before continuing.
+ let actualDuration = workInProgress.actualDuration;
+ let child = workInProgress.child;
+ while (child !== null) {
+ actualDuration += child.actualDuration;
+ child = child.sibling;
+ }
+ workInProgress.actualDuration = actualDuration;
+ }
+
+ if (next !== null) {
+ // If completing this work spawned new work, do that next. We'll come
+ // back here again.
+ // Since we're restarting, remove anything that is not a host effect
+ // from the effect tag.
+ // TODO: The name stopFailedWorkTimer is misleading because Suspense
+ // also captures and restarts.
+ stopFailedWorkTimer(workInProgress);
+ next.effectTag &= HostEffectMask;
+ return next;
+ }
+ stopWorkTimer(workInProgress);
+
+ if (returnFiber !== null) {
+ // Mark the parent fiber as incomplete and clear its effect list.
+ returnFiber.firstEffect = returnFiber.lastEffect = null;
+ returnFiber.effectTag |= Incomplete;
+ }
+ }
+
+ const siblingFiber = workInProgress.sibling;
+ if (siblingFiber !== null) {
+ // If there is more work to do in this returnFiber, do that next.
+ return siblingFiber;
+ }
+ // Otherwise, return to the parent
+ workInProgress = returnFiber;
+ } while (workInProgress !== null);
+
+ // We've reached the root.
+ if (workInProgressRootExitStatus === RootIncomplete) {
+ workInProgressRootExitStatus = RootCompleted;
+ }
+ return null;
+}
+
+function resetChildExpirationTime(completedWork: Fiber) {
+ if (
+ renderExpirationTime !== Never &&
+ completedWork.childExpirationTime === Never
+ ) {
+ // The children of this component are hidden. Don't bubble their
+ // expiration times.
+ return;
+ }
+
+ let newChildExpirationTime = NoWork;
+
+ // Bubble up the earliest expiration time.
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {
+ // In profiling mode, resetChildExpirationTime is also used to reset
+ // profiler durations.
+ let actualDuration = completedWork.actualDuration;
+ let treeBaseDuration = completedWork.selfBaseDuration;
+
+ // When a fiber is cloned, its actualDuration is reset to 0. This value will
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
+ // When work is done, it should bubble to the parent's actualDuration. If
+ // the fiber has not been cloned though, (meaning no work was done), then
+ // this value will reflect the amount of time spent working on a previous
+ // render. In that case it should not bubble. We determine whether it was
+ // cloned by comparing the child pointer.
+ const shouldBubbleActualDurations =
+ completedWork.alternate === null ||
+ completedWork.child !== completedWork.alternate.child;
+
+ let child = completedWork.child;
+ while (child !== null) {
+ const childUpdateExpirationTime = child.expirationTime;
+ const childChildExpirationTime = child.childExpirationTime;
+ if (childUpdateExpirationTime > newChildExpirationTime) {
+ newChildExpirationTime = childUpdateExpirationTime;
+ }
+ if (childChildExpirationTime > newChildExpirationTime) {
+ newChildExpirationTime = childChildExpirationTime;
+ }
+ if (shouldBubbleActualDurations) {
+ actualDuration += child.actualDuration;
+ }
+ treeBaseDuration += child.treeBaseDuration;
+ child = child.sibling;
+ }
+ completedWork.actualDuration = actualDuration;
+ completedWork.treeBaseDuration = treeBaseDuration;
+ } else {
+ let child = completedWork.child;
+ while (child !== null) {
+ const childUpdateExpirationTime = child.expirationTime;
+ const childChildExpirationTime = child.childExpirationTime;
+ if (childUpdateExpirationTime > newChildExpirationTime) {
+ newChildExpirationTime = childUpdateExpirationTime;
+ }
+ if (childChildExpirationTime > newChildExpirationTime) {
+ newChildExpirationTime = childChildExpirationTime;
+ }
+ child = child.sibling;
+ }
+ }
+
+ completedWork.childExpirationTime = newChildExpirationTime;
+}
+
+function commitRoot(root, expirationTime) {
+ runWithPriority(
+ ImmediatePriority,
+ commitRootImpl.bind(null, root, expirationTime),
+ );
+ // If there are passive effects, schedule a callback to flush them. This goes
+ // outside commitRootImpl so that it inherits the priority of the render.
+ if (rootWithPendingPassiveEffects !== null) {
+ const priorityLevel = getCurrentPriorityLevel();
+ scheduleCallback(priorityLevel, () => {
+ flushPassiveEffects();
+ return null;
+ });
+ }
+ return null;
+}
+
+function commitRootImpl(root, expirationTime) {
+ flushPassiveEffects();
+ flushRenderPhaseStrictModeWarningsInDEV();
+
+ invariant(
+ workPhase !== RenderPhase && workPhase !== CommitPhase,
+ 'Should not already be working.',
+ );
+ const finishedWork = root.current.alternate;
+ invariant(finishedWork !== null, 'Should have a work-in-progress root.');
+
+ // commitRoot never returns a continuation; it always finishes synchronously.
+ // So we can clear these now to allow a new callback to be scheduled.
+ root.callbackNode = null;
+ root.callbackExpirationTime = NoWork;
+
+ startCommitTimer();
+
+ // Update the first and last pending times on this root. The new first
+ // pending time is whatever is left on the root fiber.
+ const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
+ const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
+ const firstPendingTimeBeforeCommit =
+ childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
+ ? childExpirationTimeBeforeCommit
+ : updateExpirationTimeBeforeCommit;
+ root.firstPendingTime = firstPendingTimeBeforeCommit;
+ if (firstPendingTimeBeforeCommit < root.lastPendingTime) {
+ // This usually means we've finished all the work, but it can also happen
+ // when something gets downprioritized during render, like a hidden tree.
+ root.lastPendingTime = firstPendingTimeBeforeCommit;
+ }
+
+ if (root === workInProgressRoot) {
+ // We can reset these now that they are finished.
+ workInProgressRoot = null;
+ workInProgress = null;
+ renderExpirationTime = NoWork;
+ } else {
+ // This indicates that the last root we worked on is not the same one that
+ // we're committing now. This most commonly happens when a suspended root
+ // times out.
+ }
+
+ // Get the list of effects.
+ let firstEffect;
+ if (finishedWork.effectTag > PerformedWork) {
+ // A fiber's effect list consists only of its children, not itself. So if
+ // the root has an effect, we need to add it to the end of the list. The
+ // resulting list is the set that would belong to the root's parent, if it
+ // had one; that is, all the effects in the tree including the root.
+ if (finishedWork.lastEffect !== null) {
+ finishedWork.lastEffect.nextEffect = finishedWork;
+ firstEffect = finishedWork.firstEffect;
+ } else {
+ firstEffect = finishedWork;
+ }
+ } else {
+ // There is no effect on the root.
+ firstEffect = finishedWork.firstEffect;
+ }
+
+ if (firstEffect !== null) {
+ const prevWorkPhase = workPhase;
+ workPhase = CommitPhase;
+ let prevInteractions: Set | null = null;
+ if (enableSchedulerTracing) {
+ prevInteractions = __interactionsRef.current;
+ __interactionsRef.current = root.memoizedInteractions;
+ }
+
+ // Reset this to null before calling lifecycles
+ ReactCurrentOwner.current = null;
+
+ // The commit phase is broken into several sub-phases. We do a separate pass
+ // of the effect list for each phase: all mutation effects come before all
+ // layout effects, and so on.
+
+ // The first phase a "before mutation" phase. We use this phase to read the
+ // state of the host tree right before we mutate it. This is where
+ // getSnapshotBeforeUpdate is called.
+ startCommitSnapshotEffectsTimer();
+ prepareForCommit(root.containerInfo);
+ nextEffect = firstEffect;
+ do {
+ if (__DEV__) {
+ invokeGuardedCallback(null, commitBeforeMutationEffects, null);
+ if (hasCaughtError()) {
+ invariant(nextEffect !== null, 'Should be working on an effect.');
+ const error = clearCaughtError();
+ captureCommitPhaseError(nextEffect, error);
+ nextEffect = nextEffect.nextEffect;
+ }
+ } else {
+ try {
+ commitBeforeMutationEffects();
+ } catch (error) {
+ invariant(nextEffect !== null, 'Should be working on an effect.');
+ captureCommitPhaseError(nextEffect, error);
+ nextEffect = nextEffect.nextEffect;
+ }
+ }
+ } while (nextEffect !== null);
+ stopCommitSnapshotEffectsTimer();
+
+ if (enableProfilerTimer) {
+ // Mark the current commit time to be shared by all Profilers in this
+ // batch. This enables them to be grouped later.
+ recordCommitTime();
+ }
+
+ // The next phase is the mutation phase, where we mutate the host tree.
+ startCommitHostEffectsTimer();
+ nextEffect = firstEffect;
+ do {
+ if (__DEV__) {
+ invokeGuardedCallback(null, commitMutationEffects, null);
+ if (hasCaughtError()) {
+ invariant(nextEffect !== null, 'Should be working on an effect.');
+ const error = clearCaughtError();
+ captureCommitPhaseError(nextEffect, error);
+ nextEffect = nextEffect.nextEffect;
+ }
+ } else {
+ try {
+ commitMutationEffects();
+ } catch (error) {
+ invariant(nextEffect !== null, 'Should be working on an effect.');
+ captureCommitPhaseError(nextEffect, error);
+ nextEffect = nextEffect.nextEffect;
+ }
+ }
+ } while (nextEffect !== null);
+ stopCommitHostEffectsTimer();
+ resetAfterCommit(root.containerInfo);
+
+ // The work-in-progress tree is now the current tree. This must come after
+ // the mutation phase, so that the previous tree is still current during
+ // componentWillUnmount, but before the layout phase, so that the finished
+ // work is current during componentDidMount/Update.
+ root.current = finishedWork;
+
+ // The next phase is the layout phase, where we call effects that read
+ // the host tree after it's been mutated. The idiomatic use case for this is
+ // layout, but class component lifecycles also fire here for legacy reasons.
+ startCommitLifeCyclesTimer();
+ nextEffect = firstEffect;
+ do {
+ if (__DEV__) {
+ invokeGuardedCallback(
+ null,
+ commitLayoutEffects,
+ null,
+ root,
+ expirationTime,
+ );
+ if (hasCaughtError()) {
+ invariant(nextEffect !== null, 'Should be working on an effect.');
+ const error = clearCaughtError();
+ captureCommitPhaseError(nextEffect, error);
+ nextEffect = nextEffect.nextEffect;
+ }
+ } else {
+ try {
+ commitLayoutEffects(root, expirationTime);
+ } catch (error) {
+ invariant(nextEffect !== null, 'Should be working on an effect.');
+ captureCommitPhaseError(nextEffect, error);
+ nextEffect = nextEffect.nextEffect;
+ }
+ }
+ } while (nextEffect !== null);
+ stopCommitLifeCyclesTimer();
+
+ nextEffect = null;
+
+ if (enableSchedulerTracing) {
+ __interactionsRef.current = ((prevInteractions: any): Set);
+ }
+ workPhase = prevWorkPhase;
+ } else {
+ // No effects.
+ root.current = finishedWork;
+ // Measure these anyway so the flamegraph explicitly shows that there were
+ // no effects.
+ // TODO: Maybe there's a better way to report this.
+ startCommitSnapshotEffectsTimer();
+ stopCommitSnapshotEffectsTimer();
+ if (enableProfilerTimer) {
+ recordCommitTime();
+ }
+ startCommitHostEffectsTimer();
+ stopCommitHostEffectsTimer();
+ startCommitLifeCyclesTimer();
+ stopCommitLifeCyclesTimer();
+ }
+
+ stopCommitTimer();
+
+ if (rootDoesHavePassiveEffects) {
+ // This commit has passive effects. Stash a reference to them. But don't
+ // schedule a callback until after flushing layout work.
+ rootDoesHavePassiveEffects = false;
+ rootWithPendingPassiveEffects = root;
+ pendingPassiveEffectsExpirationTime = expirationTime;
+ } else {
+ if (enableSchedulerTracing) {
+ // If there are no passive effects, then we can complete the pending
+ // interactions. Otherwise, we'll wait until after the passive effects
+ // are flushed.
+ finishPendingInteractions(root, expirationTime);
+ }
+ }
+
+ // Check if there's remaining work on this root
+ const remainingExpirationTime = root.firstPendingTime;
+ if (remainingExpirationTime !== NoWork) {
+ const currentTime = requestCurrentTime();
+ const priorityLevel = inferPriorityFromExpirationTime(
+ currentTime,
+ remainingExpirationTime,
+ );
+ scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);
+ } else {
+ // If there's no remaining work, we can clear the set of already failed
+ // error boundaries.
+ legacyErrorBoundariesThatAlreadyFailed = null;
+ }
+
+ onCommitRoot(finishedWork.stateNode);
+
+ if (remainingExpirationTime === Sync) {
+ // Count the number of times the root synchronously re-renders without
+ // finishing. If there are too many, it indicates an infinite update loop.
+ if (root === rootWithNestedUpdates) {
+ nestedUpdateCount++;
+ } else {
+ nestedUpdateCount = 0;
+ rootWithNestedUpdates = root;
+ }
+ } else {
+ nestedUpdateCount = 0;
+ }
+
+ if (hasUncaughtError) {
+ hasUncaughtError = false;
+ const error = firstUncaughtError;
+ firstUncaughtError = null;
+ throw error;
+ }
+
+ if (workPhase === LegacyUnbatchedPhase) {
+ // This is a legacy edge case. We just committed the initial mount of
+ // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
+ // synchronously, but layout updates should be deferred until the end
+ // of the batch.
+ return null;
+ }
+
+ // If layout work was scheduled, flush it now.
+ flushImmediateQueue();
+ return null;
+}
+
+function commitBeforeMutationEffects() {
+ while (nextEffect !== null) {
+ if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
+ setCurrentDebugFiberInDEV(nextEffect);
+ recordEffect();
+
+ const current = nextEffect.alternate;
+ commitBeforeMutationEffectOnFiber(current, nextEffect);
+
+ resetCurrentDebugFiberInDEV();
+ }
+ nextEffect = nextEffect.nextEffect;
+ }
+}
+
+function commitMutationEffects() {
+ // TODO: Should probably move the bulk of this function to commitWork.
+ while (nextEffect !== null) {
+ setCurrentDebugFiberInDEV(nextEffect);
+
+ const effectTag = nextEffect.effectTag;
+
+ if (effectTag & ContentReset) {
+ commitResetTextContent(nextEffect);
+ }
+
+ if (effectTag & Ref) {
+ const current = nextEffect.alternate;
+ if (current !== null) {
+ commitDetachRef(current);
+ }
+ }
+
+ // The following switch statement is only concerned about placement,
+ // updates, and deletions. To avoid needing to add a case for every possible
+ // bitmap value, we remove the secondary effects from the effect tag and
+ // switch on that value.
+ let primaryEffectTag = effectTag & (Placement | Update | Deletion);
+ switch (primaryEffectTag) {
+ case Placement: {
+ commitPlacement(nextEffect);
+ // Clear the "placement" from effect tag so that we know that this is
+ // inserted, before any life-cycles like componentDidMount gets called.
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
+ // and isMounted is deprecated anyway so we should be able to kill this.
+ nextEffect.effectTag &= ~Placement;
+ break;
+ }
+ case PlacementAndUpdate: {
+ // Placement
+ commitPlacement(nextEffect);
+ // Clear the "placement" from effect tag so that we know that this is
+ // inserted, before any life-cycles like componentDidMount gets called.
+ nextEffect.effectTag &= ~Placement;
+
+ // Update
+ const current = nextEffect.alternate;
+ commitWork(current, nextEffect);
+ break;
+ }
+ case Update: {
+ const current = nextEffect.alternate;
+ commitWork(current, nextEffect);
+ break;
+ }
+ case Deletion: {
+ commitDeletion(nextEffect);
+ break;
+ }
+ }
+
+ // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
+ recordEffect();
+
+ resetCurrentDebugFiberInDEV();
+ nextEffect = nextEffect.nextEffect;
+ }
+}
+
+function commitLayoutEffects(
+ root: FiberRoot,
+ committedExpirationTime: ExpirationTime,
+) {
+ // TODO: Should probably move the bulk of this function to commitWork.
+ while (nextEffect !== null) {
+ setCurrentDebugFiberInDEV(nextEffect);
+
+ const effectTag = nextEffect.effectTag;
+
+ if (effectTag & (Update | Callback)) {
+ recordEffect();
+ const current = nextEffect.alternate;
+ commitLayoutEffectOnFiber(
+ root,
+ current,
+ nextEffect,
+ committedExpirationTime,
+ );
+ }
+
+ if (effectTag & Ref) {
+ recordEffect();
+ commitAttachRef(nextEffect);
+ }
+
+ if (effectTag & Passive) {
+ rootDoesHavePassiveEffects = true;
+ }
+
+ resetCurrentDebugFiberInDEV();
+ nextEffect = nextEffect.nextEffect;
+ }
+}
+
+export function flushPassiveEffects() {
+ if (rootWithPendingPassiveEffects === null) {
+ return false;
+ }
+ const root = rootWithPendingPassiveEffects;
+ const expirationTime = pendingPassiveEffectsExpirationTime;
+ rootWithPendingPassiveEffects = null;
+ pendingPassiveEffectsExpirationTime = NoWork;
+
+ let prevInteractions: Set | null = null;
+ if (enableSchedulerTracing) {
+ prevInteractions = __interactionsRef.current;
+ __interactionsRef.current = root.memoizedInteractions;
+ }
+
+ invariant(
+ workPhase !== RenderPhase && workPhase !== CommitPhase,
+ 'Cannot flush passive effects while already rendering.',
+ );
+ const prevWorkPhase = workPhase;
+ workPhase = CommitPhase;
+
+ // Note: This currently assumes there are no passive effects on the root
+ // fiber, because the root is not part of its own effect list. This could
+ // change in the future.
+ let effect = root.current.firstEffect;
+ while (effect !== null) {
+ if (__DEV__) {
+ setCurrentDebugFiberInDEV(effect);
+ invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
+ if (hasCaughtError()) {
+ invariant(effect !== null, 'Should be working on an effect.');
+ const error = clearCaughtError();
+ captureCommitPhaseError(effect, error);
+ }
+ resetCurrentDebugFiberInDEV();
+ } else {
+ try {
+ commitPassiveHookEffects(effect);
+ } catch (error) {
+ invariant(effect !== null, 'Should be working on an effect.');
+ captureCommitPhaseError(effect, error);
+ }
+ }
+ effect = effect.nextEffect;
+ }
+
+ if (enableSchedulerTracing) {
+ __interactionsRef.current = ((prevInteractions: any): Set);
+ finishPendingInteractions(root, expirationTime);
+ }
+
+ workPhase = prevWorkPhase;
+ flushImmediateQueue();
+
+ // If additional passive effects were scheduled, increment a counter. If this
+ // exceeds the limit, we'll fire a warning.
+ nestedPassiveUpdateCount =
+ rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
+
+ return true;
+}
+
+export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
+ return (
+ legacyErrorBoundariesThatAlreadyFailed !== null &&
+ legacyErrorBoundariesThatAlreadyFailed.has(instance)
+ );
+}
+
+export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
+ if (legacyErrorBoundariesThatAlreadyFailed === null) {
+ legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
+ } else {
+ legacyErrorBoundariesThatAlreadyFailed.add(instance);
+ }
+}
+
+function prepareToThrowUncaughtError(error: mixed) {
+ if (!hasUncaughtError) {
+ hasUncaughtError = true;
+ firstUncaughtError = error;
+ }
+}
+export const onUncaughtError = prepareToThrowUncaughtError;
+
+function captureCommitPhaseErrorOnRoot(
+ rootFiber: Fiber,
+ sourceFiber: Fiber,
+ error: mixed,
+) {
+ const errorInfo = createCapturedValue(error, sourceFiber);
+ const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
+ enqueueUpdate(rootFiber, update);
+ const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
+ if (root !== null) {
+ scheduleCallbackForRoot(root, ImmediatePriority, Sync);
+ }
+}
+
+export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
+ if (sourceFiber.tag === HostRoot) {
+ // Error was thrown at the root. There is no parent, so the root
+ // itself should capture it.
+ captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
+ return;
+ }
+
+ let fiber = sourceFiber.return;
+ while (fiber !== null) {
+ if (fiber.tag === HostRoot) {
+ captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
+ return;
+ } else if (fiber.tag === ClassComponent) {
+ const ctor = fiber.type;
+ const instance = fiber.stateNode;
+ if (
+ typeof ctor.getDerivedStateFromError === 'function' ||
+ (typeof instance.componentDidCatch === 'function' &&
+ !isAlreadyFailedLegacyErrorBoundary(instance))
+ ) {
+ const errorInfo = createCapturedValue(error, sourceFiber);
+ const update = createClassErrorUpdate(
+ fiber,
+ errorInfo,
+ // TODO: This is always sync
+ Sync,
+ );
+ enqueueUpdate(fiber, update);
+ const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
+ if (root !== null) {
+ scheduleCallbackForRoot(root, ImmediatePriority, Sync);
+ }
+ return;
+ }
+ }
+ fiber = fiber.return;
+ }
+}
+
+export function pingSuspendedRoot(
+ root: FiberRoot,
+ thenable: Thenable,
+ suspendedTime: ExpirationTime,
+) {
+ const pingCache = root.pingCache;
+ if (pingCache !== null) {
+ // The thenable resolved, so we no longer need to memoize, because it will
+ // never be thrown again.
+ pingCache.delete(thenable);
+ }
+
+ if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
+ // Received a ping at the same priority level at which we're currently
+ // rendering. Restart from the root. Don't need to schedule a ping because
+ // we're already working on this tree.
+ prepareFreshStack(root, renderExpirationTime);
+ return;
+ }
+
+ const lastPendingTime = root.lastPendingTime;
+ if (lastPendingTime < suspendedTime) {
+ // The root is no longer suspended at this time.
+ return;
+ }
+
+ const pingTime = root.pingTime;
+ if (pingTime !== NoWork && pingTime < suspendedTime) {
+ // There's already a lower priority ping scheduled.
+ return;
+ }
+
+ // Mark the time at which this ping was scheduled.
+ root.pingTime = suspendedTime;
+
+ const currentTime = requestCurrentTime();
+ const priorityLevel = inferPriorityFromExpirationTime(
+ currentTime,
+ suspendedTime,
+ );
+ scheduleCallbackForRoot(root, priorityLevel, suspendedTime);
+}
+
+export function retryTimedOutBoundary(boundaryFiber: Fiber) {
+ // The boundary fiber (a Suspense component) previously timed out and was
+ // rendered in its fallback state. One of the promises that suspended it has
+ // resolved, which means at least part of the tree was likely unblocked. Try
+ // rendering again, at a new expiration time.
+ const currentTime = requestCurrentTime();
+ const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
+ // TODO: Special case idle priority?
+ const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);
+ const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
+ if (root !== null) {
+ scheduleCallbackForRoot(root, priorityLevel, retryTime);
+ }
+}
+
+export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
+ let retryCache: WeakSet | Set | null;
+ if (enableSuspenseServerRenderer) {
+ switch (boundaryFiber.tag) {
+ case SuspenseComponent:
+ retryCache = boundaryFiber.stateNode;
+ break;
+ case DehydratedSuspenseComponent:
+ retryCache = boundaryFiber.memoizedState;
+ break;
+ default:
+ invariant(
+ false,
+ 'Pinged unknown suspense boundary type. ' +
+ 'This is probably a bug in React.',
+ );
+ }
+ } else {
+ retryCache = boundaryFiber.stateNode;
+ }
+
+ if (retryCache !== null) {
+ // The thenable resolved, so we no longer need to memoize, because it will
+ // never be thrown again.
+ retryCache.delete(thenable);
+ }
+
+ retryTimedOutBoundary(boundaryFiber);
+}
+
+// Computes the next Just Noticeable Difference (JND) boundary.
+// The theory is that a person can't tell the difference between small differences in time.
+// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
+// difference in the experience. However, waiting for longer might mean that we can avoid
+// showing an intermediate loading state. The longer we have already waited, the harder it
+// is to tell small differences in time. Therefore, the longer we've already waited,
+// the longer we can wait additionally. At some point we have to give up though.
+// We pick a train model where the next boundary commits at a consistent schedule.
+// These particular numbers are vague estimates. We expect to adjust them based on research.
+function jnd(timeElapsed: number) {
+ return timeElapsed < 120
+ ? 120
+ : timeElapsed < 480
+ ? 480
+ : timeElapsed < 1080
+ ? 1080
+ : timeElapsed < 1920
+ ? 1920
+ : timeElapsed < 3000
+ ? 3000
+ : timeElapsed < 4320
+ ? 4320
+ : ceil(timeElapsed / 1960) * 1960;
+}
+
+function computeMsUntilTimeout(
+ mostRecentEventTime: ExpirationTime,
+ committedExpirationTime: ExpirationTime,
+) {
+ if (disableYielding) {
+ // Timeout immediately when yielding is disabled.
+ return 0;
+ }
+
+ const eventTimeMs: number = inferTimeFromExpirationTime(mostRecentEventTime);
+ const currentTimeMs: number = now();
+ const timeElapsed = currentTimeMs - eventTimeMs;
+
+ let msUntilTimeout = jnd(timeElapsed) - timeElapsed;
+
+ // Compute the time until this render pass would expire.
+ const timeUntilExpirationMs =
+ expirationTimeToMs(committedExpirationTime) + initialTimeMs - currentTimeMs;
+
+ // Clamp the timeout to the expiration time.
+ // TODO: Once the event time is exact instead of inferred from expiration time
+ // we don't need this.
+ if (timeUntilExpirationMs < msUntilTimeout) {
+ msUntilTimeout = timeUntilExpirationMs;
+ }
+
+ // This is the value that is passed to `setTimeout`.
+ return msUntilTimeout;
+}
+
+function checkForNestedUpdates() {
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
+ nestedUpdateCount = 0;
+ rootWithNestedUpdates = null;
+ invariant(
+ false,
+ 'Maximum update depth exceeded. This can happen when a component ' +
+ 'repeatedly calls setState inside componentWillUpdate or ' +
+ 'componentDidUpdate. React limits the number of nested updates to ' +
+ 'prevent infinite loops.',
+ );
+ }
+
+ if (__DEV__) {
+ if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
+ nestedPassiveUpdateCount = 0;
+ warning(
+ false,
+ 'Maximum update depth exceeded. This can happen when a component ' +
+ "calls setState inside useEffect, but useEffect either doesn't " +
+ 'have a dependency array, or one of the dependencies changes on ' +
+ 'every render.',
+ );
+ }
+ }
+}
+
+function flushRenderPhaseStrictModeWarningsInDEV() {
+ if (__DEV__) {
+ ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
+ ReactStrictModeWarnings.flushLegacyContextWarning();
+
+ if (warnAboutDeprecatedLifecycles) {
+ ReactStrictModeWarnings.flushPendingDeprecationWarnings();
+ }
+ }
+}
+
+function stopFinishedWorkLoopTimer() {
+ const didCompleteRoot = true;
+ stopWorkLoopTimer(interruptedBy, didCompleteRoot);
+ interruptedBy = null;
+}
+
+function stopInterruptedWorkLoopTimer() {
+ // TODO: Track which fiber caused the interruption.
+ const didCompleteRoot = false;
+ stopWorkLoopTimer(interruptedBy, didCompleteRoot);
+ interruptedBy = null;
+}
+
+function checkForInterruption(
+ fiberThatReceivedUpdate: Fiber,
+ updateExpirationTime: ExpirationTime,
+) {
+ if (
+ enableUserTimingAPI &&
+ workInProgressRoot !== null &&
+ updateExpirationTime > renderExpirationTime
+ ) {
+ interruptedBy = fiberThatReceivedUpdate;
+ }
+}
+
+let didWarnStateUpdateForUnmountedComponent: Set | null = null;
+function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
+ if (__DEV__) {
+ const tag = fiber.tag;
+ if (
+ tag !== HostRoot &&
+ tag !== ClassComponent &&
+ tag !== FunctionComponent &&
+ tag !== ForwardRef &&
+ tag !== MemoComponent &&
+ tag !== SimpleMemoComponent
+ ) {
+ // Only warn for user-defined components, not internal ones like Suspense.
+ return;
+ }
+ // We show the whole stack but dedupe on the top component's name because
+ // the problematic code almost always lies inside that component.
+ const componentName = getComponentName(fiber.type) || 'ReactComponent';
+ if (didWarnStateUpdateForUnmountedComponent !== null) {
+ if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
+ return;
+ }
+ didWarnStateUpdateForUnmountedComponent.add(componentName);
+ } else {
+ didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
+ }
+ warningWithoutStack(
+ false,
+ "Can't perform a React state update on an unmounted component. This " +
+ 'is a no-op, but it indicates a memory leak in your application. To ' +
+ 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
+ tag === ClassComponent
+ ? 'the componentWillUnmount method'
+ : 'a useEffect cleanup function',
+ getStackByFiberInDevAndProd(fiber),
+ );
+ }
+}
+
+let beginWork;
+if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
+ let dummyFiber = null;
+ beginWork = (current, unitOfWork, expirationTime) => {
+ // If a component throws an error, we replay it again in a synchronously
+ // dispatched event, so that the debugger will treat it as an uncaught
+ // error See ReactErrorUtils for more information.
+
+ // Before entering the begin phase, copy the work-in-progress onto a dummy
+ // fiber. If beginWork throws, we'll use this to reset the state.
+ const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
+ dummyFiber,
+ unitOfWork,
+ );
+ try {
+ return originalBeginWork(current, unitOfWork, expirationTime);
+ } catch (originalError) {
+ if (
+ originalError !== null &&
+ typeof originalError === 'object' &&
+ typeof originalError.then === 'function'
+ ) {
+ // Don't replay promises. Treat everything else like an error.
+ throw originalError;
+ }
+
+ // Keep this code in sync with renderRoot; any changes here must have
+ // corresponding changes there.
+ resetContextDependencies();
+ resetHooks();
+
+ // Unwind the failed stack frame
+ unwindInterruptedWork(unitOfWork);
+
+ // Restore the original properties of the fiber.
+ assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
+
+ if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
+ // Reset the profiler timer.
+ startProfilerTimer(unitOfWork);
+ }
+
+ // Run beginWork again.
+ invokeGuardedCallback(
+ null,
+ originalBeginWork,
+ null,
+ current,
+ unitOfWork,
+ expirationTime,
+ );
+
+ if (hasCaughtError()) {
+ const replayError = clearCaughtError();
+ // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
+ // Rethrow this error instead of the original one.
+ throw replayError;
+ } else {
+ // This branch is reachable if the render phase is impure.
+ throw originalError;
+ }
+ }
+ };
+} else {
+ beginWork = originalBeginWork;
+}
+
+let didWarnAboutUpdateInRender = false;
+let didWarnAboutUpdateInGetChildContext = false;
+function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
+ if (__DEV__) {
+ if (fiber.tag === ClassComponent) {
+ switch (ReactCurrentDebugFiberPhaseInDEV) {
+ case 'getChildContext':
+ if (didWarnAboutUpdateInGetChildContext) {
+ return;
+ }
+ warningWithoutStack(
+ false,
+ 'setState(...): Cannot call setState() inside getChildContext()',
+ );
+ didWarnAboutUpdateInGetChildContext = true;
+ break;
+ case 'render':
+ if (didWarnAboutUpdateInRender) {
+ return;
+ }
+ warningWithoutStack(
+ false,
+ 'Cannot update during an existing state transition (such as ' +
+ 'within `render`). Render methods should be a pure function of ' +
+ 'props and state.',
+ );
+ didWarnAboutUpdateInRender = true;
+ break;
+ }
+ }
+ }
+}
+
+function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
+ if (__DEV__) {
+ if (
+ workPhase === NotWorking &&
+ ReactShouldWarnActingUpdates.current === false
+ ) {
+ warningWithoutStack(
+ false,
+ 'An update to %s inside a test was not wrapped in act(...).\n\n' +
+ 'When testing, code that causes React state updates should be ' +
+ 'wrapped into act(...):\n\n' +
+ 'act(() => {\n' +
+ ' /* fire events that update state */\n' +
+ '});\n' +
+ '/* assert on the output */\n\n' +
+ "This ensures that you're testing the behavior the user would see " +
+ 'in the browser.' +
+ ' Learn more at https://fb.me/react-wrap-tests-with-act' +
+ '%s',
+ getComponentName(fiber.type),
+ getStackByFiberInDevAndProd(fiber),
+ );
+ }
+ }
+}
+
+export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
+
+function computeThreadID(root, expirationTime) {
+ // Interaction threads are unique per root and expiration time.
+ return expirationTime * 1000 + root.interactionThreadID;
+}
+
+function schedulePendingInteraction(root, expirationTime) {
+ // This is called when work is scheduled on a root. It sets up a pending
+ // interaction, which is completed once the work commits.
+ if (!enableSchedulerTracing) {
+ return;
+ }
+
+ const interactions = __interactionsRef.current;
+ if (interactions.size > 0) {
+ const pendingInteractionMap = root.pendingInteractionMap;
+ const pendingInteractions = pendingInteractionMap.get(expirationTime);
+ if (pendingInteractions != null) {
+ interactions.forEach(interaction => {
+ if (!pendingInteractions.has(interaction)) {
+ // Update the pending async work count for previously unscheduled interaction.
+ interaction.__count++;
+ }
+
+ pendingInteractions.add(interaction);
+ });
+ } else {
+ pendingInteractionMap.set(expirationTime, new Set(interactions));
+
+ // Update the pending async work count for the current interactions.
+ interactions.forEach(interaction => {
+ interaction.__count++;
+ });
+ }
+
+ const subscriber = __subscriberRef.current;
+ if (subscriber !== null) {
+ const threadID = computeThreadID(root, expirationTime);
+ subscriber.onWorkScheduled(interactions, threadID);
+ }
+ }
+}
+
+function startWorkOnPendingInteraction(root, expirationTime) {
+ // This is called when new work is started on a root.
+ if (!enableSchedulerTracing) {
+ return;
+ }
+
+ // Determine which interactions this batch of work currently includes, So that
+ // we can accurately attribute time spent working on it, And so that cascading
+ // work triggered during the render phase will be associated with it.
+ const interactions: Set = new Set();
+ root.pendingInteractionMap.forEach(
+ (scheduledInteractions, scheduledExpirationTime) => {
+ if (scheduledExpirationTime >= expirationTime) {
+ scheduledInteractions.forEach(interaction =>
+ interactions.add(interaction),
+ );
+ }
+ },
+ );
+
+ // Store the current set of interactions on the FiberRoot for a few reasons:
+ // We can re-use it in hot functions like renderRoot() without having to
+ // recalculate it. We will also use it in commitWork() to pass to any Profiler
+ // onRender() hooks. This also provides DevTools with a way to access it when
+ // the onCommitRoot() hook is called.
+ root.memoizedInteractions = interactions;
+
+ if (interactions.size > 0) {
+ const subscriber = __subscriberRef.current;
+ if (subscriber !== null) {
+ const threadID = computeThreadID(root, expirationTime);
+ try {
+ subscriber.onWorkStarted(interactions, threadID);
+ } catch (error) {
+ // If the subscriber throws, rethrow it in a separate task
+ scheduleCallback(ImmediatePriority, () => {
+ throw error;
+ });
+ }
+ }
+ }
+}
+
+function finishPendingInteractions(root, committedExpirationTime) {
+ if (!enableSchedulerTracing) {
+ return;
+ }
+
+ const earliestRemainingTimeAfterCommit = root.firstPendingTime;
+
+ let subscriber;
+
+ try {
+ subscriber = __subscriberRef.current;
+ if (subscriber !== null && root.memoizedInteractions.size > 0) {
+ const threadID = computeThreadID(root, committedExpirationTime);
+ subscriber.onWorkStopped(root.memoizedInteractions, threadID);
+ }
+ } catch (error) {
+ // If the subscriber throws, rethrow it in a separate task
+ scheduleCallback(ImmediatePriority, () => {
+ throw error;
+ });
+ } finally {
+ // Clear completed interactions from the pending Map.
+ // Unless the render was suspended or cascading work was scheduled,
+ // In which case– leave pending interactions until the subsequent render.
+ const pendingInteractionMap = root.pendingInteractionMap;
+ pendingInteractionMap.forEach(
+ (scheduledInteractions, scheduledExpirationTime) => {
+ // Only decrement the pending interaction count if we're done.
+ // If there's still work at the current priority,
+ // That indicates that we are waiting for suspense data.
+ if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
+ pendingInteractionMap.delete(scheduledExpirationTime);
+
+ scheduledInteractions.forEach(interaction => {
+ interaction.__count--;
+
+ if (subscriber !== null && interaction.__count === 0) {
+ try {
+ subscriber.onInteractionScheduledWorkCompleted(interaction);
+ } catch (error) {
+ // If the subscriber throws, rethrow it in a separate task
+ scheduleCallback(ImmediatePriority, () => {
+ throw error;
+ });
+ }
+ }
+ });
+ }
+ },
+ );
+ }
+}
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.new.js b/packages/react-reconciler/src/ReactFiberScheduler.new.js
deleted file mode 100644
index 9c1065a568781..0000000000000
--- a/packages/react-reconciler/src/ReactFiberScheduler.new.js
+++ /dev/null
@@ -1,2244 +0,0 @@
-/**
- * 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
- */
-
-import type {Fiber} from './ReactFiber';
-import type {FiberRoot} from './ReactFiberRoot';
-import type {ExpirationTime} from './ReactFiberExpirationTime';
-import type {
- ReactPriorityLevel,
- SchedulerCallback,
-} from './SchedulerWithReactIntegration';
-import type {Interaction} from 'scheduler/src/Tracing';
-
-import {
- warnAboutDeprecatedLifecycles,
- enableUserTimingAPI,
- enableSuspenseServerRenderer,
- replayFailedUnitOfWorkWithInvokeGuardedCallback,
- enableProfilerTimer,
- disableYielding,
- enableSchedulerTracing,
-} from 'shared/ReactFeatureFlags';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-import invariant from 'shared/invariant';
-import warning from 'shared/warning';
-
-import {
- scheduleCallback,
- cancelCallback,
- getCurrentPriorityLevel,
- runWithPriority,
- shouldYield,
- now,
- ImmediatePriority,
- UserBlockingPriority,
- NormalPriority,
- LowPriority,
- IdlePriority,
- flushImmediateQueue,
-} from './SchedulerWithReactIntegration';
-
-import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
-
-import {
- prepareForCommit,
- resetAfterCommit,
- scheduleTimeout,
- cancelTimeout,
- noTimeout,
-} from './ReactFiberHostConfig';
-
-import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
-import {NoContext, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
-import {
- HostRoot,
- ClassComponent,
- SuspenseComponent,
- DehydratedSuspenseComponent,
- FunctionComponent,
- ForwardRef,
- MemoComponent,
- SimpleMemoComponent,
-} from 'shared/ReactWorkTags';
-import {
- NoEffect,
- PerformedWork,
- Placement,
- Update,
- PlacementAndUpdate,
- Deletion,
- Ref,
- ContentReset,
- Snapshot,
- Callback,
- Passive,
- Incomplete,
- HostEffectMask,
-} from 'shared/ReactSideEffectTags';
-import {
- NoWork,
- Sync,
- Never,
- msToExpirationTime,
- expirationTimeToMs,
- computeInteractiveExpiration,
- computeAsyncExpiration,
- inferPriorityFromExpirationTime,
- LOW_PRIORITY_EXPIRATION,
-} from './ReactFiberExpirationTime';
-import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
-import {completeWork} from './ReactFiberCompleteWork';
-import {
- throwException,
- unwindWork,
- unwindInterruptedWork,
- createRootErrorUpdate,
- createClassErrorUpdate,
-} from './ReactFiberUnwindWork';
-import {
- commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
- commitLifeCycles as commitLayoutEffectOnFiber,
- commitPassiveHookEffects,
- commitPlacement,
- commitWork,
- commitDeletion,
- commitDetachRef,
- commitAttachRef,
- commitResetTextContent,
-} from './ReactFiberCommitWork';
-import {enqueueUpdate} from './ReactUpdateQueue';
-// TODO: Ahaha Andrew is bad at spellling
-import {resetContextDependences as resetContextDependencies} from './ReactFiberNewContext';
-import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
-import {createCapturedValue} from './ReactCapturedValue';
-
-import {
- recordCommitTime,
- startProfilerTimer,
- stopProfilerTimerIfRunningAndRecordDelta,
-} from './ReactProfilerTimer';
-
-// DEV stuff
-import warningWithoutStack from 'shared/warningWithoutStack';
-import getComponentName from 'shared/getComponentName';
-import ReactStrictModeWarnings from './ReactStrictModeWarnings';
-import {
- phase as ReactCurrentDebugFiberPhaseInDEV,
- resetCurrentFiber as resetCurrentDebugFiberInDEV,
- setCurrentFiber as setCurrentDebugFiberInDEV,
- getStackByFiberInDevAndProd,
-} from './ReactCurrentFiber';
-import {
- recordEffect,
- recordScheduleUpdate,
- startRequestCallbackTimer,
- stopRequestCallbackTimer,
- startWorkTimer,
- stopWorkTimer,
- stopFailedWorkTimer,
- startWorkLoopTimer,
- stopWorkLoopTimer,
- startCommitTimer,
- stopCommitTimer,
- startCommitSnapshotEffectsTimer,
- stopCommitSnapshotEffectsTimer,
- startCommitHostEffectsTimer,
- stopCommitHostEffectsTimer,
- startCommitLifeCyclesTimer,
- stopCommitLifeCyclesTimer,
-} from './ReactDebugFiberPerf';
-import {
- invokeGuardedCallback,
- hasCaughtError,
- clearCaughtError,
-} from 'shared/ReactErrorUtils';
-import {onCommitRoot} from './ReactFiberDevToolsHook';
-
-const ceil = Math.ceil;
-
-const {
- ReactCurrentDispatcher,
- ReactCurrentOwner,
- ReactShouldWarnActingUpdates,
-} = ReactSharedInternals;
-
-type WorkPhase = 0 | 1 | 2 | 3 | 4 | 5;
-const NotWorking = 0;
-const BatchedPhase = 1;
-const LegacyUnbatchedPhase = 2;
-const FlushSyncPhase = 3;
-const RenderPhase = 4;
-const CommitPhase = 5;
-
-type RootExitStatus = 0 | 1 | 2 | 3;
-const RootIncomplete = 0;
-const RootErrored = 1;
-const RootSuspended = 2;
-const RootCompleted = 3;
-
-export type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
-};
-
-// The phase of work we're currently in
-let workPhase: WorkPhase = NotWorking;
-// The root we're working on
-let workInProgressRoot: FiberRoot | null = null;
-// The fiber we're working on
-let workInProgress: Fiber | null = null;
-// The expiration time we're rendering
-let renderExpirationTime: ExpirationTime = NoWork;
-// Whether to root completed, errored, suspended, etc.
-let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
-// Most recent event time among processed updates during this render.
-// This is conceptually a time stamp but expressed in terms of an ExpirationTime
-// because we deal mostly with expiration times in the hot path, so this avoids
-// the conversion happening in the hot path.
-let workInProgressRootMostRecentEventTime: ExpirationTime = Sync;
-
-let nextEffect: Fiber | null = null;
-let hasUncaughtError = false;
-let firstUncaughtError = null;
-let legacyErrorBoundariesThatAlreadyFailed: Set | null = null;
-
-let rootDoesHavePassiveEffects: boolean = false;
-let rootWithPendingPassiveEffects: FiberRoot | null = null;
-let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
-
-let rootsWithPendingDiscreteUpdates: Map<
- FiberRoot,
- ExpirationTime,
-> | null = null;
-
-// Use these to prevent an infinite loop of nested updates
-const NESTED_UPDATE_LIMIT = 50;
-let nestedUpdateCount: number = 0;
-let rootWithNestedUpdates: FiberRoot | null = null;
-
-const NESTED_PASSIVE_UPDATE_LIMIT = 50;
-let nestedPassiveUpdateCount: number = 0;
-
-let interruptedBy: Fiber | null = null;
-
-// Expiration times are computed by adding to the current time (the start
-// time). However, if two updates are scheduled within the same event, we
-// should treat their start times as simultaneous, even if the actual clock
-// time has advanced between the first and second call.
-
-// In other words, because expiration times determine how updates are batched,
-// we want all updates of like priority that occur within the same event to
-// receive the same expiration time. Otherwise we get tearing.
-let initialTimeMs: number = now();
-let currentEventTime: ExpirationTime = NoWork;
-
-export function requestCurrentTime() {
- if (workPhase === RenderPhase || workPhase === CommitPhase) {
- // We're inside React, so it's fine to read the actual time.
- return msToExpirationTime(now() - initialTimeMs);
- }
- // We're not inside React, so we may be in the middle of a browser event.
- if (currentEventTime !== NoWork) {
- // Use the same start time for all updates until we enter React again.
- return currentEventTime;
- }
- // This is the first update since React yielded. Compute a new start time.
- currentEventTime = msToExpirationTime(now() - initialTimeMs);
- return currentEventTime;
-}
-
-export function computeExpirationForFiber(
- currentTime: ExpirationTime,
- fiber: Fiber,
-): ExpirationTime {
- if ((fiber.mode & ConcurrentMode) === NoContext) {
- return Sync;
- }
-
- if (workPhase === RenderPhase) {
- // Use whatever time we're already rendering
- return renderExpirationTime;
- }
-
- // Compute an expiration time based on the Scheduler priority.
- let expirationTime;
- const priorityLevel = getCurrentPriorityLevel();
- switch (priorityLevel) {
- case ImmediatePriority:
- expirationTime = Sync;
- break;
- case UserBlockingPriority:
- // TODO: Rename this to computeUserBlockingExpiration
- expirationTime = computeInteractiveExpiration(currentTime);
- break;
- case NormalPriority:
- case LowPriority: // TODO: Handle LowPriority
- // TODO: Rename this to... something better.
- expirationTime = computeAsyncExpiration(currentTime);
- break;
- case IdlePriority:
- expirationTime = Never;
- break;
- default:
- invariant(false, 'Expected a valid priority level');
- }
-
- // If we're in the middle of rendering a tree, do not update at the same
- // expiration time that is already rendering.
- if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
- // This is a trick to move this update into a separate batch
- expirationTime -= 1;
- }
-
- return expirationTime;
-}
-
-let lastUniqueAsyncExpiration = NoWork;
-export function computeUniqueAsyncExpiration(): ExpirationTime {
- const currentTime = requestCurrentTime();
- let result = computeAsyncExpiration(currentTime);
- if (result <= lastUniqueAsyncExpiration) {
- // Since we assume the current time monotonically increases, we only hit
- // this branch when computeUniqueAsyncExpiration is fired multiple times
- // within a 200ms window (or whatever the async bucket size is).
- result -= 1;
- }
- lastUniqueAsyncExpiration = result;
- return result;
-}
-
-export function scheduleUpdateOnFiber(
- fiber: Fiber,
- expirationTime: ExpirationTime,
-) {
- checkForNestedUpdates();
- warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
-
- const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
- if (root === null) {
- warnAboutUpdateOnUnmountedFiberInDEV(fiber);
- return;
- }
-
- root.pingTime = NoWork;
-
- checkForInterruption(fiber, expirationTime);
- recordScheduleUpdate();
-
- if (expirationTime === Sync) {
- if (workPhase === LegacyUnbatchedPhase) {
- // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
- // root inside of batchedUpdates should be synchronous, but layout updates
- // should be deferred until the end of the batch.
- let callback = renderRoot(root, Sync, true);
- while (callback !== null) {
- callback = callback(true);
- }
- } else {
- scheduleCallbackForRoot(root, ImmediatePriority, Sync);
- if (workPhase === NotWorking) {
- // Flush the synchronous work now, wnless we're already working or inside
- // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
- // scheduleCallbackForFiber to preserve the ability to schedule a callback
- // without immediately flushing it. We only do this for user-initated
- // updates, to preserve historical behavior of sync mode.
- flushImmediateQueue();
- }
- }
- } else {
- // TODO: computeExpirationForFiber also reads the priority. Pass the
- // priority as an argument to that function and this one.
- const priorityLevel = getCurrentPriorityLevel();
- if (priorityLevel === UserBlockingPriority) {
- // This is the result of a discrete event. Track the lowest priority
- // discrete update per root so we can flush them early, if needed.
- if (rootsWithPendingDiscreteUpdates === null) {
- rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
- } else {
- const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
- if (
- lastDiscreteTime === undefined ||
- lastDiscreteTime > expirationTime
- ) {
- rootsWithPendingDiscreteUpdates.set(root, expirationTime);
- }
- }
- }
- scheduleCallbackForRoot(root, priorityLevel, expirationTime);
- }
-}
-export const scheduleWork = scheduleUpdateOnFiber;
-
-// This is split into a separate function so we can mark a fiber with pending
-// work without treating it as a typical update that originates from an event;
-// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
-// on a fiber.
-function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
- // Update the source fiber's expiration time
- if (fiber.expirationTime < expirationTime) {
- fiber.expirationTime = expirationTime;
- }
- let alternate = fiber.alternate;
- if (alternate !== null && alternate.expirationTime < expirationTime) {
- alternate.expirationTime = expirationTime;
- }
- // Walk the parent path to the root and update the child expiration time.
- let node = fiber.return;
- let root = null;
- if (node === null && fiber.tag === HostRoot) {
- root = fiber.stateNode;
- } else {
- while (node !== null) {
- alternate = node.alternate;
- if (node.childExpirationTime < expirationTime) {
- node.childExpirationTime = expirationTime;
- if (
- alternate !== null &&
- alternate.childExpirationTime < expirationTime
- ) {
- alternate.childExpirationTime = expirationTime;
- }
- } else if (
- alternate !== null &&
- alternate.childExpirationTime < expirationTime
- ) {
- alternate.childExpirationTime = expirationTime;
- }
- if (node.return === null && node.tag === HostRoot) {
- root = node.stateNode;
- break;
- }
- node = node.return;
- }
- }
-
- if (root !== null) {
- // Update the first and last pending expiration times in this root
- const firstPendingTime = root.firstPendingTime;
- if (expirationTime > firstPendingTime) {
- root.firstPendingTime = expirationTime;
- }
- const lastPendingTime = root.lastPendingTime;
- if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
- root.lastPendingTime = expirationTime;
- }
- }
-
- return root;
-}
-
-// Use this function, along with runRootCallback, to ensure that only a single
-// callback per root is scheduled. It's still possible to call renderRoot
-// directly, but scheduling via this function helps avoid excessive callbacks.
-// It works by storing the callback node and expiration time on the root. When a
-// new callback comes in, it compares the expiration time to determine if it
-// should cancel the previous one. It also relies on commitRoot scheduling a
-// callback to render the next level, because that means we don't need a
-// separate callback per expiration time.
-function scheduleCallbackForRoot(
- root: FiberRoot,
- priorityLevel: ReactPriorityLevel,
- expirationTime: ExpirationTime,
-) {
- const existingCallbackExpirationTime = root.callbackExpirationTime;
- if (existingCallbackExpirationTime < expirationTime) {
- // New callback has higher priority than the existing one.
- const existingCallbackNode = root.callbackNode;
- if (existingCallbackNode !== null) {
- cancelCallback(existingCallbackNode);
- }
- root.callbackExpirationTime = expirationTime;
- const options =
- expirationTime === Sync
- ? null
- : {timeout: expirationTimeToMs(expirationTime)};
- root.callbackNode = scheduleCallback(
- priorityLevel,
- runRootCallback.bind(
- null,
- root,
- renderRoot.bind(null, root, expirationTime),
- ),
- options,
- );
- if (
- enableUserTimingAPI &&
- expirationTime !== Sync &&
- workPhase !== RenderPhase &&
- workPhase !== CommitPhase
- ) {
- // Scheduled an async callback, and we're not already working. Add an
- // entry to the flamegraph that shows we're waiting for a callback
- // to fire.
- startRequestCallbackTimer();
- }
- }
-
- const timeoutHandle = root.timeoutHandle;
- if (timeoutHandle !== noTimeout) {
- // The root previous suspended and scheduled a timeout to commit a fallback
- // state. Now that we have additional work, cancel the timeout.
- root.timeoutHandle = noTimeout;
- // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
- cancelTimeout(timeoutHandle);
- }
-
- // Add the current set of interactions to the pending set associated with
- // this root.
- schedulePendingInteraction(root, expirationTime);
-}
-
-function runRootCallback(root, callback, isSync) {
- const prevCallbackNode = root.callbackNode;
- let continuation = null;
- try {
- continuation = callback(isSync);
- if (continuation !== null) {
- return runRootCallback.bind(null, root, continuation);
- } else {
- return null;
- }
- } finally {
- // If the callback exits without returning a continuation, remove the
- // corresponding callback node from the root. Unless the callback node
- // has changed, which implies that it was already cancelled by a high
- // priority update.
- if (continuation === null && prevCallbackNode === root.callbackNode) {
- root.callbackNode = null;
- root.callbackExpirationTime = NoWork;
- }
- }
-}
-
-export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
- if (workPhase === RenderPhase || workPhase === CommitPhase) {
- invariant(
- false,
- 'work.commit(): Cannot commit while already rendering. This likely ' +
- 'means you attempted to commit from inside a lifecycle method.',
- );
- }
- scheduleCallback(
- ImmediatePriority,
- renderRoot.bind(null, root, expirationTime),
- );
- flushImmediateQueue();
-}
-
-export function flushInteractiveUpdates() {
- if (workPhase === RenderPhase || workPhase === CommitPhase) {
- // Can't synchronously flush interactive updates if React is already
- // working. This is currently a no-op.
- // TODO: Should we fire a warning? This happens if you synchronously invoke
- // an input event inside an effect, like with `element.click()`.
- return;
- }
- flushPendingDiscreteUpdates();
-}
-
-function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {
- const firstBatch = root.firstBatch;
- if (
- firstBatch !== null &&
- firstBatch._defer &&
- firstBatch._expirationTime >= expirationTime
- ) {
- root.finishedWork = root.current.alternate;
- root.pendingCommitExpirationTime = expirationTime;
- scheduleCallback(NormalPriority, () => {
- firstBatch._onComplete();
- return null;
- });
- return true;
- } else {
- return false;
- }
-}
-
-export function deferredUpdates(fn: () => A): A {
- // TODO: Remove in favor of Scheduler.next
- return runWithPriority(NormalPriority, fn);
-}
-
-export function interactiveUpdates(
- fn: (A, B, C) => R,
- a: A,
- b: B,
- c: C,
-): R {
- if (workPhase === NotWorking) {
- // TODO: Remove this call. Instead of doing this automatically, the caller
- // should explicitly call flushInteractiveUpdates.
- flushPendingDiscreteUpdates();
- }
- return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));
-}
-
-export function syncUpdates(
- fn: (A, B, C) => R,
- a: A,
- b: B,
- c: C,
-): R {
- return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
-}
-
-function flushPendingDiscreteUpdates() {
- if (rootsWithPendingDiscreteUpdates !== null) {
- // For each root with pending discrete updates, schedule a callback to
- // immediately flush them.
- const roots = rootsWithPendingDiscreteUpdates;
- rootsWithPendingDiscreteUpdates = null;
- roots.forEach((expirationTime, root) => {
- scheduleCallback(
- ImmediatePriority,
- renderRoot.bind(null, root, expirationTime),
- );
- });
- // Now flush the immediate queue.
- flushImmediateQueue();
- }
-}
-
-export function batchedUpdates(fn: A => R, a: A): R {
- if (workPhase !== NotWorking) {
- // We're already working, or inside a batch, so batchedUpdates is a no-op.
- return fn(a);
- }
- workPhase = BatchedPhase;
- try {
- return fn(a);
- } finally {
- workPhase = NotWorking;
- // Flush the immediate callbacks that were scheduled during this batch
- flushImmediateQueue();
- }
-}
-
-export function unbatchedUpdates(fn: (a: A) => R, a: A): R {
- if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {
- // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is
- // a no-op.
- return fn(a);
- }
- const prevWorkPhase = workPhase;
- workPhase = LegacyUnbatchedPhase;
- try {
- return fn(a);
- } finally {
- workPhase = prevWorkPhase;
- }
-}
-
-export function flushSync(fn: A => R, a: A): R {
- if (workPhase === RenderPhase || workPhase === CommitPhase) {
- invariant(
- false,
- 'flushSync was called from inside a lifecycle method. It cannot be ' +
- 'called when React is already rendering.',
- );
- }
- const prevWorkPhase = workPhase;
- workPhase = FlushSyncPhase;
- try {
- return runWithPriority(ImmediatePriority, fn.bind(null, a));
- } finally {
- workPhase = prevWorkPhase;
- // Flush the immediate callbacks that were scheduled during this batch.
- // Note that this will happen even if batchedUpdates is higher up
- // the stack.
- flushImmediateQueue();
- }
-}
-
-export function flushControlled(fn: () => mixed): void {
- const prevWorkPhase = workPhase;
- workPhase = BatchedPhase;
- try {
- runWithPriority(ImmediatePriority, fn);
- } finally {
- workPhase = prevWorkPhase;
- if (workPhase === NotWorking) {
- // Flush the immediate callbacks that were scheduled during this batch
- flushImmediateQueue();
- }
- }
-}
-
-function prepareFreshStack(root, expirationTime) {
- root.pendingCommitExpirationTime = NoWork;
-
- if (workInProgress !== null) {
- let interruptedWork = workInProgress.return;
- while (interruptedWork !== null) {
- unwindInterruptedWork(interruptedWork);
- interruptedWork = interruptedWork.return;
- }
- }
- workInProgressRoot = root;
- workInProgress = createWorkInProgress(root.current, null, expirationTime);
- renderExpirationTime = expirationTime;
- workInProgressRootExitStatus = RootIncomplete;
- workInProgressRootMostRecentEventTime = Sync;
-
- if (__DEV__) {
- ReactStrictModeWarnings.discardPendingWarnings();
- }
-}
-
-function renderRoot(
- root: FiberRoot,
- expirationTime: ExpirationTime,
- isSync: boolean,
-): SchedulerCallback | null {
- invariant(
- workPhase !== RenderPhase && workPhase !== CommitPhase,
- 'Should not already be working.',
- );
-
- if (enableUserTimingAPI && expirationTime !== Sync) {
- const didExpire = isSync;
- const timeoutMs = expirationTimeToMs(expirationTime);
- stopRequestCallbackTimer(didExpire, timeoutMs);
- }
-
- if (root.firstPendingTime < expirationTime) {
- // If there's no work left at this expiration time, exit immediately. This
- // happens when multiple callbacks are scheduled for a single root, but an
- // earlier callback flushes the work of a later one.
- return null;
- }
-
- if (root.pendingCommitExpirationTime === expirationTime) {
- // There's already a pending commit at this expiration time.
- root.pendingCommitExpirationTime = NoWork;
- return commitRoot.bind(null, root, expirationTime);
- }
-
- flushPassiveEffects();
-
- // If the root or expiration time have changed, throw out the existing stack
- // and prepare a fresh one. Otherwise we'll continue where we left off.
- if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
- prepareFreshStack(root, expirationTime);
- startWorkOnPendingInteraction(root, expirationTime);
- }
-
- // If we have a work-in-progress fiber, it means there's still work to do
- // in this root.
- if (workInProgress !== null) {
- const prevWorkPhase = workPhase;
- workPhase = RenderPhase;
- let prevDispatcher = ReactCurrentDispatcher.current;
- if (prevDispatcher === null) {
- // The React isomorphic package does not include a default dispatcher.
- // Instead the first renderer will lazily attach one, in order to give
- // nicer error messages.
- prevDispatcher = ContextOnlyDispatcher;
- }
- ReactCurrentDispatcher.current = ContextOnlyDispatcher;
- let prevInteractions: Set | null = null;
- if (enableSchedulerTracing) {
- prevInteractions = __interactionsRef.current;
- __interactionsRef.current = root.memoizedInteractions;
- }
-
- startWorkLoopTimer(workInProgress);
-
- // TODO: Fork renderRoot into renderRootSync and renderRootAsync
- if (isSync) {
- if (expirationTime !== Sync) {
- // An async update expired. There may be other expired updates on
- // this root. We should render all the expired work in a
- // single batch.
- const currentTime = requestCurrentTime();
- if (currentTime < expirationTime) {
- // Restart at the current time.
- workPhase = prevWorkPhase;
- resetContextDependencies();
- ReactCurrentDispatcher.current = prevDispatcher;
- if (enableSchedulerTracing) {
- __interactionsRef.current = ((prevInteractions: any): Set<
- Interaction,
- >);
- }
- return renderRoot.bind(null, root, currentTime);
- }
- }
- } else {
- // Since we know we're in a React event, we can clear the current
- // event time. The next update will compute a new event time.
- currentEventTime = NoWork;
- }
-
- do {
- try {
- if (isSync) {
- workLoopSync();
- } else {
- workLoop();
- }
- break;
- } catch (thrownValue) {
- // Reset module-level state that was set during the render phase.
- resetContextDependencies();
- resetHooks();
-
- const sourceFiber = workInProgress;
- if (sourceFiber === null || sourceFiber.return === null) {
- // Expected to be working on a non-root fiber. This is a fatal error
- // because there's no ancestor that can handle it; the root is
- // supposed to capture all errors that weren't caught by an error
- // boundary.
- prepareFreshStack(root, expirationTime);
- workPhase = prevWorkPhase;
- throw thrownValue;
- }
-
- if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
- // Record the time spent rendering before an error was thrown. This
- // avoids inaccurate Profiler durations in the case of a
- // suspended render.
- stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
- }
-
- const returnFiber = sourceFiber.return;
- throwException(
- root,
- returnFiber,
- sourceFiber,
- thrownValue,
- renderExpirationTime,
- );
- workInProgress = completeUnitOfWork(sourceFiber);
- }
- } while (true);
-
- workPhase = prevWorkPhase;
- resetContextDependencies();
- ReactCurrentDispatcher.current = prevDispatcher;
- if (enableSchedulerTracing) {
- __interactionsRef.current = ((prevInteractions: any): Set);
- }
-
- if (workInProgress !== null) {
- // There's still work left over. Return a continuation.
- stopInterruptedWorkLoopTimer();
- if (expirationTime !== Sync) {
- startRequestCallbackTimer();
- }
- return renderRoot.bind(null, root, expirationTime);
- }
- }
-
- // We now have a consistent tree. The next step is either to commit it, or, if
- // something suspended, wait to commit it after a timeout.
- stopFinishedWorkLoopTimer();
-
- const isLocked = resolveLocksOnRoot(root, expirationTime);
- if (isLocked) {
- // This root has a lock that prevents it from committing. Exit. If we begin
- // work on the root again, without any intervening updates, it will finish
- // without doing additional work.
- return null;
- }
-
- // Set this to null to indicate there's no in-progress render.
- workInProgressRoot = null;
-
- switch (workInProgressRootExitStatus) {
- case RootIncomplete: {
- invariant(false, 'Should have a work-in-progress.');
- }
- // Flow knows about invariant, so it compains if I add a break statement,
- // but eslint doesn't know about invariant, so it complains if I do.
- // eslint-disable-next-line no-fallthrough
- case RootErrored: {
- // An error was thrown. First check if there is lower priority work
- // scheduled on this root.
- const lastPendingTime = root.lastPendingTime;
- if (root.lastPendingTime < expirationTime) {
- // There's lower priority work. Before raising the error, try rendering
- // at the lower priority to see if it fixes it. Use a continuation to
- // maintain the existing priority and position in the queue.
- return renderRoot.bind(null, root, lastPendingTime);
- }
- if (!isSync) {
- // If we're rendering asynchronously, it's possible the error was
- // caused by tearing due to a mutation during an event. Try rendering
- // one more time without yiedling to events.
- prepareFreshStack(root, expirationTime);
- scheduleCallback(
- ImmediatePriority,
- renderRoot.bind(null, root, expirationTime),
- );
- return null;
- }
- // If we're already rendering synchronously, commit the root in its
- // errored state.
- return commitRoot.bind(null, root, expirationTime);
- }
- case RootSuspended: {
- if (!isSync) {
- const lastPendingTime = root.lastPendingTime;
- if (root.lastPendingTime < expirationTime) {
- // There's lower priority work. It might be unsuspended. Try rendering
- // at that level.
- return renderRoot.bind(null, root, lastPendingTime);
- }
- // If workInProgressRootMostRecentEventTime is Sync, that means we didn't
- // track any event times. That can happen if we retried but nothing switched
- // from fallback to content. There's no reason to delay doing no work.
- if (workInProgressRootMostRecentEventTime !== Sync) {
- let msUntilTimeout = computeMsUntilTimeout(
- workInProgressRootMostRecentEventTime,
- expirationTime,
- );
- // Don't bother with a very short suspense time.
- if (msUntilTimeout > 10) {
- // The render is suspended, it hasn't timed out, and there's no lower
- // priority work to do. Instead of committing the fallback
- // immediately, wait for more data to arrive.
- root.timeoutHandle = scheduleTimeout(
- commitRoot.bind(null, root, expirationTime),
- msUntilTimeout,
- );
- return null;
- }
- }
- }
- // The work expired. Commit immediately.
- return commitRoot.bind(null, root, expirationTime);
- }
- case RootCompleted: {
- // The work completed. Ready to commit.
- return commitRoot.bind(null, root, expirationTime);
- }
- default: {
- invariant(false, 'Unknown root exit status.');
- }
- }
-}
-
-export function markRenderEventTime(expirationTime: ExpirationTime): void {
- if (expirationTime < workInProgressRootMostRecentEventTime) {
- workInProgressRootMostRecentEventTime = expirationTime;
- }
-}
-
-export function renderDidSuspend(): void {
- if (workInProgressRootExitStatus === RootIncomplete) {
- workInProgressRootExitStatus = RootSuspended;
- }
-}
-
-export function renderDidError() {
- if (
- workInProgressRootExitStatus === RootIncomplete ||
- workInProgressRootExitStatus === RootSuspended
- ) {
- workInProgressRootExitStatus = RootErrored;
- }
-}
-
-function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {
- // We don't know exactly when the update was scheduled, but we can infer an
- // approximate start time from the expiration time.
- const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
- return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION + initialTimeMs;
-}
-
-function workLoopSync() {
- // Already timed out, so perform work without checking if we need to yield.
- while (workInProgress !== null) {
- workInProgress = performUnitOfWork(workInProgress);
- }
-}
-
-function workLoop() {
- // Perform work until Scheduler asks us to yield
- while (workInProgress !== null && !shouldYield()) {
- workInProgress = performUnitOfWork(workInProgress);
- }
-}
-
-function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
- // The current, flushed, state of this fiber is the alternate. Ideally
- // nothing should rely on this, but relying on it here means that we don't
- // need an additional field on the work in progress.
- const current = unitOfWork.alternate;
-
- startWorkTimer(unitOfWork);
- setCurrentDebugFiberInDEV(unitOfWork);
-
- let next;
- if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {
- startProfilerTimer(unitOfWork);
- next = beginWork(current, unitOfWork, renderExpirationTime);
- stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
- } else {
- next = beginWork(current, unitOfWork, renderExpirationTime);
- }
-
- resetCurrentDebugFiberInDEV();
- unitOfWork.memoizedProps = unitOfWork.pendingProps;
- if (next === null) {
- // If this doesn't spawn new work, complete the current work.
- next = completeUnitOfWork(unitOfWork);
- }
-
- ReactCurrentOwner.current = null;
- return next;
-}
-
-function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
- // Attempt to complete the current unit of work, then move to the next
- // sibling. If there are no more siblings, return to the parent fiber.
- workInProgress = unitOfWork;
- do {
- // The current, flushed, state of this fiber is the alternate. Ideally
- // nothing should rely on this, but relying on it here means that we don't
- // need an additional field on the work in progress.
- const current = workInProgress.alternate;
- const returnFiber = workInProgress.return;
-
- // Check if the work completed or if something threw.
- if ((workInProgress.effectTag & Incomplete) === NoEffect) {
- setCurrentDebugFiberInDEV(workInProgress);
- let next;
- if (
- !enableProfilerTimer ||
- (workInProgress.mode & ProfileMode) === NoContext
- ) {
- next = completeWork(current, workInProgress, renderExpirationTime);
- } else {
- startProfilerTimer(workInProgress);
- next = completeWork(current, workInProgress, renderExpirationTime);
- // Update render duration assuming we didn't error.
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
- }
- stopWorkTimer(workInProgress);
- resetCurrentDebugFiberInDEV();
- resetChildExpirationTime(workInProgress);
-
- if (next !== null) {
- // Completing this fiber spawned new work. Work on that next.
- return next;
- }
-
- if (
- returnFiber !== null &&
- // Do not append effects to parents if a sibling failed to complete
- (returnFiber.effectTag & Incomplete) === NoEffect
- ) {
- // Append all the effects of the subtree and this fiber onto the effect
- // list of the parent. The completion order of the children affects the
- // side-effect order.
- if (returnFiber.firstEffect === null) {
- returnFiber.firstEffect = workInProgress.firstEffect;
- }
- if (workInProgress.lastEffect !== null) {
- if (returnFiber.lastEffect !== null) {
- returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
- }
- returnFiber.lastEffect = workInProgress.lastEffect;
- }
-
- // If this fiber had side-effects, we append it AFTER the children's
- // side-effects. We can perform certain side-effects earlier if needed,
- // by doing multiple passes over the effect list. We don't want to
- // schedule our own side-effect on our own list because if end up
- // reusing children we'll schedule this effect onto itself since we're
- // at the end.
- const effectTag = workInProgress.effectTag;
-
- // Skip both NoWork and PerformedWork tags when creating the effect
- // list. PerformedWork effect is read by React DevTools but shouldn't be
- // committed.
- if (effectTag > PerformedWork) {
- if (returnFiber.lastEffect !== null) {
- returnFiber.lastEffect.nextEffect = workInProgress;
- } else {
- returnFiber.firstEffect = workInProgress;
- }
- returnFiber.lastEffect = workInProgress;
- }
- }
- } else {
- // This fiber did not complete because something threw. Pop values off
- // the stack without entering the complete phase. If this is a boundary,
- // capture values if possible.
- const next = unwindWork(workInProgress, renderExpirationTime);
-
- // Because this fiber did not complete, don't reset its expiration time.
-
- if (
- enableProfilerTimer &&
- (workInProgress.mode & ProfileMode) !== NoContext
- ) {
- // Record the render duration for the fiber that errored.
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
-
- // Include the time spent working on failed children before continuing.
- let actualDuration = workInProgress.actualDuration;
- let child = workInProgress.child;
- while (child !== null) {
- actualDuration += child.actualDuration;
- child = child.sibling;
- }
- workInProgress.actualDuration = actualDuration;
- }
-
- if (next !== null) {
- // If completing this work spawned new work, do that next. We'll come
- // back here again.
- // Since we're restarting, remove anything that is not a host effect
- // from the effect tag.
- // TODO: The name stopFailedWorkTimer is misleading because Suspense
- // also captures and restarts.
- stopFailedWorkTimer(workInProgress);
- next.effectTag &= HostEffectMask;
- return next;
- }
- stopWorkTimer(workInProgress);
-
- if (returnFiber !== null) {
- // Mark the parent fiber as incomplete and clear its effect list.
- returnFiber.firstEffect = returnFiber.lastEffect = null;
- returnFiber.effectTag |= Incomplete;
- }
- }
-
- const siblingFiber = workInProgress.sibling;
- if (siblingFiber !== null) {
- // If there is more work to do in this returnFiber, do that next.
- return siblingFiber;
- }
- // Otherwise, return to the parent
- workInProgress = returnFiber;
- } while (workInProgress !== null);
-
- // We've reached the root.
- if (workInProgressRootExitStatus === RootIncomplete) {
- workInProgressRootExitStatus = RootCompleted;
- }
- return null;
-}
-
-function resetChildExpirationTime(completedWork: Fiber) {
- if (
- renderExpirationTime !== Never &&
- completedWork.childExpirationTime === Never
- ) {
- // The children of this component are hidden. Don't bubble their
- // expiration times.
- return;
- }
-
- let newChildExpirationTime = NoWork;
-
- // Bubble up the earliest expiration time.
- if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {
- // In profiling mode, resetChildExpirationTime is also used to reset
- // profiler durations.
- let actualDuration = completedWork.actualDuration;
- let treeBaseDuration = completedWork.selfBaseDuration;
-
- // When a fiber is cloned, its actualDuration is reset to 0. This value will
- // only be updated if work is done on the fiber (i.e. it doesn't bailout).
- // When work is done, it should bubble to the parent's actualDuration. If
- // the fiber has not been cloned though, (meaning no work was done), then
- // this value will reflect the amount of time spent working on a previous
- // render. In that case it should not bubble. We determine whether it was
- // cloned by comparing the child pointer.
- const shouldBubbleActualDurations =
- completedWork.alternate === null ||
- completedWork.child !== completedWork.alternate.child;
-
- let child = completedWork.child;
- while (child !== null) {
- const childUpdateExpirationTime = child.expirationTime;
- const childChildExpirationTime = child.childExpirationTime;
- if (childUpdateExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childUpdateExpirationTime;
- }
- if (childChildExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childChildExpirationTime;
- }
- if (shouldBubbleActualDurations) {
- actualDuration += child.actualDuration;
- }
- treeBaseDuration += child.treeBaseDuration;
- child = child.sibling;
- }
- completedWork.actualDuration = actualDuration;
- completedWork.treeBaseDuration = treeBaseDuration;
- } else {
- let child = completedWork.child;
- while (child !== null) {
- const childUpdateExpirationTime = child.expirationTime;
- const childChildExpirationTime = child.childExpirationTime;
- if (childUpdateExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childUpdateExpirationTime;
- }
- if (childChildExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childChildExpirationTime;
- }
- child = child.sibling;
- }
- }
-
- completedWork.childExpirationTime = newChildExpirationTime;
-}
-
-function commitRoot(root, expirationTime) {
- runWithPriority(
- ImmediatePriority,
- commitRootImpl.bind(null, root, expirationTime),
- );
- // If there are passive effects, schedule a callback to flush them. This goes
- // outside commitRootImpl so that it inherits the priority of the render.
- if (rootWithPendingPassiveEffects !== null) {
- const priorityLevel = getCurrentPriorityLevel();
- scheduleCallback(priorityLevel, () => {
- flushPassiveEffects();
- return null;
- });
- }
- return null;
-}
-
-function commitRootImpl(root, expirationTime) {
- flushPassiveEffects();
- flushRenderPhaseStrictModeWarningsInDEV();
-
- invariant(
- workPhase !== RenderPhase && workPhase !== CommitPhase,
- 'Should not already be working.',
- );
- const finishedWork = root.current.alternate;
- invariant(finishedWork !== null, 'Should have a work-in-progress root.');
-
- // commitRoot never returns a continuation; it always finishes synchronously.
- // So we can clear these now to allow a new callback to be scheduled.
- root.callbackNode = null;
- root.callbackExpirationTime = NoWork;
-
- startCommitTimer();
-
- // Update the first and last pending times on this root. The new first
- // pending time is whatever is left on the root fiber.
- const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
- const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
- const firstPendingTimeBeforeCommit =
- childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
- ? childExpirationTimeBeforeCommit
- : updateExpirationTimeBeforeCommit;
- root.firstPendingTime = firstPendingTimeBeforeCommit;
- if (firstPendingTimeBeforeCommit < root.lastPendingTime) {
- // This usually means we've finished all the work, but it can also happen
- // when something gets downprioritized during render, like a hidden tree.
- root.lastPendingTime = firstPendingTimeBeforeCommit;
- }
-
- if (root === workInProgressRoot) {
- // We can reset these now that they are finished.
- workInProgressRoot = null;
- workInProgress = null;
- renderExpirationTime = NoWork;
- } else {
- // This indicates that the last root we worked on is not the same one that
- // we're committing now. This most commonly happens when a suspended root
- // times out.
- }
-
- // Get the list of effects.
- let firstEffect;
- if (finishedWork.effectTag > PerformedWork) {
- // A fiber's effect list consists only of its children, not itself. So if
- // the root has an effect, we need to add it to the end of the list. The
- // resulting list is the set that would belong to the root's parent, if it
- // had one; that is, all the effects in the tree including the root.
- if (finishedWork.lastEffect !== null) {
- finishedWork.lastEffect.nextEffect = finishedWork;
- firstEffect = finishedWork.firstEffect;
- } else {
- firstEffect = finishedWork;
- }
- } else {
- // There is no effect on the root.
- firstEffect = finishedWork.firstEffect;
- }
-
- if (firstEffect !== null) {
- const prevWorkPhase = workPhase;
- workPhase = CommitPhase;
- let prevInteractions: Set | null = null;
- if (enableSchedulerTracing) {
- prevInteractions = __interactionsRef.current;
- __interactionsRef.current = root.memoizedInteractions;
- }
-
- // Reset this to null before calling lifecycles
- ReactCurrentOwner.current = null;
-
- // The commit phase is broken into several sub-phases. We do a separate pass
- // of the effect list for each phase: all mutation effects come before all
- // layout effects, and so on.
-
- // The first phase a "before mutation" phase. We use this phase to read the
- // state of the host tree right before we mutate it. This is where
- // getSnapshotBeforeUpdate is called.
- startCommitSnapshotEffectsTimer();
- prepareForCommit(root.containerInfo);
- nextEffect = firstEffect;
- do {
- if (__DEV__) {
- invokeGuardedCallback(null, commitBeforeMutationEffects, null);
- if (hasCaughtError()) {
- invariant(nextEffect !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(nextEffect, error);
- nextEffect = nextEffect.nextEffect;
- }
- } else {
- try {
- commitBeforeMutationEffects();
- } catch (error) {
- invariant(nextEffect !== null, 'Should be working on an effect.');
- captureCommitPhaseError(nextEffect, error);
- nextEffect = nextEffect.nextEffect;
- }
- }
- } while (nextEffect !== null);
- stopCommitSnapshotEffectsTimer();
-
- if (enableProfilerTimer) {
- // Mark the current commit time to be shared by all Profilers in this
- // batch. This enables them to be grouped later.
- recordCommitTime();
- }
-
- // The next phase is the mutation phase, where we mutate the host tree.
- startCommitHostEffectsTimer();
- nextEffect = firstEffect;
- do {
- if (__DEV__) {
- invokeGuardedCallback(null, commitMutationEffects, null);
- if (hasCaughtError()) {
- invariant(nextEffect !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(nextEffect, error);
- nextEffect = nextEffect.nextEffect;
- }
- } else {
- try {
- commitMutationEffects();
- } catch (error) {
- invariant(nextEffect !== null, 'Should be working on an effect.');
- captureCommitPhaseError(nextEffect, error);
- nextEffect = nextEffect.nextEffect;
- }
- }
- } while (nextEffect !== null);
- stopCommitHostEffectsTimer();
- resetAfterCommit(root.containerInfo);
-
- // The work-in-progress tree is now the current tree. This must come after
- // the mutation phase, so that the previous tree is still current during
- // componentWillUnmount, but before the layout phase, so that the finished
- // work is current during componentDidMount/Update.
- root.current = finishedWork;
-
- // The next phase is the layout phase, where we call effects that read
- // the host tree after it's been mutated. The idiomatic use case for this is
- // layout, but class component lifecycles also fire here for legacy reasons.
- startCommitLifeCyclesTimer();
- nextEffect = firstEffect;
- do {
- if (__DEV__) {
- invokeGuardedCallback(
- null,
- commitLayoutEffects,
- null,
- root,
- expirationTime,
- );
- if (hasCaughtError()) {
- invariant(nextEffect !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(nextEffect, error);
- nextEffect = nextEffect.nextEffect;
- }
- } else {
- try {
- commitLayoutEffects(root, expirationTime);
- } catch (error) {
- invariant(nextEffect !== null, 'Should be working on an effect.');
- captureCommitPhaseError(nextEffect, error);
- nextEffect = nextEffect.nextEffect;
- }
- }
- } while (nextEffect !== null);
- stopCommitLifeCyclesTimer();
-
- nextEffect = null;
-
- if (enableSchedulerTracing) {
- __interactionsRef.current = ((prevInteractions: any): Set);
- }
- workPhase = prevWorkPhase;
- } else {
- // No effects.
- root.current = finishedWork;
- // Measure these anyway so the flamegraph explicitly shows that there were
- // no effects.
- // TODO: Maybe there's a better way to report this.
- startCommitSnapshotEffectsTimer();
- stopCommitSnapshotEffectsTimer();
- if (enableProfilerTimer) {
- recordCommitTime();
- }
- startCommitHostEffectsTimer();
- stopCommitHostEffectsTimer();
- startCommitLifeCyclesTimer();
- stopCommitLifeCyclesTimer();
- }
-
- stopCommitTimer();
-
- if (rootDoesHavePassiveEffects) {
- // This commit has passive effects. Stash a reference to them. But don't
- // schedule a callback until after flushing layout work.
- rootDoesHavePassiveEffects = false;
- rootWithPendingPassiveEffects = root;
- pendingPassiveEffectsExpirationTime = expirationTime;
- } else {
- if (enableSchedulerTracing) {
- // If there are no passive effects, then we can complete the pending
- // interactions. Otherwise, we'll wait until after the passive effects
- // are flushed.
- finishPendingInteractions(root, expirationTime);
- }
- }
-
- // Check if there's remaining work on this root
- const remainingExpirationTime = root.firstPendingTime;
- if (remainingExpirationTime !== NoWork) {
- const currentTime = requestCurrentTime();
- const priorityLevel = inferPriorityFromExpirationTime(
- currentTime,
- remainingExpirationTime,
- );
- scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);
- } else {
- // If there's no remaining work, we can clear the set of already failed
- // error boundaries.
- legacyErrorBoundariesThatAlreadyFailed = null;
- }
-
- onCommitRoot(finishedWork.stateNode);
-
- if (remainingExpirationTime === Sync) {
- // Count the number of times the root synchronously re-renders without
- // finishing. If there are too many, it indicates an infinite update loop.
- if (root === rootWithNestedUpdates) {
- nestedUpdateCount++;
- } else {
- nestedUpdateCount = 0;
- rootWithNestedUpdates = root;
- }
- } else {
- nestedUpdateCount = 0;
- }
-
- if (hasUncaughtError) {
- hasUncaughtError = false;
- const error = firstUncaughtError;
- firstUncaughtError = null;
- throw error;
- }
-
- if (workPhase === LegacyUnbatchedPhase) {
- // This is a legacy edge case. We just committed the initial mount of
- // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
- // synchronously, but layout updates should be deferred until the end
- // of the batch.
- return null;
- }
-
- // If layout work was scheduled, flush it now.
- flushImmediateQueue();
- return null;
-}
-
-function commitBeforeMutationEffects() {
- while (nextEffect !== null) {
- if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
- setCurrentDebugFiberInDEV(nextEffect);
- recordEffect();
-
- const current = nextEffect.alternate;
- commitBeforeMutationEffectOnFiber(current, nextEffect);
-
- resetCurrentDebugFiberInDEV();
- }
- nextEffect = nextEffect.nextEffect;
- }
-}
-
-function commitMutationEffects() {
- // TODO: Should probably move the bulk of this function to commitWork.
- while (nextEffect !== null) {
- setCurrentDebugFiberInDEV(nextEffect);
-
- const effectTag = nextEffect.effectTag;
-
- if (effectTag & ContentReset) {
- commitResetTextContent(nextEffect);
- }
-
- if (effectTag & Ref) {
- const current = nextEffect.alternate;
- if (current !== null) {
- commitDetachRef(current);
- }
- }
-
- // The following switch statement is only concerned about placement,
- // updates, and deletions. To avoid needing to add a case for every possible
- // bitmap value, we remove the secondary effects from the effect tag and
- // switch on that value.
- let primaryEffectTag = effectTag & (Placement | Update | Deletion);
- switch (primaryEffectTag) {
- case Placement: {
- commitPlacement(nextEffect);
- // Clear the "placement" from effect tag so that we know that this is
- // inserted, before any life-cycles like componentDidMount gets called.
- // TODO: findDOMNode doesn't rely on this any more but isMounted does
- // and isMounted is deprecated anyway so we should be able to kill this.
- nextEffect.effectTag &= ~Placement;
- break;
- }
- case PlacementAndUpdate: {
- // Placement
- commitPlacement(nextEffect);
- // Clear the "placement" from effect tag so that we know that this is
- // inserted, before any life-cycles like componentDidMount gets called.
- nextEffect.effectTag &= ~Placement;
-
- // Update
- const current = nextEffect.alternate;
- commitWork(current, nextEffect);
- break;
- }
- case Update: {
- const current = nextEffect.alternate;
- commitWork(current, nextEffect);
- break;
- }
- case Deletion: {
- commitDeletion(nextEffect);
- break;
- }
- }
-
- // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
- recordEffect();
-
- resetCurrentDebugFiberInDEV();
- nextEffect = nextEffect.nextEffect;
- }
-}
-
-function commitLayoutEffects(
- root: FiberRoot,
- committedExpirationTime: ExpirationTime,
-) {
- // TODO: Should probably move the bulk of this function to commitWork.
- while (nextEffect !== null) {
- setCurrentDebugFiberInDEV(nextEffect);
-
- const effectTag = nextEffect.effectTag;
-
- if (effectTag & (Update | Callback)) {
- recordEffect();
- const current = nextEffect.alternate;
- commitLayoutEffectOnFiber(
- root,
- current,
- nextEffect,
- committedExpirationTime,
- );
- }
-
- if (effectTag & Ref) {
- recordEffect();
- commitAttachRef(nextEffect);
- }
-
- if (effectTag & Passive) {
- rootDoesHavePassiveEffects = true;
- }
-
- resetCurrentDebugFiberInDEV();
- nextEffect = nextEffect.nextEffect;
- }
-}
-
-export function flushPassiveEffects() {
- if (rootWithPendingPassiveEffects === null) {
- return false;
- }
- const root = rootWithPendingPassiveEffects;
- const expirationTime = pendingPassiveEffectsExpirationTime;
- rootWithPendingPassiveEffects = null;
- pendingPassiveEffectsExpirationTime = NoWork;
-
- let prevInteractions: Set | null = null;
- if (enableSchedulerTracing) {
- prevInteractions = __interactionsRef.current;
- __interactionsRef.current = root.memoizedInteractions;
- }
-
- invariant(
- workPhase !== RenderPhase && workPhase !== CommitPhase,
- 'Cannot flush passive effects while already rendering.',
- );
- const prevWorkPhase = workPhase;
- workPhase = CommitPhase;
-
- // Note: This currently assumes there are no passive effects on the root
- // fiber, because the root is not part of its own effect list. This could
- // change in the future.
- let effect = root.current.firstEffect;
- while (effect !== null) {
- if (__DEV__) {
- setCurrentDebugFiberInDEV(effect);
- invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
- if (hasCaughtError()) {
- invariant(effect !== null, 'Should be working on an effect.');
- const error = clearCaughtError();
- captureCommitPhaseError(effect, error);
- }
- resetCurrentDebugFiberInDEV();
- } else {
- try {
- commitPassiveHookEffects(effect);
- } catch (error) {
- invariant(effect !== null, 'Should be working on an effect.');
- captureCommitPhaseError(effect, error);
- }
- }
- effect = effect.nextEffect;
- }
-
- if (enableSchedulerTracing) {
- __interactionsRef.current = ((prevInteractions: any): Set);
- finishPendingInteractions(root, expirationTime);
- }
-
- workPhase = prevWorkPhase;
- flushImmediateQueue();
-
- // If additional passive effects were scheduled, increment a counter. If this
- // exceeds the limit, we'll fire a warning.
- nestedPassiveUpdateCount =
- rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
-
- return true;
-}
-
-export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
- return (
- legacyErrorBoundariesThatAlreadyFailed !== null &&
- legacyErrorBoundariesThatAlreadyFailed.has(instance)
- );
-}
-
-export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
- if (legacyErrorBoundariesThatAlreadyFailed === null) {
- legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
- } else {
- legacyErrorBoundariesThatAlreadyFailed.add(instance);
- }
-}
-
-function prepareToThrowUncaughtError(error: mixed) {
- if (!hasUncaughtError) {
- hasUncaughtError = true;
- firstUncaughtError = error;
- }
-}
-export const onUncaughtError = prepareToThrowUncaughtError;
-
-function captureCommitPhaseErrorOnRoot(
- rootFiber: Fiber,
- sourceFiber: Fiber,
- error: mixed,
-) {
- const errorInfo = createCapturedValue(error, sourceFiber);
- const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
- enqueueUpdate(rootFiber, update);
- const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
- if (root !== null) {
- scheduleCallbackForRoot(root, ImmediatePriority, Sync);
- }
-}
-
-export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
- if (sourceFiber.tag === HostRoot) {
- // Error was thrown at the root. There is no parent, so the root
- // itself should capture it.
- captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
- return;
- }
-
- let fiber = sourceFiber.return;
- while (fiber !== null) {
- if (fiber.tag === HostRoot) {
- captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
- return;
- } else if (fiber.tag === ClassComponent) {
- const ctor = fiber.type;
- const instance = fiber.stateNode;
- if (
- typeof ctor.getDerivedStateFromError === 'function' ||
- (typeof instance.componentDidCatch === 'function' &&
- !isAlreadyFailedLegacyErrorBoundary(instance))
- ) {
- const errorInfo = createCapturedValue(error, sourceFiber);
- const update = createClassErrorUpdate(
- fiber,
- errorInfo,
- // TODO: This is always sync
- Sync,
- );
- enqueueUpdate(fiber, update);
- const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
- if (root !== null) {
- scheduleCallbackForRoot(root, ImmediatePriority, Sync);
- }
- return;
- }
- }
- fiber = fiber.return;
- }
-}
-
-export function pingSuspendedRoot(
- root: FiberRoot,
- thenable: Thenable,
- suspendedTime: ExpirationTime,
-) {
- const pingCache = root.pingCache;
- if (pingCache !== null) {
- // The thenable resolved, so we no longer need to memoize, because it will
- // never be thrown again.
- pingCache.delete(thenable);
- }
-
- if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
- // Received a ping at the same priority level at which we're currently
- // rendering. Restart from the root. Don't need to schedule a ping because
- // we're already working on this tree.
- prepareFreshStack(root, renderExpirationTime);
- return;
- }
-
- const lastPendingTime = root.lastPendingTime;
- if (lastPendingTime < suspendedTime) {
- // The root is no longer suspended at this time.
- return;
- }
-
- const pingTime = root.pingTime;
- if (pingTime !== NoWork && pingTime < suspendedTime) {
- // There's already a lower priority ping scheduled.
- return;
- }
-
- // Mark the time at which this ping was scheduled.
- root.pingTime = suspendedTime;
-
- const currentTime = requestCurrentTime();
- const priorityLevel = inferPriorityFromExpirationTime(
- currentTime,
- suspendedTime,
- );
- scheduleCallbackForRoot(root, priorityLevel, suspendedTime);
-}
-
-export function retryTimedOutBoundary(boundaryFiber: Fiber) {
- // The boundary fiber (a Suspense component) previously timed out and was
- // rendered in its fallback state. One of the promises that suspended it has
- // resolved, which means at least part of the tree was likely unblocked. Try
- // rendering again, at a new expiration time.
- const currentTime = requestCurrentTime();
- const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
- // TODO: Special case idle priority?
- const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);
- const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
- if (root !== null) {
- scheduleCallbackForRoot(root, priorityLevel, retryTime);
- }
-}
-
-export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
- let retryCache: WeakSet | Set | null;
- if (enableSuspenseServerRenderer) {
- switch (boundaryFiber.tag) {
- case SuspenseComponent:
- retryCache = boundaryFiber.stateNode;
- break;
- case DehydratedSuspenseComponent:
- retryCache = boundaryFiber.memoizedState;
- break;
- default:
- invariant(
- false,
- 'Pinged unknown suspense boundary type. ' +
- 'This is probably a bug in React.',
- );
- }
- } else {
- retryCache = boundaryFiber.stateNode;
- }
-
- if (retryCache !== null) {
- // The thenable resolved, so we no longer need to memoize, because it will
- // never be thrown again.
- retryCache.delete(thenable);
- }
-
- retryTimedOutBoundary(boundaryFiber);
-}
-
-// Computes the next Just Noticeable Difference (JND) boundary.
-// The theory is that a person can't tell the difference between small differences in time.
-// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
-// difference in the experience. However, waiting for longer might mean that we can avoid
-// showing an intermediate loading state. The longer we have already waited, the harder it
-// is to tell small differences in time. Therefore, the longer we've already waited,
-// the longer we can wait additionally. At some point we have to give up though.
-// We pick a train model where the next boundary commits at a consistent schedule.
-// These particular numbers are vague estimates. We expect to adjust them based on research.
-function jnd(timeElapsed: number) {
- return timeElapsed < 120
- ? 120
- : timeElapsed < 480
- ? 480
- : timeElapsed < 1080
- ? 1080
- : timeElapsed < 1920
- ? 1920
- : timeElapsed < 3000
- ? 3000
- : timeElapsed < 4320
- ? 4320
- : ceil(timeElapsed / 1960) * 1960;
-}
-
-function computeMsUntilTimeout(
- mostRecentEventTime: ExpirationTime,
- committedExpirationTime: ExpirationTime,
-) {
- if (disableYielding) {
- // Timeout immediately when yielding is disabled.
- return 0;
- }
-
- const eventTimeMs: number = inferTimeFromExpirationTime(mostRecentEventTime);
- const currentTimeMs: number = now();
- const timeElapsed = currentTimeMs - eventTimeMs;
-
- let msUntilTimeout = jnd(timeElapsed) - timeElapsed;
-
- // Compute the time until this render pass would expire.
- const timeUntilExpirationMs =
- expirationTimeToMs(committedExpirationTime) + initialTimeMs - currentTimeMs;
-
- // Clamp the timeout to the expiration time.
- // TODO: Once the event time is exact instead of inferred from expiration time
- // we don't need this.
- if (timeUntilExpirationMs < msUntilTimeout) {
- msUntilTimeout = timeUntilExpirationMs;
- }
-
- // This is the value that is passed to `setTimeout`.
- return msUntilTimeout;
-}
-
-function checkForNestedUpdates() {
- if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
- nestedUpdateCount = 0;
- rootWithNestedUpdates = null;
- invariant(
- false,
- 'Maximum update depth exceeded. This can happen when a component ' +
- 'repeatedly calls setState inside componentWillUpdate or ' +
- 'componentDidUpdate. React limits the number of nested updates to ' +
- 'prevent infinite loops.',
- );
- }
-
- if (__DEV__) {
- if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
- nestedPassiveUpdateCount = 0;
- warning(
- false,
- 'Maximum update depth exceeded. This can happen when a component ' +
- "calls setState inside useEffect, but useEffect either doesn't " +
- 'have a dependency array, or one of the dependencies changes on ' +
- 'every render.',
- );
- }
- }
-}
-
-function flushRenderPhaseStrictModeWarningsInDEV() {
- if (__DEV__) {
- ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
- ReactStrictModeWarnings.flushLegacyContextWarning();
-
- if (warnAboutDeprecatedLifecycles) {
- ReactStrictModeWarnings.flushPendingDeprecationWarnings();
- }
- }
-}
-
-function stopFinishedWorkLoopTimer() {
- const didCompleteRoot = true;
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
- interruptedBy = null;
-}
-
-function stopInterruptedWorkLoopTimer() {
- // TODO: Track which fiber caused the interruption.
- const didCompleteRoot = false;
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
- interruptedBy = null;
-}
-
-function checkForInterruption(
- fiberThatReceivedUpdate: Fiber,
- updateExpirationTime: ExpirationTime,
-) {
- if (
- enableUserTimingAPI &&
- workInProgressRoot !== null &&
- updateExpirationTime > renderExpirationTime
- ) {
- interruptedBy = fiberThatReceivedUpdate;
- }
-}
-
-let didWarnStateUpdateForUnmountedComponent: Set | null = null;
-function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
- if (__DEV__) {
- const tag = fiber.tag;
- if (
- tag !== HostRoot &&
- tag !== ClassComponent &&
- tag !== FunctionComponent &&
- tag !== ForwardRef &&
- tag !== MemoComponent &&
- tag !== SimpleMemoComponent
- ) {
- // Only warn for user-defined components, not internal ones like Suspense.
- return;
- }
- // We show the whole stack but dedupe on the top component's name because
- // the problematic code almost always lies inside that component.
- const componentName = getComponentName(fiber.type) || 'ReactComponent';
- if (didWarnStateUpdateForUnmountedComponent !== null) {
- if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
- return;
- }
- didWarnStateUpdateForUnmountedComponent.add(componentName);
- } else {
- didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
- }
- warningWithoutStack(
- false,
- "Can't perform a React state update on an unmounted component. This " +
- 'is a no-op, but it indicates a memory leak in your application. To ' +
- 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
- tag === ClassComponent
- ? 'the componentWillUnmount method'
- : 'a useEffect cleanup function',
- getStackByFiberInDevAndProd(fiber),
- );
- }
-}
-
-let beginWork;
-if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- let dummyFiber = null;
- beginWork = (current, unitOfWork, expirationTime) => {
- // If a component throws an error, we replay it again in a synchronously
- // dispatched event, so that the debugger will treat it as an uncaught
- // error See ReactErrorUtils for more information.
-
- // Before entering the begin phase, copy the work-in-progress onto a dummy
- // fiber. If beginWork throws, we'll use this to reset the state.
- const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
- dummyFiber,
- unitOfWork,
- );
- try {
- return originalBeginWork(current, unitOfWork, expirationTime);
- } catch (originalError) {
- if (
- originalError !== null &&
- typeof originalError === 'object' &&
- typeof originalError.then === 'function'
- ) {
- // Don't replay promises. Treat everything else like an error.
- throw originalError;
- }
-
- // Keep this code in sync with renderRoot; any changes here must have
- // corresponding changes there.
- resetContextDependencies();
- resetHooks();
-
- // Unwind the failed stack frame
- unwindInterruptedWork(unitOfWork);
-
- // Restore the original properties of the fiber.
- assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
-
- if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
- // Reset the profiler timer.
- startProfilerTimer(unitOfWork);
- }
-
- // Run beginWork again.
- invokeGuardedCallback(
- null,
- originalBeginWork,
- null,
- current,
- unitOfWork,
- expirationTime,
- );
-
- if (hasCaughtError()) {
- const replayError = clearCaughtError();
- // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
- // Rethrow this error instead of the original one.
- throw replayError;
- } else {
- // This branch is reachable if the render phase is impure.
- throw originalError;
- }
- }
- };
-} else {
- beginWork = originalBeginWork;
-}
-
-let didWarnAboutUpdateInRender = false;
-let didWarnAboutUpdateInGetChildContext = false;
-function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
- if (__DEV__) {
- if (fiber.tag === ClassComponent) {
- switch (ReactCurrentDebugFiberPhaseInDEV) {
- case 'getChildContext':
- if (didWarnAboutUpdateInGetChildContext) {
- return;
- }
- warningWithoutStack(
- false,
- 'setState(...): Cannot call setState() inside getChildContext()',
- );
- didWarnAboutUpdateInGetChildContext = true;
- break;
- case 'render':
- if (didWarnAboutUpdateInRender) {
- return;
- }
- warningWithoutStack(
- false,
- 'Cannot update during an existing state transition (such as ' +
- 'within `render`). Render methods should be a pure function of ' +
- 'props and state.',
- );
- didWarnAboutUpdateInRender = true;
- break;
- }
- }
- }
-}
-
-function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
- if (__DEV__) {
- if (
- workPhase === NotWorking &&
- ReactShouldWarnActingUpdates.current === false
- ) {
- warningWithoutStack(
- false,
- 'An update to %s inside a test was not wrapped in act(...).\n\n' +
- 'When testing, code that causes React state updates should be ' +
- 'wrapped into act(...):\n\n' +
- 'act(() => {\n' +
- ' /* fire events that update state */\n' +
- '});\n' +
- '/* assert on the output */\n\n' +
- "This ensures that you're testing the behavior the user would see " +
- 'in the browser.' +
- ' Learn more at https://fb.me/react-wrap-tests-with-act' +
- '%s',
- getComponentName(fiber.type),
- getStackByFiberInDevAndProd(fiber),
- );
- }
- }
-}
-
-export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
-
-function computeThreadID(root, expirationTime) {
- // Interaction threads are unique per root and expiration time.
- return expirationTime * 1000 + root.interactionThreadID;
-}
-
-function schedulePendingInteraction(root, expirationTime) {
- // This is called when work is scheduled on a root. It sets up a pending
- // interaction, which is completed once the work commits.
- if (!enableSchedulerTracing) {
- return;
- }
-
- const interactions = __interactionsRef.current;
- if (interactions.size > 0) {
- const pendingInteractionMap = root.pendingInteractionMap;
- const pendingInteractions = pendingInteractionMap.get(expirationTime);
- if (pendingInteractions != null) {
- interactions.forEach(interaction => {
- if (!pendingInteractions.has(interaction)) {
- // Update the pending async work count for previously unscheduled interaction.
- interaction.__count++;
- }
-
- pendingInteractions.add(interaction);
- });
- } else {
- pendingInteractionMap.set(expirationTime, new Set(interactions));
-
- // Update the pending async work count for the current interactions.
- interactions.forEach(interaction => {
- interaction.__count++;
- });
- }
-
- const subscriber = __subscriberRef.current;
- if (subscriber !== null) {
- const threadID = computeThreadID(root, expirationTime);
- subscriber.onWorkScheduled(interactions, threadID);
- }
- }
-}
-
-function startWorkOnPendingInteraction(root, expirationTime) {
- // This is called when new work is started on a root.
- if (!enableSchedulerTracing) {
- return;
- }
-
- // Determine which interactions this batch of work currently includes, So that
- // we can accurately attribute time spent working on it, And so that cascading
- // work triggered during the render phase will be associated with it.
- const interactions: Set = new Set();
- root.pendingInteractionMap.forEach(
- (scheduledInteractions, scheduledExpirationTime) => {
- if (scheduledExpirationTime >= expirationTime) {
- scheduledInteractions.forEach(interaction =>
- interactions.add(interaction),
- );
- }
- },
- );
-
- // Store the current set of interactions on the FiberRoot for a few reasons:
- // We can re-use it in hot functions like renderRoot() without having to
- // recalculate it. We will also use it in commitWork() to pass to any Profiler
- // onRender() hooks. This also provides DevTools with a way to access it when
- // the onCommitRoot() hook is called.
- root.memoizedInteractions = interactions;
-
- if (interactions.size > 0) {
- const subscriber = __subscriberRef.current;
- if (subscriber !== null) {
- const threadID = computeThreadID(root, expirationTime);
- try {
- subscriber.onWorkStarted(interactions, threadID);
- } catch (error) {
- // If the subscriber throws, rethrow it in a separate task
- scheduleCallback(ImmediatePriority, () => {
- throw error;
- });
- }
- }
- }
-}
-
-function finishPendingInteractions(root, committedExpirationTime) {
- if (!enableSchedulerTracing) {
- return;
- }
-
- const earliestRemainingTimeAfterCommit = root.firstPendingTime;
-
- let subscriber;
-
- try {
- subscriber = __subscriberRef.current;
- if (subscriber !== null && root.memoizedInteractions.size > 0) {
- const threadID = computeThreadID(root, committedExpirationTime);
- subscriber.onWorkStopped(root.memoizedInteractions, threadID);
- }
- } catch (error) {
- // If the subscriber throws, rethrow it in a separate task
- scheduleCallback(ImmediatePriority, () => {
- throw error;
- });
- } finally {
- // Clear completed interactions from the pending Map.
- // Unless the render was suspended or cascading work was scheduled,
- // In which case– leave pending interactions until the subsequent render.
- const pendingInteractionMap = root.pendingInteractionMap;
- pendingInteractionMap.forEach(
- (scheduledInteractions, scheduledExpirationTime) => {
- // Only decrement the pending interaction count if we're done.
- // If there's still work at the current priority,
- // That indicates that we are waiting for suspense data.
- if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
- pendingInteractionMap.delete(scheduledExpirationTime);
-
- scheduledInteractions.forEach(interaction => {
- interaction.__count--;
-
- if (subscriber !== null && interaction.__count === 0) {
- try {
- subscriber.onInteractionScheduledWorkCompleted(interaction);
- } catch (error) {
- // If the subscriber throws, rethrow it in a separate task
- scheduleCallback(ImmediatePriority, () => {
- throw error;
- });
- }
- }
- });
- }
- },
- );
- }
-}
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.old.js b/packages/react-reconciler/src/ReactFiberScheduler.old.js
deleted file mode 100644
index c4184fbb9465d..0000000000000
--- a/packages/react-reconciler/src/ReactFiberScheduler.old.js
+++ /dev/null
@@ -1,2723 +0,0 @@
-/**
- * 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
- */
-
-import type {Fiber} from './ReactFiber';
-import type {Batch, FiberRoot} from './ReactFiberRoot';
-import type {ExpirationTime} from './ReactFiberExpirationTime';
-import type {Interaction} from 'scheduler/src/Tracing';
-
-// Intentionally not named imports because Rollup would use dynamic dispatch for
-// CommonJS interop named imports.
-import * as Scheduler from 'scheduler';
-import {
- __interactionsRef,
- __subscriberRef,
- unstable_wrap as Scheduler_tracing_wrap,
-} from 'scheduler/tracing';
-import {
- invokeGuardedCallback,
- hasCaughtError,
- clearCaughtError,
-} from 'shared/ReactErrorUtils';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-import ReactStrictModeWarnings from './ReactStrictModeWarnings';
-import {
- NoEffect,
- PerformedWork,
- Placement,
- Update,
- Snapshot,
- PlacementAndUpdate,
- Deletion,
- ContentReset,
- Callback,
- DidCapture,
- Ref,
- Incomplete,
- HostEffectMask,
- Passive,
-} from 'shared/ReactSideEffectTags';
-import {
- ClassComponent,
- HostComponent,
- ContextProvider,
- ForwardRef,
- FunctionComponent,
- HostPortal,
- HostRoot,
- MemoComponent,
- SimpleMemoComponent,
- SuspenseComponent,
- DehydratedSuspenseComponent,
-} from 'shared/ReactWorkTags';
-import {
- enableSchedulerTracing,
- enableProfilerTimer,
- enableUserTimingAPI,
- replayFailedUnitOfWorkWithInvokeGuardedCallback,
- warnAboutDeprecatedLifecycles,
- enableSuspenseServerRenderer,
- disableYielding,
-} from 'shared/ReactFeatureFlags';
-import getComponentName from 'shared/getComponentName';
-import invariant from 'shared/invariant';
-import warning from 'shared/warning';
-import warningWithoutStack from 'shared/warningWithoutStack';
-
-import ReactFiberInstrumentation from './ReactFiberInstrumentation';
-import {
- getStackByFiberInDevAndProd,
- phase as ReactCurrentFiberPhase,
- resetCurrentFiber,
- setCurrentFiber,
-} from './ReactCurrentFiber';
-import {
- prepareForCommit,
- resetAfterCommit,
- scheduleTimeout,
- cancelTimeout,
- noTimeout,
-} from './ReactFiberHostConfig';
-import {
- markPendingPriorityLevel,
- markCommittedPriorityLevels,
- markSuspendedPriorityLevel,
- markPingedPriorityLevel,
- hasLowerPriorityWork,
- isPriorityLevelSuspended,
- didExpireAtExpirationTime,
-} from './ReactFiberPendingPriority';
-import {
- recordEffect,
- recordScheduleUpdate,
- startRequestCallbackTimer,
- stopRequestCallbackTimer,
- startWorkTimer,
- stopWorkTimer,
- stopFailedWorkTimer,
- startWorkLoopTimer,
- stopWorkLoopTimer,
- startCommitTimer,
- stopCommitTimer,
- startCommitSnapshotEffectsTimer,
- stopCommitSnapshotEffectsTimer,
- startCommitHostEffectsTimer,
- stopCommitHostEffectsTimer,
- startCommitLifeCyclesTimer,
- stopCommitLifeCyclesTimer,
-} from './ReactDebugFiberPerf';
-import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
-import {onCommitRoot} from './ReactFiberDevToolsHook';
-import {
- NoWork,
- Sync,
- Never,
- msToExpirationTime,
- expirationTimeToMs,
- computeAsyncExpiration,
- computeInteractiveExpiration,
- LOW_PRIORITY_EXPIRATION,
-} from './ReactFiberExpirationTime';
-import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
-import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
-import {createCapturedValue} from './ReactCapturedValue';
-import {
- isContextProvider as isLegacyContextProvider,
- popTopLevelContextObject as popTopLevelLegacyContextObject,
- popContext as popLegacyContext,
-} from './ReactFiberContext';
-import {popProvider, resetContextDependences} from './ReactFiberNewContext';
-import {resetHooks} from './ReactFiberHooks';
-import {popHostContext, popHostContainer} from './ReactFiberHostContext';
-import {
- recordCommitTime,
- startProfilerTimer,
- stopProfilerTimerIfRunningAndRecordDelta,
-} from './ReactProfilerTimer';
-import {
- checkThatStackIsEmpty,
- resetStackAfterFatalErrorInDev,
-} from './ReactFiberStack';
-import {beginWork} from './ReactFiberBeginWork';
-import {completeWork} from './ReactFiberCompleteWork';
-import {
- throwException,
- unwindWork,
- unwindInterruptedWork,
- createRootErrorUpdate,
- createClassErrorUpdate,
-} from './ReactFiberUnwindWork';
-import {
- commitBeforeMutationLifeCycles,
- commitResetTextContent,
- commitPlacement,
- commitDeletion,
- commitWork,
- commitLifeCycles,
- commitAttachRef,
- commitDetachRef,
- commitPassiveHookEffects,
-} from './ReactFiberCommitWork';
-import {ContextOnlyDispatcher} from './ReactFiberHooks';
-
-// Intentionally not named imports because Rollup would use dynamic dispatch for
-// CommonJS interop named imports.
-const {
- unstable_scheduleCallback: scheduleCallback,
- unstable_cancelCallback: cancelCallback,
- unstable_shouldYield: shouldYield,
- unstable_now: now,
- unstable_getCurrentPriorityLevel: getCurrentPriorityLevel,
- unstable_NormalPriority: NormalPriority,
-} = Scheduler;
-
-export type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
-};
-
-const {
- ReactCurrentDispatcher,
- ReactCurrentOwner,
- ReactShouldWarnActingUpdates,
-} = ReactSharedInternals;
-
-let didWarnAboutStateTransition;
-let didWarnSetStateChildContext;
-let warnAboutUpdateOnUnmounted;
-let warnAboutInvalidUpdates;
-
-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',
- );
-}
-
-if (__DEV__) {
- didWarnAboutStateTransition = false;
- didWarnSetStateChildContext = false;
- const didWarnStateUpdateForUnmountedComponent = {};
-
- warnAboutUpdateOnUnmounted = function(fiber: Fiber, isClass: boolean) {
- // We show the whole stack but dedupe on the top component's name because
- // the problematic code almost always lies inside that component.
- const componentName = getComponentName(fiber.type) || 'ReactComponent';
- if (didWarnStateUpdateForUnmountedComponent[componentName]) {
- return;
- }
- warningWithoutStack(
- false,
- "Can't perform a React state update on an unmounted component. This " +
- 'is a no-op, but it indicates a memory leak in your application. To ' +
- 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
- isClass
- ? 'the componentWillUnmount method'
- : 'a useEffect cleanup function',
- getStackByFiberInDevAndProd(fiber),
- );
- didWarnStateUpdateForUnmountedComponent[componentName] = true;
- };
-
- warnAboutInvalidUpdates = function(instance: React$Component) {
- switch (ReactCurrentFiberPhase) {
- case 'getChildContext':
- if (didWarnSetStateChildContext) {
- return;
- }
- warningWithoutStack(
- false,
- 'setState(...): Cannot call setState() inside getChildContext()',
- );
- didWarnSetStateChildContext = true;
- break;
- case 'render':
- if (didWarnAboutStateTransition) {
- return;
- }
- warningWithoutStack(
- false,
- 'Cannot update during an existing state transition (such as within ' +
- '`render`). Render methods should be a pure function of props and state.',
- );
- didWarnAboutStateTransition = true;
- break;
- }
- };
-}
-
-// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
-let lastUniqueAsyncExpiration: number = Sync - 1;
-
-// Represents the expiration time that incoming updates should use. (If this
-// is NoWork, use the default strategy: async updates in async mode, sync
-// updates in sync mode.)
-let expirationContext: ExpirationTime = NoWork;
-
-let isWorking: boolean = false;
-
-// The next work in progress fiber that we're currently working on.
-let nextUnitOfWork: Fiber | null = null;
-let nextRoot: FiberRoot | null = null;
-// The time at which we're currently rendering work.
-let nextRenderExpirationTime: ExpirationTime = NoWork;
-let mostRecentEventTime: ExpirationTime = Sync;
-let nextRenderDidSuspend: boolean = false;
-let nextRenderDidError: boolean = false;
-
-// The next fiber with an effect that we're currently committing.
-let nextEffect: Fiber | null = null;
-
-let isCommitting: boolean = false;
-let rootWithPendingPassiveEffects: FiberRoot | null = null;
-let passiveEffectCallbackHandle: * = null;
-let passiveEffectCallback: * = null;
-
-let legacyErrorBoundariesThatAlreadyFailed: Set | null = null;
-
-// Used for performance tracking.
-let interruptedBy: Fiber | null = null;
-
-let stashedWorkInProgressProperties;
-let replayUnitOfWork;
-let mayReplayFailedUnitOfWork;
-let isReplayingFailedUnitOfWork;
-let originalReplayError;
-let rethrowOriginalError;
-if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- stashedWorkInProgressProperties = null;
- mayReplayFailedUnitOfWork = true;
- isReplayingFailedUnitOfWork = false;
- originalReplayError = null;
- replayUnitOfWork = (
- failedUnitOfWork: Fiber,
- thrownValue: mixed,
- isYieldy: boolean,
- ) => {
- if (
- thrownValue !== null &&
- typeof thrownValue === 'object' &&
- typeof thrownValue.then === 'function'
- ) {
- // Don't replay promises. Treat everything else like an error.
- // TODO: Need to figure out a different strategy if/when we add
- // support for catching other types.
- return;
- }
-
- // Restore the original state of the work-in-progress
- if (stashedWorkInProgressProperties === null) {
- // This should never happen. Don't throw because this code is DEV-only.
- warningWithoutStack(
- false,
- 'Could not replay rendering after an error. This is likely a bug in React. ' +
- 'Please file an issue.',
- );
- return;
- }
- assignFiberPropertiesInDEV(
- failedUnitOfWork,
- stashedWorkInProgressProperties,
- );
-
- switch (failedUnitOfWork.tag) {
- case HostRoot:
- popHostContainer(failedUnitOfWork);
- popTopLevelLegacyContextObject(failedUnitOfWork);
- break;
- case HostComponent:
- popHostContext(failedUnitOfWork);
- break;
- case ClassComponent: {
- const Component = failedUnitOfWork.type;
- if (isLegacyContextProvider(Component)) {
- popLegacyContext(failedUnitOfWork);
- }
- break;
- }
- case HostPortal:
- popHostContainer(failedUnitOfWork);
- break;
- case ContextProvider:
- popProvider(failedUnitOfWork);
- break;
- }
- // Replay the begin phase.
- isReplayingFailedUnitOfWork = true;
- originalReplayError = thrownValue;
- invokeGuardedCallback(null, workLoop, null, isYieldy);
- isReplayingFailedUnitOfWork = false;
- originalReplayError = null;
- if (hasCaughtError()) {
- const replayError = clearCaughtError();
- if (replayError != null && thrownValue != null) {
- try {
- // Reading the expando property is intentionally
- // inside `try` because it might be a getter or Proxy.
- if (replayError._suppressLogging) {
- // Also suppress logging for the original error.
- (thrownValue: any)._suppressLogging = true;
- }
- } catch (inner) {
- // Ignore.
- }
- }
- } else {
- // If the begin phase did not fail the second time, set this pointer
- // back to the original value.
- nextUnitOfWork = failedUnitOfWork;
- }
- };
- rethrowOriginalError = () => {
- throw originalReplayError;
- };
-}
-
-function resetStack() {
- if (nextUnitOfWork !== null) {
- let interruptedWork = nextUnitOfWork.return;
- while (interruptedWork !== null) {
- unwindInterruptedWork(interruptedWork);
- interruptedWork = interruptedWork.return;
- }
- }
-
- if (__DEV__) {
- ReactStrictModeWarnings.discardPendingWarnings();
- checkThatStackIsEmpty();
- }
-
- nextRoot = null;
- nextRenderExpirationTime = NoWork;
- mostRecentEventTime = Sync;
- nextRenderDidSuspend = false;
- nextRenderDidError = false;
- nextUnitOfWork = null;
-}
-
-function commitAllHostEffects() {
- while (nextEffect !== null) {
- if (__DEV__) {
- setCurrentFiber(nextEffect);
- }
- recordEffect();
-
- const effectTag = nextEffect.effectTag;
-
- if (effectTag & ContentReset) {
- commitResetTextContent(nextEffect);
- }
-
- if (effectTag & Ref) {
- const current = nextEffect.alternate;
- if (current !== null) {
- commitDetachRef(current);
- }
- }
-
- // The following switch statement is only concerned about placement,
- // updates, and deletions. To avoid needing to add a case for every
- // possible bitmap value, we remove the secondary effects from the
- // effect tag and switch on that value.
- let primaryEffectTag = effectTag & (Placement | Update | Deletion);
- switch (primaryEffectTag) {
- case Placement: {
- commitPlacement(nextEffect);
- // Clear the "placement" from effect tag so that we know that this is inserted, before
- // any life-cycles like componentDidMount gets called.
- // TODO: findDOMNode doesn't rely on this any more but isMounted
- // does and isMounted is deprecated anyway so we should be able
- // to kill this.
- nextEffect.effectTag &= ~Placement;
- break;
- }
- case PlacementAndUpdate: {
- // Placement
- commitPlacement(nextEffect);
- // Clear the "placement" from effect tag so that we know that this is inserted, before
- // any life-cycles like componentDidMount gets called.
- nextEffect.effectTag &= ~Placement;
-
- // Update
- const current = nextEffect.alternate;
- commitWork(current, nextEffect);
- break;
- }
- case Update: {
- const current = nextEffect.alternate;
- commitWork(current, nextEffect);
- break;
- }
- case Deletion: {
- commitDeletion(nextEffect);
- break;
- }
- }
- nextEffect = nextEffect.nextEffect;
- }
-
- if (__DEV__) {
- resetCurrentFiber();
- }
-}
-
-function commitBeforeMutationLifecycles() {
- while (nextEffect !== null) {
- if (__DEV__) {
- setCurrentFiber(nextEffect);
- }
-
- const effectTag = nextEffect.effectTag;
- if (effectTag & Snapshot) {
- recordEffect();
- const current = nextEffect.alternate;
- commitBeforeMutationLifeCycles(current, nextEffect);
- }
-
- nextEffect = nextEffect.nextEffect;
- }
-
- if (__DEV__) {
- resetCurrentFiber();
- }
-}
-
-function commitAllLifeCycles(
- finishedRoot: FiberRoot,
- committedExpirationTime: ExpirationTime,
-) {
- if (__DEV__) {
- ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
- ReactStrictModeWarnings.flushLegacyContextWarning();
-
- if (warnAboutDeprecatedLifecycles) {
- ReactStrictModeWarnings.flushPendingDeprecationWarnings();
- }
- }
- while (nextEffect !== null) {
- if (__DEV__) {
- setCurrentFiber(nextEffect);
- }
- const effectTag = nextEffect.effectTag;
-
- if (effectTag & (Update | Callback)) {
- recordEffect();
- const current = nextEffect.alternate;
- commitLifeCycles(
- finishedRoot,
- current,
- nextEffect,
- committedExpirationTime,
- );
- }
-
- if (effectTag & Ref) {
- recordEffect();
- commitAttachRef(nextEffect);
- }
-
- if (effectTag & Passive) {
- rootWithPendingPassiveEffects = finishedRoot;
- }
-
- nextEffect = nextEffect.nextEffect;
- }
- if (__DEV__) {
- resetCurrentFiber();
- }
-}
-
-function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
- rootWithPendingPassiveEffects = null;
- passiveEffectCallbackHandle = null;
- passiveEffectCallback = null;
-
- // Set this to true to prevent re-entrancy
- const previousIsRendering = isRendering;
- isRendering = true;
-
- let effect = firstEffect;
- do {
- if (__DEV__) {
- setCurrentFiber(effect);
- }
-
- if (effect.effectTag & Passive) {
- let didError = false;
- let error;
- if (__DEV__) {
- isInPassiveEffectDEV = true;
- invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
- isInPassiveEffectDEV = false;
- if (hasCaughtError()) {
- didError = true;
- error = clearCaughtError();
- }
- } else {
- try {
- commitPassiveHookEffects(effect);
- } catch (e) {
- didError = true;
- error = e;
- }
- }
- if (didError) {
- captureCommitPhaseError(effect, error);
- }
- }
- effect = effect.nextEffect;
- } while (effect !== null);
- if (__DEV__) {
- resetCurrentFiber();
- }
-
- isRendering = previousIsRendering;
-
- // Check if work was scheduled by one of the effects
- const rootExpirationTime = root.expirationTime;
- if (rootExpirationTime !== NoWork) {
- requestWork(root, rootExpirationTime);
- }
- // Flush any sync work that was scheduled by effects
- if (!isBatchingUpdates && !isRendering) {
- performSyncWork();
- }
-
- if (__DEV__) {
- if (rootWithPendingPassiveEffects === root) {
- nestedPassiveEffectCountDEV++;
- } else {
- nestedPassiveEffectCountDEV = 0;
- }
- }
-}
-
-function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
- return (
- legacyErrorBoundariesThatAlreadyFailed !== null &&
- legacyErrorBoundariesThatAlreadyFailed.has(instance)
- );
-}
-
-function markLegacyErrorBoundaryAsFailed(instance: mixed) {
- if (legacyErrorBoundariesThatAlreadyFailed === null) {
- legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
- } else {
- legacyErrorBoundariesThatAlreadyFailed.add(instance);
- }
-}
-
-function flushPassiveEffects() {
- const didFlushEffects = passiveEffectCallback !== null;
- if (passiveEffectCallbackHandle !== null) {
- cancelCallback(passiveEffectCallbackHandle);
- }
- if (passiveEffectCallback !== null) {
- // We call the scheduled callback instead of commitPassiveEffects directly
- // to ensure tracing works correctly.
- passiveEffectCallback();
- }
- return didFlushEffects;
-}
-
-function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
- isWorking = true;
- isCommitting = true;
- startCommitTimer();
-
- invariant(
- root.current !== finishedWork,
- 'Cannot commit the same tree as before. This is probably a bug ' +
- 'related to the return field. This error is likely caused by a bug ' +
- 'in React. Please file an issue.',
- );
- const committedExpirationTime = root.pendingCommitExpirationTime;
- invariant(
- committedExpirationTime !== NoWork,
- 'Cannot commit an incomplete root. This error is likely caused by a ' +
- 'bug in React. Please file an issue.',
- );
- root.pendingCommitExpirationTime = NoWork;
-
- // Update the pending priority levels to account for the work that we are
- // about to commit. This needs to happen before calling the lifecycles, since
- // they may schedule additional updates.
- const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
- const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
- const earliestRemainingTimeBeforeCommit =
- childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
- ? childExpirationTimeBeforeCommit
- : updateExpirationTimeBeforeCommit;
- markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
-
- let prevInteractions: Set = (null: any);
- if (enableSchedulerTracing) {
- // Restore any pending interactions at this point,
- // So that cascading work triggered during the render phase will be accounted for.
- prevInteractions = __interactionsRef.current;
- __interactionsRef.current = root.memoizedInteractions;
- }
-
- // Reset this to null before calling lifecycles
- ReactCurrentOwner.current = null;
-
- let firstEffect;
- if (finishedWork.effectTag > PerformedWork) {
- // A fiber's effect list consists only of its children, not itself. So if
- // the root has an effect, we need to add it to the end of the list. The
- // resulting list is the set that would belong to the root's parent, if
- // it had one; that is, all the effects in the tree including the root.
- if (finishedWork.lastEffect !== null) {
- finishedWork.lastEffect.nextEffect = finishedWork;
- firstEffect = finishedWork.firstEffect;
- } else {
- firstEffect = finishedWork;
- }
- } else {
- // There is no effect on the root.
- firstEffect = finishedWork.firstEffect;
- }
-
- prepareForCommit(root.containerInfo);
-
- // Invoke instances of getSnapshotBeforeUpdate before mutation.
- nextEffect = firstEffect;
- startCommitSnapshotEffectsTimer();
- while (nextEffect !== null) {
- let didError = false;
- let error;
- if (__DEV__) {
- invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
- if (hasCaughtError()) {
- didError = true;
- error = clearCaughtError();
- }
- } else {
- try {
- commitBeforeMutationLifecycles();
- } catch (e) {
- didError = true;
- error = e;
- }
- }
- if (didError) {
- invariant(
- nextEffect !== null,
- 'Should have next effect. This error is likely caused by a bug ' +
- 'in React. Please file an issue.',
- );
- captureCommitPhaseError(nextEffect, error);
- // Clean-up
- if (nextEffect !== null) {
- nextEffect = nextEffect.nextEffect;
- }
- }
- }
- stopCommitSnapshotEffectsTimer();
-
- if (enableProfilerTimer) {
- // Mark the current commit time to be shared by all Profilers in this batch.
- // This enables them to be grouped later.
- recordCommitTime();
- }
-
- // Commit all the side-effects within a tree. We'll do this in two passes.
- // The first pass performs all the host insertions, updates, deletions and
- // ref unmounts.
- nextEffect = firstEffect;
- startCommitHostEffectsTimer();
- while (nextEffect !== null) {
- let didError = false;
- let error;
- if (__DEV__) {
- invokeGuardedCallback(null, commitAllHostEffects, null);
- if (hasCaughtError()) {
- didError = true;
- error = clearCaughtError();
- }
- } else {
- try {
- commitAllHostEffects();
- } catch (e) {
- didError = true;
- error = e;
- }
- }
- if (didError) {
- invariant(
- nextEffect !== null,
- 'Should have next effect. This error is likely caused by a bug ' +
- 'in React. Please file an issue.',
- );
- captureCommitPhaseError(nextEffect, error);
- // Clean-up
- if (nextEffect !== null) {
- nextEffect = nextEffect.nextEffect;
- }
- }
- }
- stopCommitHostEffectsTimer();
-
- resetAfterCommit(root.containerInfo);
-
- // The work-in-progress tree is now the current tree. This must come after
- // the first pass of the commit phase, so that the previous tree is still
- // current during componentWillUnmount, but before the second pass, so that
- // the finished work is current during componentDidMount/Update.
- root.current = finishedWork;
-
- // In the second pass we'll perform all life-cycles and ref callbacks.
- // Life-cycles happen as a separate pass so that all placements, updates,
- // and deletions in the entire tree have already been invoked.
- // This pass also triggers any renderer-specific initial effects.
- nextEffect = firstEffect;
- startCommitLifeCyclesTimer();
- while (nextEffect !== null) {
- let didError = false;
- let error;
- if (__DEV__) {
- invokeGuardedCallback(
- null,
- commitAllLifeCycles,
- null,
- root,
- committedExpirationTime,
- );
- if (hasCaughtError()) {
- didError = true;
- error = clearCaughtError();
- }
- } else {
- try {
- commitAllLifeCycles(root, committedExpirationTime);
- } catch (e) {
- didError = true;
- error = e;
- }
- }
- if (didError) {
- invariant(
- nextEffect !== null,
- 'Should have next effect. This error is likely caused by a bug ' +
- 'in React. Please file an issue.',
- );
- captureCommitPhaseError(nextEffect, error);
- if (nextEffect !== null) {
- nextEffect = nextEffect.nextEffect;
- }
- }
- }
-
- if (firstEffect !== null && rootWithPendingPassiveEffects !== null) {
- // This commit included a passive effect. These do not need to fire until
- // after the next paint. Schedule an callback to fire them in an async
- // event. To ensure serial execution, the callback will be flushed early if
- // we enter rootWithPendingPassiveEffects commit phase before then.
- let callback = commitPassiveEffects.bind(null, root, firstEffect);
- if (enableSchedulerTracing) {
- // TODO: Avoid this extra callback by mutating the tracing ref directly,
- // like we do at the beginning of commitRoot. I've opted not to do that
- // here because that code is still in flux.
- callback = Scheduler_tracing_wrap(callback);
- }
- passiveEffectCallbackHandle = scheduleCallback(NormalPriority, callback);
- passiveEffectCallback = callback;
- }
-
- isCommitting = false;
- isWorking = false;
- stopCommitLifeCyclesTimer();
- stopCommitTimer();
- onCommitRoot(finishedWork.stateNode);
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
- ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
- }
-
- const updateExpirationTimeAfterCommit = finishedWork.expirationTime;
- const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
- const earliestRemainingTimeAfterCommit =
- childExpirationTimeAfterCommit > updateExpirationTimeAfterCommit
- ? childExpirationTimeAfterCommit
- : updateExpirationTimeAfterCommit;
- if (earliestRemainingTimeAfterCommit === NoWork) {
- // If there's no remaining work, we can clear the set of already failed
- // error boundaries.
- legacyErrorBoundariesThatAlreadyFailed = null;
- }
- onCommit(root, earliestRemainingTimeAfterCommit);
-
- if (enableSchedulerTracing) {
- __interactionsRef.current = prevInteractions;
-
- let subscriber;
-
- try {
- subscriber = __subscriberRef.current;
- if (subscriber !== null && root.memoizedInteractions.size > 0) {
- const threadID = computeThreadID(
- committedExpirationTime,
- root.interactionThreadID,
- );
- subscriber.onWorkStopped(root.memoizedInteractions, threadID);
- }
- } catch (error) {
- // It's not safe for commitRoot() to throw.
- // Store the error for now and we'll re-throw in finishRendering().
- if (!hasUnhandledError) {
- hasUnhandledError = true;
- unhandledError = error;
- }
- } finally {
- // Clear completed interactions from the pending Map.
- // Unless the render was suspended or cascading work was scheduled,
- // In which case– leave pending interactions until the subsequent render.
- const pendingInteractionMap = root.pendingInteractionMap;
- pendingInteractionMap.forEach(
- (scheduledInteractions, scheduledExpirationTime) => {
- // Only decrement the pending interaction count if we're done.
- // If there's still work at the current priority,
- // That indicates that we are waiting for suspense data.
- if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
- pendingInteractionMap.delete(scheduledExpirationTime);
-
- scheduledInteractions.forEach(interaction => {
- interaction.__count--;
-
- if (subscriber !== null && interaction.__count === 0) {
- try {
- subscriber.onInteractionScheduledWorkCompleted(interaction);
- } catch (error) {
- // It's not safe for commitRoot() to throw.
- // Store the error for now and we'll re-throw in finishRendering().
- if (!hasUnhandledError) {
- hasUnhandledError = true;
- unhandledError = error;
- }
- }
- }
- });
- }
- },
- );
- }
- }
-}
-
-function resetChildExpirationTime(
- workInProgress: Fiber,
- renderTime: ExpirationTime,
-) {
- if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
- // The children of this component are hidden. Don't bubble their
- // expiration times.
- return;
- }
-
- let newChildExpirationTime = NoWork;
-
- // Bubble up the earliest expiration time.
- if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
- // We're in profiling mode.
- // Let's use this same traversal to update the render durations.
- let actualDuration = workInProgress.actualDuration;
- let treeBaseDuration = workInProgress.selfBaseDuration;
-
- // When a fiber is cloned, its actualDuration is reset to 0.
- // This value will only be updated if work is done on the fiber (i.e. it doesn't bailout).
- // When work is done, it should bubble to the parent's actualDuration.
- // If the fiber has not been cloned though, (meaning no work was done),
- // Then this value will reflect the amount of time spent working on a previous render.
- // In that case it should not bubble.
- // We determine whether it was cloned by comparing the child pointer.
- const shouldBubbleActualDurations =
- workInProgress.alternate === null ||
- workInProgress.child !== workInProgress.alternate.child;
-
- let child = workInProgress.child;
- while (child !== null) {
- const childUpdateExpirationTime = child.expirationTime;
- const childChildExpirationTime = child.childExpirationTime;
- if (childUpdateExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childUpdateExpirationTime;
- }
- if (childChildExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childChildExpirationTime;
- }
- if (shouldBubbleActualDurations) {
- actualDuration += child.actualDuration;
- }
- treeBaseDuration += child.treeBaseDuration;
- child = child.sibling;
- }
- workInProgress.actualDuration = actualDuration;
- workInProgress.treeBaseDuration = treeBaseDuration;
- } else {
- let child = workInProgress.child;
- while (child !== null) {
- const childUpdateExpirationTime = child.expirationTime;
- const childChildExpirationTime = child.childExpirationTime;
- if (childUpdateExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childUpdateExpirationTime;
- }
- if (childChildExpirationTime > newChildExpirationTime) {
- newChildExpirationTime = childChildExpirationTime;
- }
- child = child.sibling;
- }
- }
-
- workInProgress.childExpirationTime = newChildExpirationTime;
-}
-
-function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
- // Attempt to complete the current unit of work, then move to the
- // next sibling. If there are no more siblings, return to the
- // parent fiber.
- while (true) {
- // The current, flushed, state of this fiber is the alternate.
- // Ideally nothing should rely on this, but relying on it here
- // means that we don't need an additional field on the work in
- // progress.
- const current = workInProgress.alternate;
- if (__DEV__) {
- setCurrentFiber(workInProgress);
- }
-
- const returnFiber = workInProgress.return;
- const siblingFiber = workInProgress.sibling;
-
- if ((workInProgress.effectTag & Incomplete) === NoEffect) {
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- // Don't replay if it fails during completion phase.
- mayReplayFailedUnitOfWork = false;
- }
- // This fiber completed.
- // Remember we're completing this unit so we can find a boundary if it fails.
- nextUnitOfWork = workInProgress;
- if (enableProfilerTimer) {
- if (workInProgress.mode & ProfileMode) {
- startProfilerTimer(workInProgress);
- }
- nextUnitOfWork = completeWork(
- current,
- workInProgress,
- nextRenderExpirationTime,
- );
- if (workInProgress.mode & ProfileMode) {
- // Update render duration assuming we didn't error.
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
- }
- } else {
- nextUnitOfWork = completeWork(
- current,
- workInProgress,
- nextRenderExpirationTime,
- );
- }
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- // We're out of completion phase so replaying is fine now.
- mayReplayFailedUnitOfWork = true;
- }
- stopWorkTimer(workInProgress);
- resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
- if (__DEV__) {
- resetCurrentFiber();
- }
-
- if (nextUnitOfWork !== null) {
- // Completing this fiber spawned new work. Work on that next.
- return nextUnitOfWork;
- }
-
- if (
- returnFiber !== null &&
- // Do not append effects to parents if a sibling failed to complete
- (returnFiber.effectTag & Incomplete) === NoEffect
- ) {
- // Append all the effects of the subtree and this fiber onto the effect
- // list of the parent. The completion order of the children affects the
- // side-effect order.
- if (returnFiber.firstEffect === null) {
- returnFiber.firstEffect = workInProgress.firstEffect;
- }
- if (workInProgress.lastEffect !== null) {
- if (returnFiber.lastEffect !== null) {
- returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
- }
- returnFiber.lastEffect = workInProgress.lastEffect;
- }
-
- // If this fiber had side-effects, we append it AFTER the children's
- // side-effects. We can perform certain side-effects earlier if
- // needed, by doing multiple passes over the effect list. We don't want
- // to schedule our own side-effect on our own list because if end up
- // reusing children we'll schedule this effect onto itself since we're
- // at the end.
- const effectTag = workInProgress.effectTag;
- // Skip both NoWork and PerformedWork tags when creating the effect list.
- // PerformedWork effect is read by React DevTools but shouldn't be committed.
- if (effectTag > PerformedWork) {
- if (returnFiber.lastEffect !== null) {
- returnFiber.lastEffect.nextEffect = workInProgress;
- } else {
- returnFiber.firstEffect = workInProgress;
- }
- returnFiber.lastEffect = workInProgress;
- }
- }
-
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
- }
-
- if (siblingFiber !== null) {
- // If there is more work to do in this returnFiber, do that next.
- return siblingFiber;
- } else if (returnFiber !== null) {
- // If there's no more work in this returnFiber. Complete the returnFiber.
- workInProgress = returnFiber;
- continue;
- } else {
- // We've reached the root.
- return null;
- }
- } else {
- if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
- // Record the render duration for the fiber that errored.
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
-
- // Include the time spent working on failed children before continuing.
- let actualDuration = workInProgress.actualDuration;
- let child = workInProgress.child;
- while (child !== null) {
- actualDuration += child.actualDuration;
- child = child.sibling;
- }
- workInProgress.actualDuration = actualDuration;
- }
-
- // This fiber did not complete because something threw. Pop values off
- // the stack without entering the complete phase. If this is a boundary,
- // capture values if possible.
- const next = unwindWork(workInProgress, nextRenderExpirationTime);
- // Because this fiber did not complete, don't reset its expiration time.
- if (workInProgress.effectTag & DidCapture) {
- // Restarting an error boundary
- stopFailedWorkTimer(workInProgress);
- } else {
- stopWorkTimer(workInProgress);
- }
-
- if (__DEV__) {
- resetCurrentFiber();
- }
-
- if (next !== null) {
- stopWorkTimer(workInProgress);
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
- }
-
- // If completing this work spawned new work, do that next. We'll come
- // back here again.
- // Since we're restarting, remove anything that is not a host effect
- // from the effect tag.
- next.effectTag &= HostEffectMask;
- return next;
- }
-
- if (returnFiber !== null) {
- // Mark the parent fiber as incomplete and clear its effect list.
- returnFiber.firstEffect = returnFiber.lastEffect = null;
- returnFiber.effectTag |= Incomplete;
- }
-
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
- }
-
- if (siblingFiber !== null) {
- // If there is more work to do in this returnFiber, do that next.
- return siblingFiber;
- } else if (returnFiber !== null) {
- // If there's no more work in this returnFiber. Complete the returnFiber.
- workInProgress = returnFiber;
- continue;
- } else {
- return null;
- }
- }
- }
-
- // Without this explicit null return Flow complains of invalid return type
- // TODO Remove the above while(true) loop
- // eslint-disable-next-line no-unreachable
- return null;
-}
-
-function performUnitOfWork(workInProgress: Fiber): Fiber | null {
- // The current, flushed, state of this fiber is the alternate.
- // Ideally nothing should rely on this, but relying on it here
- // means that we don't need an additional field on the work in
- // progress.
- const current = workInProgress.alternate;
-
- // See if beginning this work spawns more work.
- startWorkTimer(workInProgress);
- if (__DEV__) {
- setCurrentFiber(workInProgress);
- }
-
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
- stashedWorkInProgressProperties,
- workInProgress,
- );
- }
-
- let next;
- if (enableProfilerTimer) {
- if (workInProgress.mode & ProfileMode) {
- startProfilerTimer(workInProgress);
- }
-
- next = beginWork(current, workInProgress, nextRenderExpirationTime);
- workInProgress.memoizedProps = workInProgress.pendingProps;
-
- if (workInProgress.mode & ProfileMode) {
- // Record the render duration assuming we didn't bailout (or error).
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
- }
- } else {
- next = beginWork(current, workInProgress, nextRenderExpirationTime);
- workInProgress.memoizedProps = workInProgress.pendingProps;
- }
-
- if (__DEV__) {
- resetCurrentFiber();
- if (isReplayingFailedUnitOfWork) {
- // Currently replaying a failed unit of work. This should be unreachable,
- // because the render phase is meant to be idempotent, and it should
- // have thrown again. Since it didn't, rethrow the original error, so
- // React's internal stack is not misaligned.
- rethrowOriginalError();
- }
- }
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
- ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
- }
-
- if (next === null) {
- // If this doesn't spawn new work, complete the current work.
- next = completeUnitOfWork(workInProgress);
- }
-
- ReactCurrentOwner.current = null;
-
- return next;
-}
-
-function workLoop(isYieldy) {
- if (!isYieldy) {
- // Flush work without yielding
- while (nextUnitOfWork !== null) {
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
- }
- } else {
- // Flush asynchronous work until there's a higher priority event
- while (nextUnitOfWork !== null && !shouldYield()) {
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
- }
- }
-}
-
-function jnd(timeElapsed: number) {
- return timeElapsed < 120
- ? 120
- : timeElapsed < 480
- ? 480
- : timeElapsed < 1080
- ? 1080
- : timeElapsed < 1920
- ? 1920
- : timeElapsed < 3000
- ? 3000
- : timeElapsed < 4320
- ? 4320
- : Math.ceil(timeElapsed / 1960) * 1960;
-}
-
-function renderRoot(root: FiberRoot, isYieldy: boolean): void {
- invariant(
- !isWorking,
- 'renderRoot was called recursively. This error is likely caused ' +
- 'by a bug in React. Please file an issue.',
- );
-
- flushPassiveEffects();
-
- isWorking = true;
- const previousDispatcher = ReactCurrentDispatcher.current;
- ReactCurrentDispatcher.current = ContextOnlyDispatcher;
-
- const expirationTime = root.nextExpirationTimeToWorkOn;
-
- // Check if we're starting from a fresh stack, or if we're resuming from
- // previously yielded work.
- if (
- expirationTime !== nextRenderExpirationTime ||
- root !== nextRoot ||
- nextUnitOfWork === null
- ) {
- // Reset the stack and start working from the root.
- resetStack();
- nextRoot = root;
- nextRenderExpirationTime = expirationTime;
- nextUnitOfWork = createWorkInProgress(
- nextRoot.current,
- null,
- nextRenderExpirationTime,
- );
- root.pendingCommitExpirationTime = NoWork;
-
- if (enableSchedulerTracing) {
- // Determine which interactions this batch of work currently includes,
- // So that we can accurately attribute time spent working on it,
- // And so that cascading work triggered during the render phase will be associated with it.
- const interactions: Set = new Set();
- root.pendingInteractionMap.forEach(
- (scheduledInteractions, scheduledExpirationTime) => {
- if (scheduledExpirationTime >= expirationTime) {
- scheduledInteractions.forEach(interaction =>
- interactions.add(interaction),
- );
- }
- },
- );
-
- // Store the current set of interactions on the FiberRoot for a few reasons:
- // We can re-use it in hot functions like renderRoot() without having to recalculate it.
- // We will also use it in commitWork() to pass to any Profiler onRender() hooks.
- // This also provides DevTools with a way to access it when the onCommitRoot() hook is called.
- root.memoizedInteractions = interactions;
-
- if (interactions.size > 0) {
- const subscriber = __subscriberRef.current;
- if (subscriber !== null) {
- const threadID = computeThreadID(
- expirationTime,
- root.interactionThreadID,
- );
- try {
- subscriber.onWorkStarted(interactions, threadID);
- } catch (error) {
- // Work thrown by an interaction tracing subscriber should be rethrown,
- // But only once it's safe (to avoid leaving the scheduler in an invalid state).
- // Store the error for now and we'll re-throw in finishRendering().
- if (!hasUnhandledError) {
- hasUnhandledError = true;
- unhandledError = error;
- }
- }
- }
- }
- }
- }
-
- let prevInteractions: Set = (null: any);
- if (enableSchedulerTracing) {
- // We're about to start new traced work.
- // Restore pending interactions so cascading work triggered during the render phase will be accounted for.
- prevInteractions = __interactionsRef.current;
- __interactionsRef.current = root.memoizedInteractions;
- }
-
- let didFatal = false;
-
- startWorkLoopTimer(nextUnitOfWork);
-
- do {
- try {
- workLoop(isYieldy);
- } catch (thrownValue) {
- resetContextDependences();
- resetHooks();
-
- // Reset in case completion throws.
- // This is only used in DEV and when replaying is on.
- let mayReplay;
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- mayReplay = mayReplayFailedUnitOfWork;
- mayReplayFailedUnitOfWork = true;
- }
-
- if (nextUnitOfWork === null) {
- // This is a fatal error.
- didFatal = true;
- onUncaughtError(thrownValue);
- } else {
- if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) {
- // Record the time spent rendering before an error was thrown.
- // This avoids inaccurate Profiler durations in the case of a suspended render.
- stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true);
- }
-
- if (__DEV__) {
- // Reset global debug state
- // We assume this is defined in DEV
- (resetCurrentlyProcessingQueue: any)();
- }
-
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
- if (mayReplay) {
- const failedUnitOfWork: Fiber = nextUnitOfWork;
- replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
- }
- }
-
- // TODO: we already know this isn't true in some cases.
- // At least this shows a nicer error message until we figure out the cause.
- // https://github.com/facebook/react/issues/12449#issuecomment-386727431
- invariant(
- nextUnitOfWork !== null,
- 'Failed to replay rendering after an error. This ' +
- 'is likely caused by a bug in React. Please file an issue ' +
- 'with a reproducing case to help us find it.',
- );
-
- const sourceFiber: Fiber = nextUnitOfWork;
- let returnFiber = sourceFiber.return;
- if (returnFiber === null) {
- // This is the root. The root could capture its own errors. However,
- // we don't know if it errors before or after we pushed the host
- // context. This information is needed to avoid a stack mismatch.
- // Because we're not sure, treat this as a fatal error. We could track
- // which phase it fails in, but doesn't seem worth it. At least
- // for now.
- didFatal = true;
- onUncaughtError(thrownValue);
- } else {
- throwException(
- root,
- returnFiber,
- sourceFiber,
- thrownValue,
- nextRenderExpirationTime,
- );
- nextUnitOfWork = completeUnitOfWork(sourceFiber);
- continue;
- }
- }
- }
- break;
- } while (true);
-
- if (enableSchedulerTracing) {
- // Traced work is done for now; restore the previous interactions.
- __interactionsRef.current = prevInteractions;
- }
-
- // We're done performing work. Time to clean up.
- isWorking = false;
- ReactCurrentDispatcher.current = previousDispatcher;
- resetContextDependences();
- resetHooks();
-
- // Yield back to main thread.
- if (didFatal) {
- const didCompleteRoot = false;
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
- interruptedBy = null;
- // There was a fatal error.
- if (__DEV__) {
- resetStackAfterFatalErrorInDev();
- }
- // `nextRoot` points to the in-progress root. A non-null value indicates
- // that we're in the middle of an async render. Set it to null to indicate
- // there's no more work to be done in the current batch.
- nextRoot = null;
- onFatal(root);
- return;
- }
-
- if (nextUnitOfWork !== null) {
- // There's still remaining async work in this tree, but we ran out of time
- // in the current frame. Yield back to the renderer. Unless we're
- // interrupted by a higher priority update, we'll continue later from where
- // we left off.
- const didCompleteRoot = false;
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
- interruptedBy = null;
- onYield(root);
- return;
- }
-
- // We completed the whole tree.
- const didCompleteRoot = true;
- stopWorkLoopTimer(interruptedBy, didCompleteRoot);
- const rootWorkInProgress = root.current.alternate;
- invariant(
- rootWorkInProgress !== null,
- 'Finished root should have a work-in-progress. This error is likely ' +
- 'caused by a bug in React. Please file an issue.',
- );
-
- // `nextRoot` points to the in-progress root. A non-null value indicates
- // that we're in the middle of an async render. Set it to null to indicate
- // there's no more work to be done in the current batch.
- nextRoot = null;
- interruptedBy = null;
-
- if (nextRenderDidError) {
- // There was an error
- if (hasLowerPriorityWork(root, expirationTime)) {
- // There's lower priority work. If so, it may have the effect of fixing
- // the exception that was just thrown. Exit without committing. This is
- // similar to a suspend, but without a timeout because we're not waiting
- // for a promise to resolve. React will restart at the lower
- // priority level.
- markSuspendedPriorityLevel(root, expirationTime);
- const suspendedExpirationTime = expirationTime;
- const rootExpirationTime = root.expirationTime;
- onSuspend(
- root,
- rootWorkInProgress,
- suspendedExpirationTime,
- rootExpirationTime,
- -1, // Indicates no timeout
- );
- return;
- } else if (
- // There's no lower priority work, but we're rendering asynchronously.
- // Synchronously attempt to render the same level one more time. This is
- // similar to a suspend, but without a timeout because we're not waiting
- // for a promise to resolve.
- !root.didError &&
- isYieldy
- ) {
- root.didError = true;
- const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
- const rootExpirationTime = (root.expirationTime = Sync);
- onSuspend(
- root,
- rootWorkInProgress,
- suspendedExpirationTime,
- rootExpirationTime,
- -1, // Indicates no timeout
- );
- return;
- }
- }
-
- // Check if we should suspend this commit.
- // If mostRecentEventTime is Sync, that means we didn't track any event
- // times. That can happen if we retried but nothing switched from fallback
- // to content. There's no reason to delay doing no work.
- if (isYieldy && nextRenderDidSuspend && mostRecentEventTime !== Sync) {
- // The tree was suspended.
- const suspendedExpirationTime = expirationTime;
- markSuspendedPriorityLevel(root, suspendedExpirationTime);
-
- const eventTimeMs: number = inferTimeFromExpirationTime(
- mostRecentEventTime,
- );
- const currentTimeMs: number = now();
- const timeElapsed = currentTimeMs - eventTimeMs;
-
- let msUntilTimeout = jnd(timeElapsed) - timeElapsed;
-
- if (msUntilTimeout < 10) {
- // Don't bother with a very short suspense time.
- msUntilTimeout = 0;
- } else {
- // Compute the time until this render pass would expire.
- const timeUntilExpirationMs =
- expirationTimeToMs(suspendedExpirationTime) +
- originalStartTimeMs -
- currentTimeMs;
- // Clamp the timeout to the expiration time.
- if (timeUntilExpirationMs < msUntilTimeout) {
- msUntilTimeout = timeUntilExpirationMs;
- }
- }
-
- const rootExpirationTime = root.expirationTime;
- onSuspend(
- root,
- rootWorkInProgress,
- suspendedExpirationTime,
- rootExpirationTime,
- msUntilTimeout,
- );
- return;
- }
-
- // Ready to commit.
- onComplete(root, rootWorkInProgress, expirationTime);
-}
-
-function captureCommitPhaseError(sourceFiber: Fiber, value: mixed) {
- const expirationTime = Sync;
- let fiber = sourceFiber.return;
- while (fiber !== null) {
- switch (fiber.tag) {
- case ClassComponent:
- const ctor = fiber.type;
- const instance = fiber.stateNode;
- if (
- typeof ctor.getDerivedStateFromError === 'function' ||
- (typeof instance.componentDidCatch === 'function' &&
- !isAlreadyFailedLegacyErrorBoundary(instance))
- ) {
- const errorInfo = createCapturedValue(value, sourceFiber);
- const update = createClassErrorUpdate(
- fiber,
- errorInfo,
- expirationTime,
- );
- enqueueUpdate(fiber, update);
- scheduleWork(fiber, expirationTime);
- return;
- }
- break;
- case HostRoot: {
- const errorInfo = createCapturedValue(value, sourceFiber);
- const update = createRootErrorUpdate(fiber, errorInfo, expirationTime);
- enqueueUpdate(fiber, update);
- scheduleWork(fiber, expirationTime);
- return;
- }
- }
- fiber = fiber.return;
- }
-
- if (sourceFiber.tag === HostRoot) {
- // Error was thrown at the root. There is no parent, so the root
- // itself should capture it.
- const rootFiber = sourceFiber;
- const errorInfo = createCapturedValue(value, rootFiber);
- const update = createRootErrorUpdate(rootFiber, errorInfo, expirationTime);
- enqueueUpdate(rootFiber, update);
- scheduleWork(rootFiber, expirationTime);
- }
-}
-
-function computeThreadID(
- expirationTime: ExpirationTime,
- interactionThreadID: number,
-): number {
- // Interaction threads are unique per root and expiration time.
- return expirationTime * 1000 + interactionThreadID;
-}
-
-// Creates a unique async expiration time.
-function computeUniqueAsyncExpiration(): ExpirationTime {
- const currentTime = requestCurrentTime();
- let result = computeAsyncExpiration(currentTime);
- if (result >= lastUniqueAsyncExpiration) {
- // Since we assume the current time monotonically increases, we only hit
- // this branch when computeUniqueAsyncExpiration is fired multiple times
- // within a 200ms window (or whatever the async bucket size is).
- result = lastUniqueAsyncExpiration - 1;
- }
- lastUniqueAsyncExpiration = result;
- return lastUniqueAsyncExpiration;
-}
-
-function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
- let expirationTime;
- if (expirationContext !== NoWork) {
- // An explicit expiration context was set;
- expirationTime = expirationContext;
- } else if (isWorking) {
- if (isCommitting) {
- // Updates that occur during the commit phase should have sync priority
- // by default.
- expirationTime = Sync;
- } else {
- // Updates during the render phase should expire at the same time as
- // the work that is being rendered.
- expirationTime = nextRenderExpirationTime;
- }
- } else {
- // No explicit expiration context was set, and we're not currently
- // performing work. Calculate a new expiration time.
- if (fiber.mode & ConcurrentMode) {
- if (isBatchingInteractiveUpdates) {
- // This is an interactive update
- expirationTime = computeInteractiveExpiration(currentTime);
- } else {
- // This is an async update
- expirationTime = computeAsyncExpiration(currentTime);
- }
- // If we're in the middle of rendering a tree, do not update at the same
- // expiration time that is already rendering.
- if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
- expirationTime -= 1;
- }
- } else {
- // This is a sync update
- expirationTime = Sync;
- }
- }
- if (isBatchingInteractiveUpdates) {
- // This is an interactive update. Keep track of the lowest pending
- // interactive expiration time. This allows us to synchronously flush
- // all interactive updates when needed.
- if (
- lowestPriorityPendingInteractiveExpirationTime === NoWork ||
- expirationTime < lowestPriorityPendingInteractiveExpirationTime
- ) {
- lowestPriorityPendingInteractiveExpirationTime = expirationTime;
- }
- }
- return expirationTime;
-}
-
-function markRenderEventTime(expirationTime: ExpirationTime): void {
- if (expirationTime < mostRecentEventTime) {
- mostRecentEventTime = expirationTime;
- }
-}
-
-function renderDidSuspend() {
- nextRenderDidSuspend = true;
-}
-
-function renderDidError() {
- nextRenderDidError = true;
-}
-
-function inferTimeFromExpirationTime(expirationTime: ExpirationTime) {
- // We don't know exactly when the update was scheduled, but we can infer an
- // approximate start time from the expiration time.
- const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
- return (
- earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION + originalStartTimeMs
- );
-}
-
-function pingSuspendedRoot(
- root: FiberRoot,
- thenable: Thenable,
- pingTime: ExpirationTime,
-) {
- // A promise that previously suspended React from committing has resolved.
- // If React is still suspended, try again at the previous level (pingTime).
-
- const pingCache = root.pingCache;
- if (pingCache !== null) {
- // The thenable resolved, so we no longer need to memoize, because it will
- // never be thrown again.
- pingCache.delete(thenable);
- }
-
- if (nextRoot !== null && nextRenderExpirationTime === pingTime) {
- // Received a ping at the same priority level at which we're currently
- // rendering. Restart from the root.
- nextRoot = null;
- } else {
- // Confirm that the root is still suspended at this level. Otherwise exit.
- if (isPriorityLevelSuspended(root, pingTime)) {
- // Ping at the original level
- markPingedPriorityLevel(root, pingTime);
- const rootExpirationTime = root.expirationTime;
- if (rootExpirationTime !== NoWork) {
- requestWork(root, rootExpirationTime);
- }
- }
- }
-}
-
-function retryTimedOutBoundary(boundaryFiber: Fiber) {
- const currentTime = requestCurrentTime();
- const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
- const root = scheduleWorkToRoot(boundaryFiber, retryTime);
- if (root !== null) {
- markPendingPriorityLevel(root, retryTime);
- const rootExpirationTime = root.expirationTime;
- if (rootExpirationTime !== NoWork) {
- requestWork(root, rootExpirationTime);
- }
- }
-}
-
-function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
- // The boundary fiber (a Suspense component) previously timed out and was
- // rendered in its fallback state. One of the promises that suspended it has
- // resolved, which means at least part of the tree was likely unblocked. Try
- // rendering again, at a new expiration time.
-
- let retryCache: WeakSet | Set | null;
- if (enableSuspenseServerRenderer) {
- switch (boundaryFiber.tag) {
- case SuspenseComponent:
- retryCache = boundaryFiber.stateNode;
- break;
- case DehydratedSuspenseComponent:
- retryCache = boundaryFiber.memoizedState;
- break;
- default:
- invariant(
- false,
- 'Pinged unknown suspense boundary type. ' +
- 'This is probably a bug in React.',
- );
- }
- } else {
- retryCache = boundaryFiber.stateNode;
- }
- if (retryCache !== null) {
- // The thenable resolved, so we no longer need to memoize, because it will
- // never be thrown again.
- retryCache.delete(thenable);
- }
-
- retryTimedOutBoundary(boundaryFiber);
-}
-
-function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
- recordScheduleUpdate();
-
- if (__DEV__) {
- if (fiber.tag === ClassComponent) {
- const instance = fiber.stateNode;
- warnAboutInvalidUpdates(instance);
- }
- }
-
- // Update the source fiber's expiration time
- if (fiber.expirationTime < expirationTime) {
- fiber.expirationTime = expirationTime;
- }
- let alternate = fiber.alternate;
- if (alternate !== null && alternate.expirationTime < expirationTime) {
- alternate.expirationTime = expirationTime;
- }
- // Walk the parent path to the root and update the child expiration time.
- let node = fiber.return;
- let root = null;
- if (node === null && fiber.tag === HostRoot) {
- root = fiber.stateNode;
- } else {
- while (node !== null) {
- alternate = node.alternate;
- if (node.childExpirationTime < expirationTime) {
- node.childExpirationTime = expirationTime;
- if (
- alternate !== null &&
- alternate.childExpirationTime < expirationTime
- ) {
- alternate.childExpirationTime = expirationTime;
- }
- } else if (
- alternate !== null &&
- alternate.childExpirationTime < expirationTime
- ) {
- alternate.childExpirationTime = expirationTime;
- }
- if (node.return === null && node.tag === HostRoot) {
- root = node.stateNode;
- break;
- }
- node = node.return;
- }
- }
-
- if (enableSchedulerTracing) {
- if (root !== null) {
- const interactions = __interactionsRef.current;
- if (interactions.size > 0) {
- const pendingInteractionMap = root.pendingInteractionMap;
- const pendingInteractions = pendingInteractionMap.get(expirationTime);
- if (pendingInteractions != null) {
- interactions.forEach(interaction => {
- if (!pendingInteractions.has(interaction)) {
- // Update the pending async work count for previously unscheduled interaction.
- interaction.__count++;
- }
-
- pendingInteractions.add(interaction);
- });
- } else {
- pendingInteractionMap.set(expirationTime, new Set(interactions));
-
- // Update the pending async work count for the current interactions.
- interactions.forEach(interaction => {
- interaction.__count++;
- });
- }
-
- const subscriber = __subscriberRef.current;
- if (subscriber !== null) {
- const threadID = computeThreadID(
- expirationTime,
- root.interactionThreadID,
- );
- subscriber.onWorkScheduled(interactions, threadID);
- }
- }
- }
- }
- return root;
-}
-
-// in a test-like environment, we want to warn if dispatchAction() is
-// called outside of a TestUtils.act(...)/batchedUpdates/render call.
-// so we have a a step counter for when we descend/ascend from
-// act() calls, and test on it for when to warn
-// It's a tuple with a single value. Look for shared/createAct to
-// see how we change the value inside act() calls
-
-export function warnIfNotCurrentlyActingUpdatesInDev(fiber: Fiber): void {
- if (__DEV__) {
- if (
- isBatchingUpdates === false &&
- isRendering === false &&
- ReactShouldWarnActingUpdates.current === false
- ) {
- warningWithoutStack(
- false,
- 'An update to %s inside a test was not wrapped in act(...).\n\n' +
- 'When testing, code that causes React state updates should be wrapped into act(...):\n\n' +
- 'act(() => {\n' +
- ' /* fire events that update state */\n' +
- '});\n' +
- '/* assert on the output */\n\n' +
- "This ensures that you're testing the behavior the user would see in the browser." +
- ' Learn more at https://fb.me/react-wrap-tests-with-act' +
- '%s',
- getComponentName(fiber.type),
- getStackByFiberInDevAndProd(fiber),
- );
- }
- }
-}
-
-function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
- const root = scheduleWorkToRoot(fiber, expirationTime);
- if (root === null) {
- if (__DEV__) {
- switch (fiber.tag) {
- case ClassComponent:
- warnAboutUpdateOnUnmounted(fiber, true);
- break;
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent:
- warnAboutUpdateOnUnmounted(fiber, false);
- break;
- }
- }
- return;
- }
-
- if (
- !isWorking &&
- nextRenderExpirationTime !== NoWork &&
- expirationTime > nextRenderExpirationTime
- ) {
- // This is an interruption. (Used for performance tracking.)
- interruptedBy = fiber;
- resetStack();
- }
- markPendingPriorityLevel(root, expirationTime);
- if (
- // If we're in the render phase, we don't need to schedule this root
- // for an update, because we'll do it before we exit...
- !isWorking ||
- isCommitting ||
- // ...unless this is a different root than the one we're rendering.
- nextRoot !== root
- ) {
- const rootExpirationTime = root.expirationTime;
- requestWork(root, rootExpirationTime);
- }
- if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
- // Reset this back to zero so subsequent updates don't throw.
- nestedUpdateCount = 0;
- invariant(
- false,
- 'Maximum update depth exceeded. This can happen when a ' +
- 'component repeatedly calls setState inside ' +
- 'componentWillUpdate or componentDidUpdate. React limits ' +
- 'the number of nested updates to prevent infinite loops.',
- );
- }
- if (__DEV__) {
- if (
- isInPassiveEffectDEV &&
- nestedPassiveEffectCountDEV > NESTED_PASSIVE_UPDATE_LIMIT
- ) {
- nestedPassiveEffectCountDEV = 0;
- warning(
- false,
- 'Maximum update depth exceeded. This can happen when a ' +
- 'component calls setState inside useEffect, but ' +
- "useEffect either doesn't have a dependency array, or " +
- 'one of the dependencies changes on every render.',
- );
- }
- }
-}
-
-function deferredUpdates(fn: () => A): A {
- const currentTime = requestCurrentTime();
- const previousExpirationContext = expirationContext;
- const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
- expirationContext = computeAsyncExpiration(currentTime);
- isBatchingInteractiveUpdates = false;
- try {
- return fn();
- } finally {
- expirationContext = previousExpirationContext;
- isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
- }
-}
-
-function syncUpdates(
- fn: (A, B, C0, D) => R,
- a: A,
- b: B,
- c: C0,
- d: D,
-): R {
- const previousExpirationContext = expirationContext;
- expirationContext = Sync;
- try {
- return fn(a, b, c, d);
- } finally {
- expirationContext = previousExpirationContext;
- }
-}
-
-// TODO: Everything below this is written as if it has been lifted to the
-// renderers. I'll do this in a follow-up.
-
-// Linked-list of roots
-let firstScheduledRoot: FiberRoot | null = null;
-let lastScheduledRoot: FiberRoot | null = null;
-
-let callbackExpirationTime: ExpirationTime = NoWork;
-let callbackID: *;
-let isRendering: boolean = false;
-let nextFlushedRoot: FiberRoot | null = null;
-let nextFlushedExpirationTime: ExpirationTime = NoWork;
-let lowestPriorityPendingInteractiveExpirationTime: ExpirationTime = NoWork;
-let hasUnhandledError: boolean = false;
-let unhandledError: mixed | null = null;
-
-let isBatchingUpdates: boolean = false;
-let isUnbatchingUpdates: boolean = false;
-let isBatchingInteractiveUpdates: boolean = false;
-
-let completedBatches: Array | null = null;
-
-let originalStartTimeMs: number = now();
-let currentRendererTime: ExpirationTime = msToExpirationTime(
- originalStartTimeMs,
-);
-let currentSchedulerTime: ExpirationTime = currentRendererTime;
-
-// Use these to prevent an infinite loop of nested updates
-const NESTED_UPDATE_LIMIT = 50;
-let nestedUpdateCount: number = 0;
-let lastCommittedRootDuringThisBatch: FiberRoot | null = null;
-
-// Similar, but for useEffect infinite loops. These are DEV-only.
-const NESTED_PASSIVE_UPDATE_LIMIT = 50;
-let nestedPassiveEffectCountDEV;
-let isInPassiveEffectDEV;
-if (__DEV__) {
- nestedPassiveEffectCountDEV = 0;
- isInPassiveEffectDEV = false;
-}
-
-function recomputeCurrentRendererTime() {
- const currentTimeMs = now() - originalStartTimeMs;
- currentRendererTime = msToExpirationTime(currentTimeMs);
-}
-
-function scheduleCallbackWithExpirationTime(
- root: FiberRoot,
- expirationTime: ExpirationTime,
-) {
- if (callbackExpirationTime !== NoWork) {
- // A callback is already scheduled. Check its expiration time (timeout).
- if (expirationTime < callbackExpirationTime) {
- // Existing callback has sufficient timeout. Exit.
- return;
- } else {
- if (callbackID !== null) {
- // Existing callback has insufficient timeout. Cancel and schedule a
- // new one.
- cancelCallback(callbackID);
- }
- }
- // The request callback timer is already running. Don't start a new one.
- } else {
- startRequestCallbackTimer();
- }
-
- callbackExpirationTime = expirationTime;
- const currentMs = now() - originalStartTimeMs;
- const expirationTimeMs = expirationTimeToMs(expirationTime);
- const timeout = expirationTimeMs - currentMs;
- const priorityLevel = getCurrentPriorityLevel();
- callbackID = scheduleCallback(priorityLevel, performAsyncWork, {timeout});
-}
-
-// For every call to renderRoot, one of onFatal, onComplete, onSuspend, and
-// onYield is called upon exiting. We use these in lieu of returning a tuple.
-// I've also chosen not to inline them into renderRoot because these will
-// eventually be lifted into the renderer.
-function onFatal(root) {
- root.finishedWork = null;
-}
-
-function onComplete(
- root: FiberRoot,
- finishedWork: Fiber,
- expirationTime: ExpirationTime,
-) {
- root.pendingCommitExpirationTime = expirationTime;
- root.finishedWork = finishedWork;
-}
-
-function onSuspend(
- root: FiberRoot,
- finishedWork: Fiber,
- suspendedExpirationTime: ExpirationTime,
- rootExpirationTime: ExpirationTime,
- msUntilTimeout: number,
-): void {
- root.expirationTime = rootExpirationTime;
- if (msUntilTimeout === 0 && (disableYielding || !shouldYield())) {
- // Don't wait an additional tick. Commit the tree immediately.
- root.pendingCommitExpirationTime = suspendedExpirationTime;
- root.finishedWork = finishedWork;
- } else if (msUntilTimeout > 0) {
- // Wait `msUntilTimeout` milliseconds before committing.
- root.timeoutHandle = scheduleTimeout(
- onTimeout.bind(null, root, finishedWork, suspendedExpirationTime),
- msUntilTimeout,
- );
- }
-}
-
-function onYield(root) {
- root.finishedWork = null;
-}
-
-function onTimeout(root, finishedWork, suspendedExpirationTime) {
- // The root timed out. Commit it.
- root.pendingCommitExpirationTime = suspendedExpirationTime;
- root.finishedWork = finishedWork;
- // Read the current time before entering the commit phase. We can be
- // certain this won't cause tearing related to batching of event updates
- // because we're at the top of a timer event.
- recomputeCurrentRendererTime();
- currentSchedulerTime = currentRendererTime;
- flushRoot(root, suspendedExpirationTime);
-}
-
-function onCommit(root, expirationTime) {
- root.expirationTime = expirationTime;
- root.finishedWork = null;
-}
-
-function requestCurrentTime() {
- // requestCurrentTime is called by the scheduler to compute an expiration
- // time.
- //
- // Expiration times are computed by adding to the current time (the start
- // time). However, if two updates are scheduled within the same event, we
- // should treat their start times as simultaneous, even if the actual clock
- // time has advanced between the first and second call.
-
- // In other words, because expiration times determine how updates are batched,
- // we want all updates of like priority that occur within the same event to
- // receive the same expiration time. Otherwise we get tearing.
- //
- // We keep track of two separate times: the current "renderer" time and the
- // current "scheduler" time. The renderer time can be updated whenever; it
- // only exists to minimize the calls performance.now.
- //
- // But the scheduler time can only be updated if there's no pending work, or
- // if we know for certain that we're not in the middle of an event.
-
- if (isRendering) {
- // We're already rendering. Return the most recently read time.
- return currentSchedulerTime;
- }
- // Check if there's pending work.
- findHighestPriorityRoot();
- if (
- nextFlushedExpirationTime === NoWork ||
- nextFlushedExpirationTime === Never
- ) {
- // If there's no pending work, or if the pending work is offscreen, we can
- // read the current time without risk of tearing.
- recomputeCurrentRendererTime();
- currentSchedulerTime = currentRendererTime;
- return currentSchedulerTime;
- }
- // There's already pending work. We might be in the middle of a browser
- // event. If we were to read the current time, it could cause multiple updates
- // within the same event to receive different expiration times, leading to
- // tearing. Return the last read time. During the next idle callback, the
- // time will be updated.
- return currentSchedulerTime;
-}
-
-// requestWork is called by the scheduler whenever a root receives an update.
-// It's up to the renderer to call renderRoot at some point in the future.
-function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
- addRootToSchedule(root, expirationTime);
- if (isRendering) {
- // Prevent reentrancy. Remaining work will be scheduled at the end of
- // the currently rendering batch.
- return;
- }
-
- if (isBatchingUpdates) {
- // Flush work at the end of the batch.
- if (isUnbatchingUpdates) {
- // ...unless we're inside unbatchedUpdates, in which case we should
- // flush it now.
- nextFlushedRoot = root;
- nextFlushedExpirationTime = Sync;
- performWorkOnRoot(root, Sync, false);
- }
- return;
- }
-
- // TODO: Get rid of Sync and use current time?
- if (expirationTime === Sync) {
- performSyncWork();
- } else {
- scheduleCallbackWithExpirationTime(root, expirationTime);
- }
-}
-
-function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
- // Add the root to the schedule.
- // Check if this root is already part of the schedule.
- if (root.nextScheduledRoot === null) {
- // This root is not already scheduled. Add it.
- root.expirationTime = expirationTime;
- if (lastScheduledRoot === null) {
- firstScheduledRoot = lastScheduledRoot = root;
- root.nextScheduledRoot = root;
- } else {
- lastScheduledRoot.nextScheduledRoot = root;
- lastScheduledRoot = root;
- lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
- }
- } else {
- // This root is already scheduled, but its priority may have increased.
- const remainingExpirationTime = root.expirationTime;
- if (expirationTime > remainingExpirationTime) {
- // Update the priority.
- root.expirationTime = expirationTime;
- }
- }
-}
-
-function findHighestPriorityRoot() {
- let highestPriorityWork = NoWork;
- let highestPriorityRoot = null;
- if (lastScheduledRoot !== null) {
- let previousScheduledRoot = lastScheduledRoot;
- let root = firstScheduledRoot;
- while (root !== null) {
- const remainingExpirationTime = root.expirationTime;
- if (remainingExpirationTime === NoWork) {
- // This root no longer has work. Remove it from the scheduler.
-
- // TODO: This check is redudant, but Flow is confused by the branch
- // below where we set lastScheduledRoot to null, even though we break
- // from the loop right after.
- invariant(
- previousScheduledRoot !== null && lastScheduledRoot !== null,
- 'Should have a previous and last root. This error is likely ' +
- 'caused by a bug in React. Please file an issue.',
- );
- if (root === root.nextScheduledRoot) {
- // This is the only root in the list.
- root.nextScheduledRoot = null;
- firstScheduledRoot = lastScheduledRoot = null;
- break;
- } else if (root === firstScheduledRoot) {
- // This is the first root in the list.
- const next = root.nextScheduledRoot;
- firstScheduledRoot = next;
- lastScheduledRoot.nextScheduledRoot = next;
- root.nextScheduledRoot = null;
- } else if (root === lastScheduledRoot) {
- // This is the last root in the list.
- lastScheduledRoot = previousScheduledRoot;
- lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
- root.nextScheduledRoot = null;
- break;
- } else {
- previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
- root.nextScheduledRoot = null;
- }
- root = previousScheduledRoot.nextScheduledRoot;
- } else {
- if (remainingExpirationTime > highestPriorityWork) {
- // Update the priority, if it's higher
- highestPriorityWork = remainingExpirationTime;
- highestPriorityRoot = root;
- }
- if (root === lastScheduledRoot) {
- break;
- }
- if (highestPriorityWork === Sync) {
- // Sync is highest priority by definition so
- // we can stop searching.
- break;
- }
- previousScheduledRoot = root;
- root = root.nextScheduledRoot;
- }
- }
- }
-
- nextFlushedRoot = highestPriorityRoot;
- nextFlushedExpirationTime = highestPriorityWork;
-}
-
-function performAsyncWork(didTimeout) {
- if (didTimeout) {
- // The callback timed out. That means at least one update has expired.
- // Iterate through the root schedule. If they contain expired work, set
- // the next render expiration time to the current time. This has the effect
- // of flushing all expired work in a single batch, instead of flushing each
- // level one at a time.
- if (firstScheduledRoot !== null) {
- recomputeCurrentRendererTime();
- let root: FiberRoot = firstScheduledRoot;
- do {
- didExpireAtExpirationTime(root, currentRendererTime);
- // The root schedule is circular, so this is never null.
- root = (root.nextScheduledRoot: any);
- } while (root !== firstScheduledRoot);
- }
- }
-
- // Keep working on roots until there's no more work, or until there's a higher
- // priority event.
- findHighestPriorityRoot();
-
- if (disableYielding) {
- // Just do it all
- while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork) {
- performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
- findHighestPriorityRoot();
- }
- } else {
- recomputeCurrentRendererTime();
- currentSchedulerTime = currentRendererTime;
-
- if (enableUserTimingAPI) {
- const didExpire = nextFlushedExpirationTime > currentRendererTime;
- const timeout = expirationTimeToMs(nextFlushedExpirationTime);
- stopRequestCallbackTimer(didExpire, timeout);
- }
-
- while (
- nextFlushedRoot !== null &&
- nextFlushedExpirationTime !== NoWork &&
- !(shouldYield() && currentRendererTime > nextFlushedExpirationTime)
- ) {
- performWorkOnRoot(
- nextFlushedRoot,
- nextFlushedExpirationTime,
- currentRendererTime > nextFlushedExpirationTime,
- );
- findHighestPriorityRoot();
- recomputeCurrentRendererTime();
- currentSchedulerTime = currentRendererTime;
- }
- }
-
- // We're done flushing work. Either we ran out of time in this callback,
- // or there's no more work left with sufficient priority.
-
- // If we're inside a callback, set this to false since we just completed it.
- callbackExpirationTime = NoWork;
- callbackID = null;
-
- // If there's work left over, schedule a new callback.
- if (nextFlushedExpirationTime !== NoWork) {
- scheduleCallbackWithExpirationTime(
- ((nextFlushedRoot: any): FiberRoot),
- nextFlushedExpirationTime,
- );
- }
-
- // Clean-up.
- finishRendering();
-}
-
-function performSyncWork() {
- performWork(Sync);
-}
-
-function performWork(minExpirationTime: ExpirationTime) {
- // Keep working on roots until there's no more work, or until there's a higher
- // priority event.
- findHighestPriorityRoot();
-
- while (
- nextFlushedRoot !== null &&
- nextFlushedExpirationTime !== NoWork &&
- minExpirationTime <= nextFlushedExpirationTime
- ) {
- performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
- findHighestPriorityRoot();
- }
-
- // We're done flushing work. Either we ran out of time in this callback,
- // or there's no more work left with sufficient priority.
-
- // If there's work left over, schedule a new callback.
- if (nextFlushedExpirationTime !== NoWork) {
- scheduleCallbackWithExpirationTime(
- ((nextFlushedRoot: any): FiberRoot),
- nextFlushedExpirationTime,
- );
- }
-
- // Clean-up.
- finishRendering();
-}
-
-function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
- invariant(
- !isRendering,
- 'work.commit(): Cannot commit while already rendering. This likely ' +
- 'means you attempted to commit from inside a lifecycle method.',
- );
- // Perform work on root as if the given expiration time is the current time.
- // This has the effect of synchronously flushing all work up to and
- // including the given time.
- nextFlushedRoot = root;
- nextFlushedExpirationTime = expirationTime;
- performWorkOnRoot(root, expirationTime, false);
- // Flush any sync work that was scheduled by lifecycles
- performSyncWork();
-}
-
-function finishRendering() {
- nestedUpdateCount = 0;
- lastCommittedRootDuringThisBatch = null;
-
- if (__DEV__) {
- if (rootWithPendingPassiveEffects === null) {
- nestedPassiveEffectCountDEV = 0;
- }
- }
-
- if (completedBatches !== null) {
- const batches = completedBatches;
- completedBatches = null;
- for (let i = 0; i < batches.length; i++) {
- const batch = batches[i];
- try {
- batch._onComplete();
- } catch (error) {
- if (!hasUnhandledError) {
- hasUnhandledError = true;
- unhandledError = error;
- }
- }
- }
- }
-
- if (hasUnhandledError) {
- const error = unhandledError;
- unhandledError = null;
- hasUnhandledError = false;
- throw error;
- }
-}
-
-function performWorkOnRoot(
- root: FiberRoot,
- expirationTime: ExpirationTime,
- isYieldy: boolean,
-) {
- invariant(
- !isRendering,
- 'performWorkOnRoot was called recursively. This error is likely caused ' +
- 'by a bug in React. Please file an issue.',
- );
-
- isRendering = true;
-
- // Check if this is async work or sync/expired work.
- if (!isYieldy) {
- // Flush work without yielding.
- // TODO: Non-yieldy work does not necessarily imply expired work. A renderer
- // may want to perform some work without yielding, but also without
- // requiring the root to complete (by triggering placeholders).
-
- let finishedWork = root.finishedWork;
- if (finishedWork !== null) {
- // This root is already complete. We can commit it.
- completeRoot(root, finishedWork, expirationTime);
- } else {
- root.finishedWork = null;
- // If this root previously suspended, clear its existing timeout, since
- // we're about to try rendering again.
- const timeoutHandle = root.timeoutHandle;
- if (timeoutHandle !== noTimeout) {
- root.timeoutHandle = noTimeout;
- // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
- cancelTimeout(timeoutHandle);
- }
- renderRoot(root, isYieldy);
- finishedWork = root.finishedWork;
- if (finishedWork !== null) {
- // We've completed the root. Commit it.
- completeRoot(root, finishedWork, expirationTime);
- }
- }
- } else {
- // Flush async work.
- let finishedWork = root.finishedWork;
- if (finishedWork !== null) {
- // This root is already complete. We can commit it.
- completeRoot(root, finishedWork, expirationTime);
- } else {
- root.finishedWork = null;
- // If this root previously suspended, clear its existing timeout, since
- // we're about to try rendering again.
- const timeoutHandle = root.timeoutHandle;
- if (timeoutHandle !== noTimeout) {
- root.timeoutHandle = noTimeout;
- // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
- cancelTimeout(timeoutHandle);
- }
- renderRoot(root, isYieldy);
- finishedWork = root.finishedWork;
- if (finishedWork !== null) {
- // We've completed the root. Check the if we should yield one more time
- // before committing.
- if (!shouldYield()) {
- // Still time left. Commit the root.
- completeRoot(root, finishedWork, expirationTime);
- } else {
- // There's no time left. Mark this root as complete. We'll come
- // back and commit it later.
- root.finishedWork = finishedWork;
- }
- }
- }
- }
-
- isRendering = false;
-}
-
-function completeRoot(
- root: FiberRoot,
- finishedWork: Fiber,
- expirationTime: ExpirationTime,
-): void {
- // Check if there's a batch that matches this expiration time.
- const firstBatch = root.firstBatch;
- if (firstBatch !== null && firstBatch._expirationTime >= expirationTime) {
- if (completedBatches === null) {
- completedBatches = [firstBatch];
- } else {
- completedBatches.push(firstBatch);
- }
- if (firstBatch._defer) {
- // This root is blocked from committing by a batch. Unschedule it until
- // we receive another update.
- root.finishedWork = finishedWork;
- root.expirationTime = NoWork;
- return;
- }
- }
-
- // Commit the root.
- root.finishedWork = null;
-
- // Check if this is a nested update (a sync update scheduled during the
- // commit phase).
- if (root === lastCommittedRootDuringThisBatch) {
- // If the next root is the same as the previous root, this is a nested
- // update. To prevent an infinite loop, increment the nested update count.
- nestedUpdateCount++;
- } else {
- // Reset whenever we switch roots.
- lastCommittedRootDuringThisBatch = root;
- nestedUpdateCount = 0;
- }
- commitRoot(root, finishedWork);
-}
-
-function onUncaughtError(error: mixed) {
- invariant(
- nextFlushedRoot !== null,
- 'Should be working on a root. This error is likely caused by a bug in ' +
- 'React. Please file an issue.',
- );
- // Unschedule this root so we don't work on it again until there's
- // another update.
- nextFlushedRoot.expirationTime = NoWork;
- if (!hasUnhandledError) {
- hasUnhandledError = true;
- unhandledError = error;
- }
-}
-
-// TODO: Batching should be implemented at the renderer level, not inside
-// the reconciler.
-function batchedUpdates(fn: (a: A) => R, a: A): R {
- const previousIsBatchingUpdates = isBatchingUpdates;
- isBatchingUpdates = true;
- try {
- return fn(a);
- } finally {
- isBatchingUpdates = previousIsBatchingUpdates;
- if (!isBatchingUpdates && !isRendering) {
- performSyncWork();
- }
- }
-}
-
-// TODO: Batching should be implemented at the renderer level, not inside
-// the reconciler.
-function unbatchedUpdates(fn: (a: A) => R, a: A): R {
- if (isBatchingUpdates && !isUnbatchingUpdates) {
- isUnbatchingUpdates = true;
- try {
- return fn(a);
- } finally {
- isUnbatchingUpdates = false;
- }
- }
- return fn(a);
-}
-
-// TODO: Batching should be implemented at the renderer level, not within
-// the reconciler.
-function flushSync(fn: (a: A) => R, a: A): R {
- invariant(
- !isRendering,
- 'flushSync was called from inside a lifecycle method. It cannot be ' +
- 'called when React is already rendering.',
- );
- const previousIsBatchingUpdates = isBatchingUpdates;
- isBatchingUpdates = true;
- try {
- return syncUpdates(fn, a);
- } finally {
- isBatchingUpdates = previousIsBatchingUpdates;
- performSyncWork();
- }
-}
-
-function interactiveUpdates(
- fn: (A, B, C) => R,
- a: A,
- b: B,
- c: C,
-): R {
- if (isBatchingInteractiveUpdates) {
- return fn(a, b, c);
- }
- // If there are any pending interactive updates, synchronously flush them.
- // This needs to happen before we read any handlers, because the effect of
- // the previous event may influence which handlers are called during
- // this event.
- if (
- !isBatchingUpdates &&
- !isRendering &&
- lowestPriorityPendingInteractiveExpirationTime !== NoWork
- ) {
- // Synchronously flush pending interactive updates.
- performWork(lowestPriorityPendingInteractiveExpirationTime);
- lowestPriorityPendingInteractiveExpirationTime = NoWork;
- }
- const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
- const previousIsBatchingUpdates = isBatchingUpdates;
- isBatchingInteractiveUpdates = true;
- isBatchingUpdates = true;
- try {
- return fn(a, b, c);
- } finally {
- isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
- isBatchingUpdates = previousIsBatchingUpdates;
- if (!isBatchingUpdates && !isRendering) {
- performSyncWork();
- }
- }
-}
-
-function flushInteractiveUpdates() {
- if (
- !isRendering &&
- lowestPriorityPendingInteractiveExpirationTime !== NoWork
- ) {
- // Synchronously flush pending interactive updates.
- performWork(lowestPriorityPendingInteractiveExpirationTime);
- lowestPriorityPendingInteractiveExpirationTime = NoWork;
- }
-}
-
-function flushControlled(fn: () => mixed): void {
- const previousIsBatchingUpdates = isBatchingUpdates;
- isBatchingUpdates = true;
- try {
- syncUpdates(fn);
- } finally {
- isBatchingUpdates = previousIsBatchingUpdates;
- if (!isBatchingUpdates && !isRendering) {
- performSyncWork();
- }
- }
-}
-
-export {
- requestCurrentTime,
- computeExpirationForFiber,
- captureCommitPhaseError,
- onUncaughtError,
- markRenderEventTime,
- renderDidSuspend,
- renderDidError,
- pingSuspendedRoot,
- retryTimedOutBoundary,
- resolveRetryThenable,
- markLegacyErrorBoundaryAsFailed,
- isAlreadyFailedLegacyErrorBoundary,
- scheduleWork,
- flushRoot,
- batchedUpdates,
- unbatchedUpdates,
- flushSync,
- flushControlled,
- deferredUpdates,
- syncUpdates,
- interactiveUpdates,
- flushInteractiveUpdates,
- computeUniqueAsyncExpiration,
- flushPassiveEffects,
-};
diff --git a/packages/react-reconciler/src/SchedulerWithReactIntegration.js b/packages/react-reconciler/src/SchedulerWithReactIntegration.js
index 323ab5528fdbf..3a30a2ea2e48a 100644
--- a/packages/react-reconciler/src/SchedulerWithReactIntegration.js
+++ b/packages/react-reconciler/src/SchedulerWithReactIntegration.js
@@ -10,8 +10,11 @@
// Intentionally not named imports because Rollup would use dynamic dispatch for
// CommonJS interop named imports.
import * as Scheduler from 'scheduler';
-
-import {disableYielding} from 'shared/ReactFeatureFlags';
+import {__interactionsRef} from 'scheduler/tracing';
+import {
+ disableYielding,
+ enableSchedulerTracing,
+} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
const {
@@ -28,6 +31,20 @@ const {
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;
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
index 4d6ddee647149..6fe7fc093663c 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
@@ -15,7 +15,6 @@ let ReactFeatureFlags;
let React;
let ReactNoop;
let Scheduler;
-let enableNewScheduler;
describe('ReactIncrementalErrorHandling', () => {
beforeEach(() => {
@@ -23,7 +22,6 @@ describe('ReactIncrementalErrorHandling', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
PropTypes = require('prop-types');
React = require('react');
ReactNoop = require('react-noop-renderer');
@@ -1034,23 +1032,15 @@ describe('ReactIncrementalErrorHandling', () => {
ReactNoop.renderToRootWithID(, 'e');
ReactNoop.renderToRootWithID(, 'f');
- if (enableNewScheduler) {
- // The new scheduler will throw all three errors.
- expect(() => {
- expect(Scheduler).toFlushWithoutYielding();
- }).toThrow('a');
- expect(() => {
- expect(Scheduler).toFlushWithoutYielding();
- }).toThrow('c');
- expect(() => {
- expect(Scheduler).toFlushWithoutYielding();
- }).toThrow('e');
- } else {
- // The old scheduler only throws the first one.
- expect(() => {
- expect(Scheduler).toFlushWithoutYielding();
- }).toThrow('a');
- }
+ expect(() => {
+ expect(Scheduler).toFlushWithoutYielding();
+ }).toThrow('a');
+ expect(() => {
+ expect(Scheduler).toFlushWithoutYielding();
+ }).toThrow('c');
+ expect(() => {
+ expect(Scheduler).toFlushWithoutYielding();
+ }).toThrow('e');
expect(Scheduler).toFlushWithoutYielding();
expect(ReactNoop.getChildren('a')).toEqual([]);
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
index 1c6bf0f6b733c..2080cbc17a23b 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
@@ -118,568 +118,554 @@ describe('ReactDebugFiberPerf', () => {
return {props.children}
;
}
- describe('old scheduler', () => {
- runTests(false);
+ beforeEach(() => {
+ jest.resetModules();
+ resetFlamechart();
+ global.performance = createUserTimingPolyfill();
+
+ require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
+ require('shared/ReactFeatureFlags').enableProfilerTimer = false;
+ require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
+ require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
+
+ // Import after the polyfill is set up:
+ React = require('react');
+ ReactNoop = require('react-noop-renderer');
+ Scheduler = require('scheduler');
+ PropTypes = require('prop-types');
});
- describe('new scheduler', () => {
- runTests(true);
+ afterEach(() => {
+ delete global.performance;
});
- function runTests(enableNewScheduler) {
- beforeEach(() => {
- jest.resetModules();
- resetFlamechart();
- global.performance = createUserTimingPolyfill();
-
- require('shared/ReactFeatureFlags').enableNewScheduler = enableNewScheduler;
- require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
- require('shared/ReactFeatureFlags').enableProfilerTimer = false;
- require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
- require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
-
- // Import after the polyfill is set up:
- React = require('react');
- ReactNoop = require('react-noop-renderer');
- Scheduler = require('scheduler');
- PropTypes = require('prop-types');
- });
+ it('measures a simple reconciliation', () => {
+ ReactNoop.render(
+
+
+ ,
+ );
+ addComment('Mount');
+ expect(Scheduler).toFlushWithoutYielding();
+
+ ReactNoop.render(
+
+
+ ,
+ );
+ addComment('Update');
+ expect(Scheduler).toFlushWithoutYielding();
+
+ ReactNoop.render(null);
+ addComment('Unmount');
+ expect(Scheduler).toFlushWithoutYielding();
+
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- afterEach(() => {
- delete global.performance;
+ it('properly displays the forwardRef component in measurements', () => {
+ const AnonymousForwardRef = React.forwardRef((props, ref) => (
+
+ ));
+ const NamedForwardRef = React.forwardRef(function refForwarder(props, ref) {
+ return ;
});
+ function notImportant(props, ref) {
+ return ;
+ }
+ notImportant.displayName = 'OverriddenName';
+ const DisplayNamedForwardRef = React.forwardRef(notImportant);
+
+ ReactNoop.render(
+
+
+
+
+ ,
+ );
+ addComment('Mount');
+ expect(Scheduler).toFlushWithoutYielding();
+
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('measures a simple reconciliation', () => {
- ReactNoop.render(
-
-
- ,
- );
- addComment('Mount');
- expect(Scheduler).toFlushWithoutYielding();
-
- ReactNoop.render(
-
-
- ,
- );
- addComment('Update');
- expect(Scheduler).toFlushWithoutYielding();
-
- ReactNoop.render(null);
- addComment('Unmount');
- expect(Scheduler).toFlushWithoutYielding();
+ it('does not include ConcurrentMode, StrictMode, or Profiler components in measurements', () => {
+ ReactNoop.render(
+
+
+
+
+
+
+
+
+ ,
+ );
+ addComment('Mount');
+ expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('properly displays the forwardRef component in measurements', () => {
- const AnonymousForwardRef = React.forwardRef((props, ref) => (
-
- ));
- const NamedForwardRef = React.forwardRef(function refForwarder(
- props,
- ref,
- ) {
- return ;
- });
- function notImportant(props, ref) {
- return ;
- }
- notImportant.displayName = 'OverriddenName';
- const DisplayNamedForwardRef = React.forwardRef(notImportant);
+ it('does not include context provider or consumer in measurements', () => {
+ const {Consumer, Provider} = React.createContext(true);
- ReactNoop.render(
+ ReactNoop.render(
+
-
-
-
- ,
- );
- addComment('Mount');
- expect(Scheduler).toFlushWithoutYielding();
-
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('does not include ConcurrentMode, StrictMode, or Profiler components in measurements', () => {
- ReactNoop.render(
-
-
-
-
-
-
-
-
- ,
- );
- addComment('Mount');
- expect(Scheduler).toFlushWithoutYielding();
-
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('does not include context provider or consumer in measurements', () => {
- const {Consumer, Provider} = React.createContext(true);
-
- ReactNoop.render(
-
-
- {value => }
-
- ,
- );
- addComment('Mount');
- expect(Scheduler).toFlushWithoutYielding();
-
- expect(getFlameChart()).toMatchSnapshot();
- });
+ {value => }
+
+ ,
+ );
+ addComment('Mount');
+ expect(Scheduler).toFlushWithoutYielding();
+
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('skips parents during setState', () => {
- class A extends React.Component {
- render() {
- return {this.props.children}
;
- }
+ it('skips parents during setState', () => {
+ class A extends React.Component {
+ render() {
+ return {this.props.children}
;
}
+ }
- class B extends React.Component {
- render() {
- return {this.props.children}
;
- }
+ class B extends React.Component {
+ render() {
+ return {this.props.children}
;
}
+ }
- let a;
- let b;
- ReactNoop.render(
+ let a;
+ let b;
+ ReactNoop.render(
+
-
- (a = inst)} />
-
-
-
- (b = inst)} />
+ (a = inst)} />
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- resetFlamechart();
-
- a.setState({});
- b.setState({});
- addComment('Should include just A and B, no Parents');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('warns on cascading renders from setState', () => {
- class Cascading extends React.Component {
- componentDidMount() {
- this.setState({});
- }
- render() {
- return {this.props.children}
;
- }
- }
-
- ReactNoop.render(
+
-
- ,
- );
- addComment('Should print a warning');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ (b = inst)} />
+
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ resetFlamechart();
+
+ a.setState({});
+ b.setState({});
+ addComment('Should include just A and B, no Parents');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('warns on cascading renders from top-level render', () => {
- class Cascading extends React.Component {
- componentDidMount() {
- ReactNoop.renderToRootWithID(, 'b');
- addComment('Scheduling another root from componentDidMount');
- }
- render() {
- return {this.props.children}
;
- }
+ it('warns on cascading renders from setState', () => {
+ class Cascading extends React.Component {
+ componentDidMount() {
+ this.setState({});
}
-
- ReactNoop.renderToRootWithID(, 'a');
- addComment('Rendering the first root');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('does not treat setState from cWM or cWRP as cascading', () => {
- class NotCascading extends React.Component {
- UNSAFE_componentWillMount() {
- this.setState({});
- }
- UNSAFE_componentWillReceiveProps() {
- this.setState({});
- }
- render() {
- return {this.props.children}
;
- }
+ render() {
+ return {this.props.children}
;
}
+ }
+
+ ReactNoop.render(
+
+
+ ,
+ );
+ addComment('Should print a warning');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- ReactNoop.render(
-
-
- ,
- );
- addComment('Should not print a warning');
- expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
- [
- 'componentWillMount: Please update the following components ' +
- 'to use componentDidMount instead: NotCascading' +
- '\n\ncomponentWillReceiveProps: Please update the following components ' +
- 'to use static getDerivedStateFromProps instead: NotCascading',
- ],
- {withoutStack: true},
- );
- ReactNoop.render(
-
-
- ,
- );
- addComment('Should not print a warning');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('captures all lifecycles', () => {
- class AllLifecycles extends React.Component {
- static childContextTypes = {
- foo: PropTypes.any,
- };
- shouldComponentUpdate() {
- return true;
- }
- getChildContext() {
- return {foo: 42};
- }
- UNSAFE_componentWillMount() {}
- componentDidMount() {}
- UNSAFE_componentWillReceiveProps() {}
- UNSAFE_componentWillUpdate() {}
- componentDidUpdate() {}
- componentWillUnmount() {}
- render() {
- return ;
- }
+ it('warns on cascading renders from top-level render', () => {
+ class Cascading extends React.Component {
+ componentDidMount() {
+ ReactNoop.renderToRootWithID(, 'b');
+ addComment('Scheduling another root from componentDidMount');
}
- ReactNoop.render();
- addComment('Mount');
- expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
- [
- 'componentWillMount: Please update the following components ' +
- 'to use componentDidMount instead: AllLifecycles' +
- '\n\ncomponentWillReceiveProps: Please update the following components ' +
- 'to use static getDerivedStateFromProps instead: AllLifecycles' +
- '\n\ncomponentWillUpdate: Please update the following components ' +
- 'to use componentDidUpdate instead: AllLifecycles',
- 'Legacy context API has been detected within a strict-mode tree: \n\n' +
- 'Please update the following components: AllLifecycles',
- ],
- {withoutStack: true},
- );
- ReactNoop.render();
- addComment('Update');
- expect(Scheduler).toFlushWithoutYielding();
- ReactNoop.render(null);
- addComment('Unmount');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ render() {
+ return {this.props.children}
;
+ }
+ }
- it('measures deprioritized work', () => {
- addComment('Flush the parent');
- ReactNoop.flushSync(() => {
- ReactNoop.render(
-
-
-
-
- ,
- );
- });
- addComment('Flush the child');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ ReactNoop.renderToRootWithID(, 'a');
+ addComment('Rendering the first root');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('measures deferred work in chunks', () => {
- class A extends React.Component {
- render() {
- Scheduler.yieldValue('A');
- return {this.props.children}
;
- }
+ it('does not treat setState from cWM or cWRP as cascading', () => {
+ class NotCascading extends React.Component {
+ UNSAFE_componentWillMount() {
+ this.setState({});
}
-
- class B extends React.Component {
- render() {
- Scheduler.yieldValue('B');
- return {this.props.children}
;
- }
+ UNSAFE_componentWillReceiveProps() {
+ this.setState({});
}
+ render() {
+ return {this.props.children}
;
+ }
+ }
+
+ ReactNoop.render(
+
+
+ ,
+ );
+ addComment('Should not print a warning');
+ expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
+ [
+ 'componentWillMount: Please update the following components ' +
+ 'to use componentDidMount instead: NotCascading' +
+ '\n\ncomponentWillReceiveProps: Please update the following components ' +
+ 'to use static getDerivedStateFromProps instead: NotCascading',
+ ],
+ {withoutStack: true},
+ );
+ ReactNoop.render(
+
+
+ ,
+ );
+ addComment('Should not print a warning');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- class C extends React.Component {
- render() {
- Scheduler.yieldValue('C');
- return {this.props.children}
;
- }
+ it('captures all lifecycles', () => {
+ class AllLifecycles extends React.Component {
+ static childContextTypes = {
+ foo: PropTypes.any,
+ };
+ shouldComponentUpdate() {
+ return true;
}
+ getChildContext() {
+ return {foo: 42};
+ }
+ UNSAFE_componentWillMount() {}
+ componentDidMount() {}
+ UNSAFE_componentWillReceiveProps() {}
+ UNSAFE_componentWillUpdate() {}
+ componentDidUpdate() {}
+ componentWillUnmount() {}
+ render() {
+ return ;
+ }
+ }
+ ReactNoop.render();
+ addComment('Mount');
+ expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
+ [
+ 'componentWillMount: Please update the following components ' +
+ 'to use componentDidMount instead: AllLifecycles' +
+ '\n\ncomponentWillReceiveProps: Please update the following components ' +
+ 'to use static getDerivedStateFromProps instead: AllLifecycles' +
+ '\n\ncomponentWillUpdate: Please update the following components ' +
+ 'to use componentDidUpdate instead: AllLifecycles',
+ 'Legacy context API has been detected within a strict-mode tree: \n\n' +
+ 'Please update the following components: AllLifecycles',
+ ],
+ {withoutStack: true},
+ );
+ ReactNoop.render();
+ addComment('Update');
+ expect(Scheduler).toFlushWithoutYielding();
+ ReactNoop.render(null);
+ addComment('Unmount');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
+ it('measures deprioritized work', () => {
+ addComment('Flush the parent');
+ ReactNoop.flushSync(() => {
ReactNoop.render(
-
+
-
-
-
-
-
-
-
+
,
);
- addComment('Start rendering through B');
- expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
- addComment('Complete the rest');
- expect(Scheduler).toFlushAndYield(['C']);
- expect(getFlameChart()).toMatchSnapshot();
});
+ addComment('Flush the child');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('recovers from fatal errors', () => {
- function Baddie() {
- throw new Error('Game over');
- }
-
- ReactNoop.render(
-
-
- ,
- );
- try {
- addComment('Will fatal');
- expect(Scheduler).toFlushWithoutYielding();
- } catch (err) {
- expect(err.message).toBe('Game over');
+ it('measures deferred work in chunks', () => {
+ class A extends React.Component {
+ render() {
+ Scheduler.yieldValue('A');
+ return {this.props.children}
;
}
- ReactNoop.render(
-
-
- ,
- );
- addComment('Will reconcile from a clean state');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ }
- it('recovers from caught errors', () => {
- function Baddie() {
- throw new Error('Game over');
+ class B extends React.Component {
+ render() {
+ Scheduler.yieldValue('B');
+ return {this.props.children}
;
}
+ }
- function ErrorReport() {
- return ;
+ class C extends React.Component {
+ render() {
+ Scheduler.yieldValue('C');
+ return {this.props.children}
;
}
+ }
- class Boundary extends React.Component {
- state = {error: null};
- componentDidCatch(error) {
- this.setState({error});
- }
- render() {
- if (this.state.error) {
- return ;
- }
- return this.props.children;
- }
- }
+ ReactNoop.render(
+
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ addComment('Start rendering through B');
+ expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
+ addComment('Complete the rest');
+ expect(Scheduler).toFlushAndYield(['C']);
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- ReactNoop.render(
-
-
-
-
-
-
- ,
- );
- addComment('Stop on Baddie and restart from Boundary');
+ it('recovers from fatal errors', () => {
+ function Baddie() {
+ throw new Error('Game over');
+ }
+
+ ReactNoop.render(
+
+
+ ,
+ );
+ try {
+ addComment('Will fatal');
expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ } catch (err) {
+ expect(err.message).toBe('Game over');
+ }
+ ReactNoop.render(
+
+
+ ,
+ );
+ addComment('Will reconcile from a clean state');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('deduplicates lifecycle names during commit to reduce overhead', () => {
- class A extends React.Component {
- componentDidUpdate() {}
- render() {
- return ;
- }
- }
+ it('recovers from caught errors', () => {
+ function Baddie() {
+ throw new Error('Game over');
+ }
- class B extends React.Component {
- componentDidUpdate(prevProps) {
- if (this.props.cascade && !prevProps.cascade) {
- this.setState({});
- }
- }
- render() {
- return ;
+ function ErrorReport() {
+ return ;
+ }
+
+ class Boundary extends React.Component {
+ state = {error: null};
+ componentDidCatch(error) {
+ this.setState({error});
+ }
+ render() {
+ if (this.state.error) {
+ return ;
}
+ return this.props.children;
}
+ }
- ReactNoop.render(
-
-
-
-
-
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- resetFlamechart();
-
- ReactNoop.render(
-
-
-
-
-
- ,
- );
- addComment('The commit phase should mention A and B just once');
- expect(Scheduler).toFlushWithoutYielding();
- ReactNoop.render(
-
-
-
-
-
- ,
- );
- addComment("Because of deduplication, we don't know B was cascading,");
- addComment('but we should still see the warning for the commit phase.');
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('supports portals', () => {
- const portalContainer = ReactNoop.getOrCreateRootContainer(
- 'portalContainer',
- );
- ReactNoop.render(
-
- {ReactNoop.createPortal(, portalContainer, null)}
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ ReactNoop.render(
+
+
+
+
+
+
+ ,
+ );
+ addComment('Stop on Baddie and restart from Boundary');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('supports memo', () => {
- const MemoFoo = React.memo(function Foo() {
+ it('deduplicates lifecycle names during commit to reduce overhead', () => {
+ class A extends React.Component {
+ componentDidUpdate() {}
+ render() {
return ;
- });
- ReactNoop.render(
-
-
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
-
- it('supports Suspense and lazy', async () => {
- function Spinner() {
- return ;
}
+ }
- function fakeImport(result) {
- return {default: result};
+ class B extends React.Component {
+ componentDidUpdate(prevProps) {
+ if (this.props.cascade && !prevProps.cascade) {
+ this.setState({});
+ }
+ }
+ render() {
+ return ;
}
+ }
+
+ ReactNoop.render(
+
+
+
+
+
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ resetFlamechart();
+
+ ReactNoop.render(
+
+
+
+
+
+ ,
+ );
+ addComment('The commit phase should mention A and B just once');
+ expect(Scheduler).toFlushWithoutYielding();
+ ReactNoop.render(
+
+
+
+
+
+ ,
+ );
+ addComment("Because of deduplication, we don't know B was cascading,");
+ addComment('but we should still see the warning for the commit phase.');
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- let resolve;
- const LazyFoo = React.lazy(
- () =>
- new Promise(r => {
- resolve = r;
- }),
- );
+ it('supports portals', () => {
+ const portalContainer = ReactNoop.getOrCreateRootContainer(
+ 'portalContainer',
+ );
+ ReactNoop.render(
+
+ {ReactNoop.createPortal(, portalContainer, null)}
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- ReactNoop.render(
-
- }>
-
-
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
+ it('supports memo', () => {
+ const MemoFoo = React.memo(function Foo() {
+ return ;
+ });
+ ReactNoop.render(
+
+
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- resolve(
- fakeImport(function Foo() {
- return ;
- }),
- );
+ it('supports Suspense and lazy', async () => {
+ function Spinner() {
+ return ;
+ }
- await Promise.resolve();
+ function fakeImport(result) {
+ return {default: result};
+ }
- ReactNoop.render(
-
-
-
-
- ,
- );
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
+ let resolve;
+ const LazyFoo = React.lazy(
+ () =>
+ new Promise(r => {
+ resolve = r;
+ }),
+ );
+
+ ReactNoop.render(
+
+ }>
+
+
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+
+ resolve(
+ fakeImport(function Foo() {
+ return ;
+ }),
+ );
+
+ await Promise.resolve();
+
+ ReactNoop.render(
+
+
+
+
+ ,
+ );
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
- class Component extends React.Component {
- state = {step: 1};
- componentDidMount() {
- this.setState({step: 2});
- }
- render() {
- return ;
- }
+ it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
+ class Component extends React.Component {
+ state = {step: 1};
+ componentDidMount() {
+ this.setState({step: 2});
+ }
+ render() {
+ return ;
}
- ReactNoop.flushSync(() => {
- ReactNoop.render();
- });
- expect(getFlameChart()).toMatchSnapshot();
+ }
+ ReactNoop.flushSync(() => {
+ ReactNoop.render();
});
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('warns if an in-progress update is interrupted', () => {
- function Foo() {
- Scheduler.yieldValue('Foo');
- return ;
- }
+ it('warns if an in-progress update is interrupted', () => {
+ function Foo() {
+ Scheduler.yieldValue('Foo');
+ return ;
+ }
+ ReactNoop.render();
+ ReactNoop.flushNextYield();
+ ReactNoop.flushSync(() => {
ReactNoop.render();
- ReactNoop.flushNextYield();
- ReactNoop.flushSync(() => {
- ReactNoop.render();
- });
- expect(Scheduler).toHaveYielded(['Foo']);
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
});
+ expect(Scheduler).toHaveYielded(['Foo']);
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
- it('warns if async work expires (starvation)', () => {
- function Foo() {
- return ;
- }
+ it('warns if async work expires (starvation)', () => {
+ function Foo() {
+ return ;
+ }
- ReactNoop.render();
- ReactNoop.expire(6000);
- expect(Scheduler).toFlushWithoutYielding();
- expect(getFlameChart()).toMatchSnapshot();
- });
- }
+ ReactNoop.render();
+ ReactNoop.expire(6000);
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
});
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index bacabb906c581..195f98fd7a53a 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -5,7 +5,6 @@ let Scheduler;
let ReactFeatureFlags;
let Suspense;
let lazy;
-let enableNewScheduler;
describe('ReactLazy', () => {
beforeEach(() => {
@@ -19,7 +18,6 @@ describe('ReactLazy', () => {
lazy = React.lazy;
ReactTestRenderer = require('react-test-renderer');
Scheduler = require('scheduler');
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
});
function Text(props) {
@@ -487,13 +485,7 @@ describe('ReactLazy', () => {
await Promise.resolve();
- if (enableNewScheduler) {
- // The new scheduler pings in a separate task
- expect(Scheduler).toHaveYielded([]);
- } else {
- // The old scheduler pings synchronously
- expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A1']);
- }
+ expect(Scheduler).toHaveYielded([]);
root.update(
}>
@@ -501,19 +493,7 @@ describe('ReactLazy', () => {
,
);
- if (enableNewScheduler) {
- // Because this ping happens in a new task, the ping and the update
- // are batched together
- expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A2']);
- } else {
- // The old scheduler must do two separate renders, no batching.
- expect(Scheduler).toHaveYielded([
- 'UNSAFE_componentWillReceiveProps: A -> A',
- 'UNSAFE_componentWillUpdate: A -> A',
- 'A2',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A2']);
expect(root).toMatchRenderedOutput('A2');
root.update(
diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js
index 153d93692a612..d558a578a20b5 100644
--- a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js
@@ -26,7 +26,6 @@ describe('ReactSchedulerIntegration', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
- ReactFeatureFlags.enableNewScheduler = true;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 1a23bdf09303a..18d477d839860 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -5,7 +5,6 @@ let Scheduler;
let ReactCache;
let Suspense;
let act;
-let enableNewScheduler;
let TextResource;
let textResourceShouldFail;
@@ -23,7 +22,6 @@ describe('ReactSuspense', () => {
act = ReactTestRenderer.act;
Scheduler = require('scheduler');
ReactCache = require('react-cache');
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
Suspense = React.Suspense;
@@ -267,11 +265,7 @@ describe('ReactSuspense', () => {
await LazyClass;
- if (enableNewScheduler) {
- expect(Scheduler).toFlushExpired(['Hi', 'Did mount: Hi']);
- } else {
- expect(Scheduler).toHaveYielded(['Hi', 'Did mount: Hi']);
- }
+ expect(Scheduler).toFlushExpired(['Hi', 'Did mount: Hi']);
expect(root).toMatchRenderedOutput('Hi');
});
@@ -400,24 +394,13 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
- expect(Scheduler).toFlushExpired([
- 'B:1',
- 'Unmount [Loading...]',
- // Should be a mount, not an update
- 'Mount [B:1]',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [B:1]',
- 'B:1',
- 'Unmount [Loading...]',
- // Should be a mount, not an update
- 'Mount [B:1]',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
+ expect(Scheduler).toFlushExpired([
+ 'B:1',
+ 'Unmount [Loading...]',
+ // Should be a mount, not an update
+ 'Mount [B:1]',
+ ]);
expect(root).toMatchRenderedOutput('AB:1C');
instance.setState({step: 2});
@@ -430,21 +413,12 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
- expect(Scheduler).toFlushExpired([
- 'B:2',
- 'Unmount [Loading...]',
- 'Update [B:2]',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [B:2]',
- 'B:2',
- 'Unmount [Loading...]',
- 'Update [B:2]',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
+ expect(Scheduler).toFlushExpired([
+ 'B:2',
+ 'Unmount [Loading...]',
+ 'Update [B:2]',
+ ]);
expect(root).toMatchRenderedOutput('AB:2C');
});
@@ -477,13 +451,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushExpired(['A']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
+ expect(Scheduler).toFlushExpired(['A']);
expect(root).toMatchRenderedOutput('Stateful: 1A');
root.update();
@@ -500,13 +469,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushExpired(['B']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
+ expect(Scheduler).toFlushExpired(['B']);
expect(root).toMatchRenderedOutput('Stateful: 2B');
});
@@ -547,12 +511,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushExpired(['A']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
+ expect(Scheduler).toFlushExpired(['A']);
expect(root).toMatchRenderedOutput('Stateful: 1A');
root.update();
@@ -576,13 +536,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushExpired(['B']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
+ expect(Scheduler).toFlushExpired(['B']);
expect(root).toMatchRenderedOutput('Stateful: 2B');
});
@@ -664,16 +619,8 @@ describe('ReactSuspense', () => {
expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
jest.advanceTimersByTime(500);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushExpired(['A', 'Did commit: A']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [A]',
- 'A',
- 'Did commit: A',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
+ expect(Scheduler).toFlushExpired(['A', 'Did commit: A']);
});
it('retries when an update is scheduled on a timed out tree', () => {
@@ -756,43 +703,25 @@ describe('ReactSuspense', () => {
'Loading...',
]);
expect(Scheduler).toFlushAndYield([]);
+
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
- expect(Scheduler).toFlushExpired([
- 'Child 1',
- 'Suspend! [Child 2]',
- 'Suspend! [Child 3]',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Child 1]',
- 'Child 1',
- 'Suspend! [Child 2]',
- 'Suspend! [Child 3]',
- ]);
- }
+
+ expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
+ expect(Scheduler).toFlushExpired([
+ 'Child 1',
+ 'Suspend! [Child 2]',
+ 'Suspend! [Child 3]',
+ ]);
+
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
- expect(Scheduler).toFlushExpired(['Child 2', 'Suspend! [Child 3]']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Child 2]',
- 'Child 2',
- 'Suspend! [Child 3]',
- ]);
- }
+
+ expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
+ expect(Scheduler).toFlushExpired(['Child 2', 'Suspend! [Child 3]']);
+
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
- expect(Scheduler).toFlushExpired(['Child 3']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Child 3]',
- 'Child 3',
- ]);
- }
+
+ expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
+ expect(Scheduler).toFlushExpired(['Child 3']);
expect(root).toMatchRenderedOutput(
['Child 1', 'Child 2', 'Child 3'].join(''),
);
@@ -852,15 +781,8 @@ describe('ReactSuspense', () => {
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
- expect(Scheduler).toFlushExpired(['Tab: 0']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Tab: 0]',
- 'Tab: 0',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
+ expect(Scheduler).toFlushExpired(['Tab: 0']);
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
act(() => setTab(1));
@@ -872,16 +794,8 @@ describe('ReactSuspense', () => {
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
- expect(Scheduler).toFlushExpired(['Tab: 1']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Tab: 1]',
- 'Tab: 1',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
+ expect(Scheduler).toFlushExpired(['Tab: 1']);
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
act(() => setTab(2));
@@ -893,16 +807,8 @@ describe('ReactSuspense', () => {
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
- expect(Scheduler).toFlushExpired(['Tab: 2']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Tab: 2]',
- 'Tab: 2',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
+ expect(Scheduler).toFlushExpired(['Tab: 2']);
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
});
@@ -939,13 +845,8 @@ describe('ReactSuspense', () => {
expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
- expect(Scheduler).toFlushExpired(['A:0']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [A:0]', 'A:0']);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
+ expect(Scheduler).toFlushExpired(['A:0']);
expect(root).toMatchRenderedOutput('A:0');
act(() => setStep(1));
@@ -982,65 +883,35 @@ describe('ReactSuspense', () => {
// Resolve A
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushExpired([
- 'A',
- // The promises for B and C have now been thrown twice
- 'Suspend! [B]',
- 'Suspend! [C]',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [A]',
- 'A',
- // The promises for B and C have now been thrown twice
- 'Suspend! [B]',
- 'Suspend! [C]',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
+ expect(Scheduler).toFlushExpired([
+ 'A',
+ // The promises for B and C have now been thrown twice
+ 'Suspend! [B]',
+ 'Suspend! [C]',
+ ]);
// Resolve B
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushExpired([
- // Even though the promise for B was thrown twice, we should only
- // re-render once.
- 'B',
- // The promise for C has now been thrown three times
- 'Suspend! [C]',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [B]',
- // Even though the promise for B was thrown twice, we should only
- // re-render once.
- 'B',
- // The promise for C has now been thrown three times
- 'Suspend! [C]',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
+ expect(Scheduler).toFlushExpired([
+ // Even though the promise for B was thrown twice, we should only
+ // re-render once.
+ 'B',
+ // The promise for C has now been thrown three times
+ 'Suspend! [C]',
+ ]);
// Resolve C
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
- expect(Scheduler).toFlushExpired([
- // Even though the promise for C was thrown three times, we should only
- // re-render once.
- 'C',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [C]',
- // Even though the promise for C was thrown three times, we should only
- // re-render once.
- 'C',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
+ expect(Scheduler).toFlushExpired([
+ // Even though the promise for C was thrown three times, we should only
+ // re-render once.
+ 'C',
+ ]);
});
it('#14162', () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
index 529bb33fc93f5..7060f427303a1 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
@@ -17,7 +17,6 @@ let ReactCache;
let Suspense;
let TextResource;
let textResourceShouldFail;
-let enableNewScheduler;
describe('ReactSuspensePlaceholder', () => {
beforeEach(() => {
@@ -31,7 +30,6 @@ describe('ReactSuspensePlaceholder', () => {
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
ReactCache = require('react-cache');
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
Profiler = React.Profiler;
Suspense = React.Suspense;
@@ -325,16 +323,8 @@ describe('ReactSuspensePlaceholder', () => {
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
- expect(Scheduler).toFlushExpired(['Loaded']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Loaded]',
- 'Loaded',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
+ expect(Scheduler).toFlushExpired(['Loaded']);
expect(ReactNoop).toMatchRenderedOutput('LoadedText');
expect(onRender).toHaveBeenCalledTimes(2);
@@ -434,16 +424,8 @@ describe('ReactSuspensePlaceholder', () => {
jest.advanceTimersByTime(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
- expect(Scheduler).toFlushExpired(['Loaded']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Loaded]',
- 'Loaded',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
+ expect(Scheduler).toFlushExpired(['Loaded']);
expect(ReactNoop).toMatchRenderedOutput('LoadedNew');
expect(onRender).toHaveBeenCalledTimes(4);
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
index 46ace8065bfca..9be24b826b9e2 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
@@ -7,7 +7,6 @@ let ReactCache;
let Suspense;
let StrictMode;
let ConcurrentMode;
-let enableNewScheduler;
let TextResource;
let textResourceShouldFail;
@@ -29,7 +28,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
Suspense = React.Suspense;
StrictMode = React.StrictMode;
ConcurrentMode = React.unstable_ConcurrentMode;
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
return new Promise((resolve, reject) =>
@@ -889,16 +887,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.expire(100);
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
- expect(Scheduler).toFlushExpired(['Result']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Result]',
- 'Result',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
+ expect(Scheduler).toFlushExpired(['Result']);
expect(ReactNoop.getChildren()).toEqual([span('Result')]);
});
@@ -935,27 +925,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Initial mount. This is synchronous, because the root is sync.
ReactNoop.renderLegacySyncRoot();
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded([
- 'Suspend! [Step: 1]',
- 'Sibling',
- 'Loading (1)',
- 'Loading (2)',
- 'Loading (3)',
- 'Promise resolved [Step: 1]',
- ]);
- expect(Scheduler).toFlushExpired(['Step: 1']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Suspend! [Step: 1]',
- 'Sibling',
- 'Loading (1)',
- 'Loading (2)',
- 'Loading (3)',
- 'Promise resolved [Step: 1]',
- 'Step: 1',
- ]);
- }
+ expect(Scheduler).toHaveYielded([
+ 'Suspend! [Step: 1]',
+ 'Sibling',
+ 'Loading (1)',
+ 'Loading (2)',
+ 'Loading (3)',
+ 'Promise resolved [Step: 1]',
+ ]);
+ expect(Scheduler).toFlushExpired(['Step: 1']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -987,15 +965,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
- expect(Scheduler).toFlushExpired(['Step: 2']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Step: 2]',
- 'Step: 2',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
+ expect(Scheduler).toFlushExpired(['Step: 2']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -1054,33 +1025,18 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded([
- 'Before',
- 'Suspend! [Async: 1]',
- 'After',
- 'Loading...',
- 'Before',
- 'Sync: 1',
- 'After',
- 'Did mount',
- 'Promise resolved [Async: 1]',
- ]);
- expect(Scheduler).toFlushExpired(['Async: 1']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Before',
- 'Suspend! [Async: 1]',
- 'After',
- 'Loading...',
- 'Before',
- 'Sync: 1',
- 'After',
- 'Did mount',
- 'Promise resolved [Async: 1]',
- 'Async: 1',
- ]);
- }
+ expect(Scheduler).toHaveYielded([
+ 'Before',
+ 'Suspend! [Async: 1]',
+ 'After',
+ 'Loading...',
+ 'Before',
+ 'Sync: 1',
+ 'After',
+ 'Did mount',
+ 'Promise resolved [Async: 1]',
+ ]);
+ expect(Scheduler).toFlushExpired(['Async: 1']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -1135,16 +1091,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// synchronously.
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
- expect(Scheduler).toFlushExpired(['Async: 2']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Async: 2]',
- 'Async: 2',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
+ expect(Scheduler).toFlushExpired(['Async: 2']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -1208,33 +1156,18 @@ describe('ReactSuspenseWithNoopRenderer', () => {
Scheduler.yieldValue('Did mount'),
);
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded([
- 'Before',
- 'Suspend! [Async: 1]',
- 'After',
- 'Loading...',
- 'Before',
- 'Sync: 1',
- 'After',
- 'Did mount',
- 'Promise resolved [Async: 1]',
- ]);
- expect(Scheduler).toFlushExpired(['Async: 1']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Before',
- 'Suspend! [Async: 1]',
- 'After',
- 'Loading...',
- 'Before',
- 'Sync: 1',
- 'After',
- 'Did mount',
- 'Promise resolved [Async: 1]',
- 'Async: 1',
- ]);
- }
+ expect(Scheduler).toHaveYielded([
+ 'Before',
+ 'Suspend! [Async: 1]',
+ 'After',
+ 'Loading...',
+ 'Before',
+ 'Sync: 1',
+ 'After',
+ 'Did mount',
+ 'Promise resolved [Async: 1]',
+ ]);
+ expect(Scheduler).toFlushExpired(['Async: 1']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -1289,16 +1222,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// synchronously.
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
- expect(Scheduler).toFlushExpired(['Async: 2']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Async: 2]',
- 'Async: 2',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
+ expect(Scheduler).toFlushExpired(['Async: 2']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -1376,13 +1301,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.expire(1000);
await advanceTimers(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushExpired(['B']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
+ expect(Scheduler).toFlushExpired(['B']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -1434,21 +1354,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await advanceTimers(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
- expect(Scheduler).toFlushExpired([
- 'constructor',
- 'Hi',
- 'componentDidMount',
- ]);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Hi]',
- 'constructor',
- 'Hi',
- 'componentDidMount',
- ]);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
+ expect(Scheduler).toFlushExpired([
+ 'constructor',
+ 'Hi',
+ 'componentDidMount',
+ ]);
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
});
@@ -1487,12 +1398,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
await advanceTimers(100);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
- expect(Scheduler).toFlushExpired(['Hi']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
+ expect(Scheduler).toFlushExpired(['Hi']);
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
});
@@ -1536,12 +1443,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await advanceTimers(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
- expect(Scheduler).toFlushExpired(['Hi']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
+ expect(Scheduler).toFlushExpired(['Hi']);
});
} else {
it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
@@ -1580,12 +1483,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await advanceTimers(1000);
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
- expect(Scheduler).toFlushExpired(['Hi']);
- } else {
- expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
- }
+ expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
+ expect(Scheduler).toFlushExpired(['Hi']);
});
}
});
diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
index 541307deeadc4..9f39e61ea88a3 100644
--- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
+++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ReactDebugFiberPerf new scheduler captures all lifecycles 1`] = `
+exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Mount
@@ -44,7 +44,7 @@ exports[`ReactDebugFiberPerf new scheduler captures all lifecycles 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler deduplicates lifecycle names during commit to reduce overhead 1`] = `
+exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// The commit phase should mention A and B just once
@@ -91,7 +91,7 @@ exports[`ReactDebugFiberPerf new scheduler deduplicates lifecycle names during c
"
`;
-exports[`ReactDebugFiberPerf new scheduler does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
+exports[`ReactDebugFiberPerf does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Mount
@@ -107,7 +107,7 @@ exports[`ReactDebugFiberPerf new scheduler does not include ConcurrentMode, Stri
"
`;
-exports[`ReactDebugFiberPerf new scheduler does not include context provider or consumer in measurements 1`] = `
+exports[`ReactDebugFiberPerf does not include context provider or consumer in measurements 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Mount
@@ -122,7 +122,7 @@ exports[`ReactDebugFiberPerf new scheduler does not include context provider or
"
`;
-exports[`ReactDebugFiberPerf new scheduler does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
+exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
"⚛ (React Tree Reconciliation: Completed Root)
⚛ Component [mount]
@@ -142,7 +142,7 @@ exports[`ReactDebugFiberPerf new scheduler does not schedule an extra callback i
"
`;
-exports[`ReactDebugFiberPerf new scheduler does not treat setState from cWM or cWRP as cascading 1`] = `
+exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Should not print a warning
@@ -171,7 +171,7 @@ exports[`ReactDebugFiberPerf new scheduler does not treat setState from cWM or c
"
`;
-exports[`ReactDebugFiberPerf new scheduler measures a simple reconciliation 1`] = `
+exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Mount
@@ -208,7 +208,7 @@ exports[`ReactDebugFiberPerf new scheduler measures a simple reconciliation 1`]
"
`;
-exports[`ReactDebugFiberPerf new scheduler measures deferred work in chunks 1`] = `
+exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Start rendering through B
@@ -235,7 +235,7 @@ exports[`ReactDebugFiberPerf new scheduler measures deferred work in chunks 1`]
"
`;
-exports[`ReactDebugFiberPerf new scheduler measures deprioritized work 1`] = `
+exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
"// Flush the parent
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
@@ -258,7 +258,7 @@ exports[`ReactDebugFiberPerf new scheduler measures deprioritized work 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler properly displays the forwardRef component in measurements 1`] = `
+exports[`ReactDebugFiberPerf properly displays the forwardRef component in measurements 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Mount
@@ -278,7 +278,7 @@ exports[`ReactDebugFiberPerf new scheduler properly displays the forwardRef comp
"
`;
-exports[`ReactDebugFiberPerf new scheduler recovers from caught errors 1`] = `
+exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Stop on Baddie and restart from Boundary
@@ -312,7 +312,7 @@ exports[`ReactDebugFiberPerf new scheduler recovers from caught errors 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler recovers from fatal errors 1`] = `
+exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Will fatal
@@ -343,7 +343,7 @@ exports[`ReactDebugFiberPerf new scheduler recovers from fatal errors 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler skips parents during setState 1`] = `
+exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Should include just A and B, no Parents
@@ -358,7 +358,7 @@ exports[`ReactDebugFiberPerf new scheduler skips parents during setState 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 1`] = `
+exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
@@ -369,7 +369,7 @@ exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 2`] = `
+exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
@@ -392,7 +392,7 @@ exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 2`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler supports memo 1`] = `
+exports[`ReactDebugFiberPerf supports memo 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
@@ -406,7 +406,7 @@ exports[`ReactDebugFiberPerf new scheduler supports memo 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler supports portals 1`] = `
+exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
@@ -420,7 +420,7 @@ exports[`ReactDebugFiberPerf new scheduler supports portals 1`] = `
"
`;
-exports[`ReactDebugFiberPerf new scheduler warns if an in-progress update is interrupted 1`] = `
+exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Yielded)
@@ -443,7 +443,7 @@ exports[`ReactDebugFiberPerf new scheduler warns if an in-progress update is int
"
`;
-exports[`ReactDebugFiberPerf new scheduler warns if async work expires (starvation) 1`] = `
+exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = `
"⛔ (Waiting for async callback... will force flush in 5250 ms) Warning: React was blocked by main thread
⚛ (Committing Changes)
@@ -453,7 +453,7 @@ exports[`ReactDebugFiberPerf new scheduler warns if async work expires (starvati
"
`;
-exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from setState 1`] = `
+exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Should print a warning
@@ -477,511 +477,7 @@ exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from setSt
"
`;
-exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from top-level render 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Rendering the first root
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Cascading [mount]
-
-⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
- ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
-
-// Scheduling another root from componentDidMount
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler captures all lifecycles 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Mount
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ AllLifecycles [mount]
- ⚛ AllLifecycles.componentWillMount
- ⚛ AllLifecycles.getChildContext
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
- ⚛ AllLifecycles.componentDidMount
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Update
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ AllLifecycles [update]
- ⚛ AllLifecycles.componentWillReceiveProps
- ⚛ AllLifecycles.shouldComponentUpdate
- ⚛ AllLifecycles.componentWillUpdate
- ⚛ AllLifecycles.getChildContext
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 2 Total)
- ⚛ AllLifecycles.componentDidUpdate
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Unmount
-⚛ (React Tree Reconciliation: Completed Root)
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ AllLifecycles.componentWillUnmount
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler deduplicates lifecycle names during commit to reduce overhead 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// The commit phase should mention A and B just once
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [update]
- ⚛ A [update]
- ⚛ B [update]
- ⚛ A [update]
- ⚛ B [update]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 9 Total)
- ⚛ (Calling Lifecycle Methods: 9 Total)
- ⚛ A.componentDidUpdate
- ⚛ B.componentDidUpdate
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Because of deduplication, we don't know B was cascading,
-// but we should still see the warning for the commit phase.
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [update]
- ⚛ A [update]
- ⚛ B [update]
- ⚛ A [update]
- ⚛ B [update]
-
-⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 9 Total)
- ⚛ (Calling Lifecycle Methods: 9 Total)
- ⚛ A.componentDidUpdate
- ⚛ B.componentDidUpdate
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ B [update]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 2 Total)
- ⚛ B.componentDidUpdate
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Mount
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Profiler [mount]
- ⚛ Parent [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler does not include context provider or consumer in measurements 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Mount
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
-"⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Component [mount]
-
-⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
- ⛔ Component.componentDidMount Warning: Scheduled a cascading update
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Component [update]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler does not treat setState from cWM or cWRP as cascading 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Should not print a warning
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ NotCascading [mount]
- ⚛ NotCascading.componentWillMount
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Should not print a warning
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [update]
- ⚛ NotCascading [update]
- ⚛ NotCascading.componentWillReceiveProps
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 2 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler measures a simple reconciliation 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Mount
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Update
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [update]
- ⚛ Child [update]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 2 Total)
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Unmount
-⚛ (React Tree Reconciliation: Completed Root)
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler measures deferred work in chunks 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Start rendering through B
-⚛ (React Tree Reconciliation: Yielded)
- ⚛ Parent [mount]
- ⚛ A [mount]
- ⚛ Child [mount]
- ⚛ B [mount]
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Complete the rest
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ B [mount]
- ⚛ Child [mount]
- ⚛ C [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler measures deprioritized work 1`] = `
-"// Flush the parent
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-
-⚛ (Waiting for async callback... will force flush in 10737418210 ms)
-
-// Flush the child
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler properly displays the forwardRef component in measurements 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Mount
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ ForwardRef [mount]
- ⚛ Child [mount]
- ⚛ ForwardRef(refForwarder) [mount]
- ⚛ Child [mount]
- ⚛ ForwardRef(OverriddenName) [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler recovers from caught errors 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Stop on Baddie and restart from Boundary
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
- ⚛ Parent [mount]
- ⚛ Baddie [mount]
- ⚛ Boundary [mount]
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
- ⚛ Parent [mount]
- ⚛ Baddie [mount]
- ⚛ Boundary [mount]
-
-⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Boundary [update]
- ⚛ ErrorReport [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler recovers from fatal errors 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Will fatal
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Baddie [mount]
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Baddie [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Will reconcile from a clean state
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler skips parents during setState 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Should include just A and B, no Parents
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ A [update]
- ⚛ B [update]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 2 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler supports Suspense and lazy 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⛔ Suspense [mount] Warning: Rendering was suspended
- ⚛ Suspense [mount]
- ⚛ Spinner [mount]
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler supports Suspense and lazy 2`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⛔ Suspense [mount] Warning: Rendering was suspended
- ⚛ Suspense [mount]
- ⚛ Spinner [mount]
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Suspense [mount]
- ⚛ Foo [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler supports memo 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Foo [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler supports portals 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Child [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler warns if an in-progress update is interrupted 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-⚛ (React Tree Reconciliation: Yielded)
- ⚛ Foo [mount]
-
-⚛ (Waiting for async callback... will force flush in 5250 ms)
- ⛔ (React Tree Reconciliation: Completed Root) Warning: A top-level update interrupted the previous render
- ⚛ Foo [mount]
- ⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-
-⚛ (React Tree Reconciliation: Completed Root)
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 0 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler warns if async work expires (starvation) 1`] = `
-"⛔ (Waiting for async callback... will force flush in 5250 ms) Warning: React was blocked by main thread
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Foo [mount]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 0 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler warns on cascading renders from setState 1`] = `
-"⚛ (Waiting for async callback... will force flush in 5250 ms)
-
-// Should print a warning
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Parent [mount]
- ⚛ Cascading [mount]
-
-⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 2 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
- ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
-
-⚛ (React Tree Reconciliation: Completed Root)
- ⚛ Cascading [update]
-
-⚛ (Committing Changes)
- ⚛ (Committing Snapshot Effects: 0 Total)
- ⚛ (Committing Host Effects: 1 Total)
- ⚛ (Calling Lifecycle Methods: 1 Total)
-"
-`;
-
-exports[`ReactDebugFiberPerf old scheduler warns on cascading renders from top-level render 1`] = `
+exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Rendering the first root
diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js
index 95c3b54770db4..e894f2c3173e4 100644
--- a/packages/react/src/__tests__/ReactProfiler-test.internal.js
+++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js
@@ -12,7 +12,6 @@
let React;
let ReactFeatureFlags;
-let enableNewScheduler;
let ReactNoop;
let Scheduler;
let ReactCache;
@@ -36,7 +35,6 @@ function loadModules({
ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback;
- enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
React = require('react');
Scheduler = require('scheduler');
@@ -1354,9 +1352,7 @@ describe('Profiler', () => {
},
);
}).toThrow('Expected error onWorkScheduled');
- if (enableNewScheduler) {
- expect(Scheduler).toFlushAndYield(['Component:fail']);
- }
+ expect(Scheduler).toFlushAndYield(['Component:fail']);
throwInOnWorkScheduled = false;
expect(onWorkScheduled).toHaveBeenCalled();
@@ -1391,14 +1387,10 @@ describe('Profiler', () => {
// Errors that happen inside of a subscriber should throw,
throwInOnWorkStarted = true;
expect(Scheduler).toFlushAndThrow('Expected error onWorkStarted');
- if (enableNewScheduler) {
- // Rendering was interrupted by the error that was thrown
- expect(Scheduler).toHaveYielded([]);
- // Rendering continues in the next task
- expect(Scheduler).toFlushAndYield(['Component:text']);
- } else {
- expect(Scheduler).toHaveYielded(['Component:text']);
- }
+ // Rendering was interrupted by the error that was thrown
+ expect(Scheduler).toHaveYielded([]);
+ // Rendering continues in the next task
+ expect(Scheduler).toFlushAndYield(['Component:text']);
throwInOnWorkStarted = false;
expect(onWorkStarted).toHaveBeenCalled();
@@ -2389,16 +2381,8 @@ describe('Profiler', () => {
jest.runAllTimers();
await resourcePromise;
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
- expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [loaded]',
- 'AsyncText [loaded]',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
+ expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
@@ -2454,9 +2438,7 @@ describe('Profiler', () => {
await resourcePromise;
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
- if (enableNewScheduler) {
- expect(Scheduler).toFlushExpired([]);
- }
+ expect(Scheduler).toFlushExpired([]);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
@@ -2631,16 +2613,8 @@ describe('Profiler', () => {
jest.advanceTimersByTime(100);
await originalPromise;
- if (enableNewScheduler) {
- expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
- expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
- } else {
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [loaded]',
- 'AsyncText [loaded]',
- ]);
- }
-
+ expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
+ expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
expect(onRender).toHaveBeenCalledTimes(1);
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index bab2d9a2cfff0..398aa209707d4 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -65,9 +65,5 @@ export const warnAboutDeprecatedSetNativeProps = false;
// Experimental React Events support. Only used in www builds for now.
export const enableEventAPI = false;
-// Enables rewritten version of ReactFiberScheduler. Added in case we need to
-// quickly revert it.
-export const enableNewScheduler = false;
-
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
export const enableJSXTransformAPI = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 16b811bbbab2f..285ea06bbc64f 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -31,7 +31,6 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = true;
export const warnAboutDeprecatedSetNativeProps = true;
export const enableEventAPI = false;
-export const enableNewScheduler = false;
export const enableJSXTransformAPI = false;
// Only used in www builds.
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 3d26ebea941d8..60f29acdc7797 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -28,7 +28,6 @@ export const warnAboutShorthandPropertyCollision = false;
export const enableSchedulerDebugging = false;
export const warnAboutDeprecatedSetNativeProps = false;
export const enableEventAPI = false;
-export const enableNewScheduler = false;
export const enableJSXTransformAPI = false;
// Only used in www builds.
diff --git a/packages/shared/forks/ReactFeatureFlags.new-scheduler.js b/packages/shared/forks/ReactFeatureFlags.new-scheduler.js
deleted file mode 100644
index ab43d3f3081f4..0000000000000
--- a/packages/shared/forks/ReactFeatureFlags.new-scheduler.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * 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 strict
- */
-
-export const enableUserTimingAPI = __DEV__;
-export const debugRenderPhaseSideEffects = false;
-export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
-export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
-export const warnAboutDeprecatedLifecycles = true;
-export const enableProfilerTimer = __PROFILE__;
-export const enableSchedulerTracing = __PROFILE__;
-export const enableSuspenseServerRenderer = false; // TODO: __DEV__? Here it might just be false.
-export const enableSchedulerDebugging = false;
-export function addUserTimingListener() {
- throw new Error('Not implemented.');
-}
-export const disableJavaScriptURLs = false;
-export const disableYielding = false;
-export const disableInputAttributeSyncing = false;
-export const enableStableConcurrentModeAPIs = false;
-export const warnAboutShorthandPropertyCollision = false;
-export const warnAboutDeprecatedSetNativeProps = false;
-export const enableEventAPI = false;
-export const enableJSXTransformAPI = false;
-
-export const enableNewScheduler = true;
diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js
index 8e96b43623d20..14b8716b96342 100644
--- a/packages/shared/forks/ReactFeatureFlags.persistent.js
+++ b/packages/shared/forks/ReactFeatureFlags.persistent.js
@@ -28,7 +28,6 @@ export const warnAboutShorthandPropertyCollision = false;
export const enableSchedulerDebugging = false;
export const warnAboutDeprecatedSetNativeProps = false;
export const enableEventAPI = false;
-export const enableNewScheduler = false;
export const enableJSXTransformAPI = false;
// Only used in www builds.
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 178a542430a5f..40c982f3e7cc3 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -28,7 +28,6 @@ export const warnAboutShorthandPropertyCollision = false;
export const enableSchedulerDebugging = false;
export const warnAboutDeprecatedSetNativeProps = false;
export const enableEventAPI = false;
-export const enableNewScheduler = false;
export const enableJSXTransformAPI = false;
// Only used in www builds.
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index dd3bfd274f44a..f6f80c8985350 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -26,7 +26,6 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const disableJavaScriptURLs = false;
export const disableYielding = false;
export const enableEventAPI = true;
-export const enableNewScheduler = false;
export const enableJSXTransformAPI = true;
// Only used in www builds.
diff --git a/packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js b/packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js
deleted file mode 100644
index cd6b303a75660..0000000000000
--- a/packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * 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
- */
-
-import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
-import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www-new-scheduler';
-
-export {
- enableUserTimingAPI,
- debugRenderPhaseSideEffects,
- debugRenderPhaseSideEffectsForStrictMode,
- replayFailedUnitOfWorkWithInvokeGuardedCallback,
- warnAboutDeprecatedLifecycles,
- enableProfilerTimer,
- enableSchedulerTracing,
- enableSuspenseServerRenderer,
- enableSchedulerDebugging,
- addUserTimingListener,
- disableJavaScriptURLs,
- disableYielding,
- disableInputAttributeSyncing,
- enableStableConcurrentModeAPIs,
- warnAboutShorthandPropertyCollision,
- warnAboutDeprecatedSetNativeProps,
- enableEventAPI,
-} from './ReactFeatureFlags.www';
-
-export const enableNewScheduler = true;
-export const enableJSXTransformAPI = true;
-
-// Flow magic to verify the exports of this file match the original version.
-// eslint-disable-next-line no-unused-vars
-type Check<_X, Y: _X, X: Y = _X> = null;
-// eslint-disable-next-line no-unused-expressions
-(null: Check);
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index e77c28389d027..0be35ad2d9f4a 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -40,11 +40,6 @@ export const enableSuspenseServerRenderer = true;
export const disableJavaScriptURLs = true;
-// I've chosen to make this a static flag instead of a dynamic flag controlled
-// by a GK so that it doesn't increase bundle size. It should still be easy
-// to rollback by reverting the commit that turns this on.
-export const enableNewScheduler = false;
-
let refCount = 0;
export function addUserTimingListener() {
if (__DEV__) {
diff --git a/scripts/circleci/test_entry_point.sh b/scripts/circleci/test_entry_point.sh
index 6227b7c5dcbd0..87bbad4aba9e8 100755
--- a/scripts/circleci/test_entry_point.sh
+++ b/scripts/circleci/test_entry_point.sh
@@ -11,7 +11,6 @@ if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
COMMANDS_TO_RUN+=('node ./scripts/tasks/flow-ci')
COMMANDS_TO_RUN+=('node ./scripts/tasks/eslint')
COMMANDS_TO_RUN+=('yarn test --maxWorkers=2')
- COMMANDS_TO_RUN+=('yarn test-new-scheduler --maxWorkers=2')
COMMANDS_TO_RUN+=('yarn test-persistent --maxWorkers=2')
COMMANDS_TO_RUN+=('./scripts/circleci/check_license.sh')
COMMANDS_TO_RUN+=('./scripts/circleci/check_modules.sh')
diff --git a/scripts/jest/config.source-new-scheduler.js b/scripts/jest/config.source-new-scheduler.js
deleted file mode 100644
index 6d74d5bb1b0fa..0000000000000
--- a/scripts/jest/config.source-new-scheduler.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-const baseConfig = require('./config.base');
-
-module.exports = Object.assign({}, baseConfig, {
- setupFiles: [
- ...baseConfig.setupFiles,
- require.resolve('./setupNewScheduler.js'),
- require.resolve('./setupHostConfigs.js'),
- ],
-});
diff --git a/scripts/jest/setupNewScheduler.js b/scripts/jest/setupNewScheduler.js
deleted file mode 100644
index d3d58bd5653db..0000000000000
--- a/scripts/jest/setupNewScheduler.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-jest.mock('shared/ReactFeatureFlags', () => {
- const ReactFeatureFlags = require.requireActual('shared/ReactFeatureFlags');
- ReactFeatureFlags.enableNewScheduler = true;
- return ReactFeatureFlags;
-});
diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js
index 2d6babd5760fd..47005d3c81338 100644
--- a/scripts/rollup/bundles.js
+++ b/scripts/rollup/bundles.js
@@ -110,22 +110,6 @@ const bundles = [
externals: ['react'],
},
- /******* React DOM (new scheduler) *******/
- {
- bundleTypes: [
- FB_WWW_DEV,
- FB_WWW_PROD,
- FB_WWW_PROFILING,
- NODE_DEV,
- NODE_PROD,
- NODE_PROFILING,
- ],
- moduleType: RENDERER,
- entry: 'react-dom/unstable-new-scheduler',
- global: 'ReactDOMNewScheduler',
- externals: ['react'],
- },
-
/******* Test Utils *******/
{
moduleType: RENDERER_UTILS,
diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js
index 45c1fd411f178..c93ac87559aaa 100644
--- a/scripts/rollup/forks.js
+++ b/scripts/rollup/forks.js
@@ -7,9 +7,6 @@ const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
const UMD_DEV = bundleTypes.UMD_DEV;
const UMD_PROD = bundleTypes.UMD_PROD;
const UMD_PROFILING = bundleTypes.UMD_PROFILING;
-const NODE_DEV = bundleTypes.NODE_DEV;
-const NODE_PROD = bundleTypes.NODE_PROD;
-const NODE_PROFILING = bundleTypes.NODE_PROFILING;
const FB_WWW_DEV = bundleTypes.FB_WWW_DEV;
const FB_WWW_PROD = bundleTypes.FB_WWW_PROD;
const FB_WWW_PROFILING = bundleTypes.FB_WWW_PROFILING;
@@ -71,22 +68,6 @@ const forks = Object.freeze({
// We have a few forks for different environments.
'shared/ReactFeatureFlags': (bundleType, entry) => {
switch (entry) {
- case 'react-dom/unstable-new-scheduler': {
- switch (bundleType) {
- case FB_WWW_DEV:
- case FB_WWW_PROD:
- case FB_WWW_PROFILING:
- return 'shared/forks/ReactFeatureFlags.www-new-scheduler.js';
- case NODE_DEV:
- case NODE_PROD:
- case NODE_PROFILING:
- return 'shared/forks/ReactFeatureFlags.new-scheduler.js';
- default:
- throw Error(
- `Unexpected entry (${entry}) and bundleType (${bundleType})`
- );
- }
- }
case 'react-native-renderer':
switch (bundleType) {
case RN_FB_DEV:
diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js
index 02c131a847a61..66c54963c7f28 100644
--- a/scripts/shared/inlinedHostConfigs.js
+++ b/scripts/shared/inlinedHostConfigs.js
@@ -9,11 +9,7 @@
module.exports = [
{
shortName: 'dom',
- entryPoints: [
- 'react-dom',
- 'react-dom/unstable-fizz.node',
- 'react-dom/unstable-new-scheduler',
- ],
+ entryPoints: ['react-dom', 'react-dom/unstable-fizz.node'],
isFlowTyped: true,
isFizzSupported: true,
},