@samitouri / QOS-React-2 / commits / 9055e31e5c

Replace old Fiber Scheduler with new one (#15387)

The new Fiber Scheduler has been running in Facebook for several days without issues. Let's switch to it.

Andrew Clark committed Apr 11, 2019 at 19:15 UTC 9055e31e5c82d03f0a365c459f7bc79e402dbef5
34 files changed +2917 -7085
package.json
-1
@@ -102,7 +102,6 @@
102 "test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
103 "test-persistent": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-persistent.js",
104 "test-fire": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-fire.js",
105 - "test-new-scheduler": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-new-scheduler.js",
105 "test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
106 "test-fire-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source-fire.js",
107 "test-prod-build": "yarn test-build-prod",
packages/react-dom/src/__tests__/ReactDOMHooks-test.js renamed
+9 -28
@@ -9,8 +9,6 @@
9
10 'use strict';
11
12 -let ReactFeatureFlags;
13 -let enableNewScheduler;
12 let React;
13 let ReactDOM;
14 let Scheduler;
@@ -21,8 +19,6 @@ describe('ReactDOMHooks', () => {
19 beforeEach(() => {
20 jest.resetModules();
21
24 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
25 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
22 React = require('react');
23 ReactDOM = require('react-dom');
24 Scheduler = require('scheduler');
@@ -101,30 +97,15 @@ describe('ReactDOMHooks', () => {
97 }
98
99 ReactDOM.render(<Foo />, container);
104 -
105 - if (enableNewScheduler) {
106 - // The old behavior was accidental; in the new scheduler, flushing passive
107 - // effects also flushes synchronous work, even inside batchedUpdates.
108 - ReactDOM.unstable_batchedUpdates(() => {
109 - _set(0); // Forces the effect to be flushed
110 - expect(otherContainer.textContent).toBe('A');
111 - ReactDOM.render(<B />, otherContainer);
112 - expect(otherContainer.textContent).toBe('A');
113 - });
114 - expect(otherContainer.textContent).toBe('B');
115 - expect(calledA).toBe(true);
116 - expect(calledB).toBe(true);
117 - } else {
118 - ReactDOM.unstable_batchedUpdates(() => {
119 - _set(0); // Forces the effect to be flushed
120 - expect(otherContainer.textContent).toBe('');
121 - ReactDOM.render(<B />, otherContainer);
122 - expect(otherContainer.textContent).toBe('');
123 - });
124 - expect(otherContainer.textContent).toBe('B');
125 - expect(calledA).toBe(false); // It was in a batch
126 - expect(calledB).toBe(true);
127 - }
100 + ReactDOM.unstable_batchedUpdates(() => {
101 + _set(0); // Forces the effect to be flushed
102 + expect(otherContainer.textContent).toBe('A');
103 + ReactDOM.render(<B />, otherContainer);
104 + expect(otherContainer.textContent).toBe('A');
105 + });
106 + expect(otherContainer.textContent).toBe('B');
107 + expect(calledA).toBe(true);
108 + expect(calledB).toBe(true);
109 });
110
111 it('should not bail out when an update is scheduled from within an event handler', () => {
packages/react-dom/unstable-new-scheduler.fb.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -'use strict';
9 -
10 -const ReactDOMFB = require('./src/client/ReactDOMFB');
11 -
12 -// TODO: decide on the top-level export form.
13 -// This is hacky but makes it work with both Rollup and Jest.
14 -module.exports = ReactDOMFB.default || ReactDOMFB;
packages/react-dom/unstable-new-scheduler.js deleted
-16
@@ -1,16 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -'use strict';
11 -
12 -const ReactDOM = require('./src/client/ReactDOM');
13 -
14 -// TODO: decide on the top-level export form.
15 -// This is hacky but makes it work with both Rollup and Jest.
16 -module.exports = ReactDOM.default || ReactDOM;
packages/react-reconciler/src/ReactFiberPendingPriority.js deleted
-265
@@ -1,265 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {FiberRoot} from './ReactFiberRoot';
11 -import type {ExpirationTime} from './ReactFiberExpirationTime';
12 -
13 -import {NoWork} from './ReactFiberExpirationTime';
14 -
15 -// TODO: Offscreen updates should never suspend. However, a promise that
16 -// suspended inside an offscreen subtree should be able to ping at the priority
17 -// of the outer render.
18 -
19 -export function markPendingPriorityLevel(
20 - root: FiberRoot,
21 - expirationTime: ExpirationTime,
22 -): void {
23 - // If there's a gap between completing a failed root and retrying it,
24 - // additional updates may be scheduled. Clear `didError`, in case the update
25 - // is sufficient to fix the error.
26 - root.didError = false;
27 -
28 - // Update the latest and earliest pending times
29 - const earliestPendingTime = root.earliestPendingTime;
30 - if (earliestPendingTime === NoWork) {
31 - // No other pending updates.
32 - root.earliestPendingTime = root.latestPendingTime = expirationTime;
33 - } else {
34 - if (earliestPendingTime < expirationTime) {
35 - // This is the earliest pending update.
36 - root.earliestPendingTime = expirationTime;
37 - } else {
38 - const latestPendingTime = root.latestPendingTime;
39 - if (latestPendingTime > expirationTime) {
40 - // This is the latest pending update
41 - root.latestPendingTime = expirationTime;
42 - }
43 - }
44 - }
45 - findNextExpirationTimeToWorkOn(expirationTime, root);
46 -}
47 -
48 -export function markCommittedPriorityLevels(
49 - root: FiberRoot,
50 - earliestRemainingTime: ExpirationTime,
51 -): void {
52 - root.didError = false;
53 -
54 - if (earliestRemainingTime === NoWork) {
55 - // Fast path. There's no remaining work. Clear everything.
56 - root.earliestPendingTime = NoWork;
57 - root.latestPendingTime = NoWork;
58 - root.earliestSuspendedTime = NoWork;
59 - root.latestSuspendedTime = NoWork;
60 - root.latestPingedTime = NoWork;
61 - findNextExpirationTimeToWorkOn(NoWork, root);
62 - return;
63 - }
64 -
65 - if (earliestRemainingTime < root.latestPingedTime) {
66 - root.latestPingedTime = NoWork;
67 - }
68 -
69 - // Let's see if the previous latest known pending level was just flushed.
70 - const latestPendingTime = root.latestPendingTime;
71 - if (latestPendingTime !== NoWork) {
72 - if (latestPendingTime > earliestRemainingTime) {
73 - // We've flushed all the known pending levels.
74 - root.earliestPendingTime = root.latestPendingTime = NoWork;
75 - } else {
76 - const earliestPendingTime = root.earliestPendingTime;
77 - if (earliestPendingTime > earliestRemainingTime) {
78 - // We've flushed the earliest known pending level. Set this to the
79 - // latest pending time.
80 - root.earliestPendingTime = root.latestPendingTime;
81 - }
82 - }
83 - }
84 -
85 - // Now let's handle the earliest remaining level in the whole tree. We need to
86 - // decide whether to treat it as a pending level or as suspended. Check
87 - // it falls within the range of known suspended levels.
88 -
89 - const earliestSuspendedTime = root.earliestSuspendedTime;
90 - if (earliestSuspendedTime === NoWork) {
91 - // There's no suspended work. Treat the earliest remaining level as a
92 - // pending level.
93 - markPendingPriorityLevel(root, earliestRemainingTime);
94 - findNextExpirationTimeToWorkOn(NoWork, root);
95 - return;
96 - }
97 -
98 - const latestSuspendedTime = root.latestSuspendedTime;
99 - if (earliestRemainingTime < latestSuspendedTime) {
100 - // The earliest remaining level is later than all the suspended work. That
101 - // means we've flushed all the suspended work.
102 - root.earliestSuspendedTime = NoWork;
103 - root.latestSuspendedTime = NoWork;
104 - root.latestPingedTime = NoWork;
105 -
106 - // There's no suspended work. Treat the earliest remaining level as a
107 - // pending level.
108 - markPendingPriorityLevel(root, earliestRemainingTime);
109 - findNextExpirationTimeToWorkOn(NoWork, root);
110 - return;
111 - }
112 -
113 - if (earliestRemainingTime > earliestSuspendedTime) {
114 - // The earliest remaining time is earlier than all the suspended work.
115 - // Treat it as a pending update.
116 - markPendingPriorityLevel(root, earliestRemainingTime);
117 - findNextExpirationTimeToWorkOn(NoWork, root);
118 - return;
119 - }
120 -
121 - // The earliest remaining time falls within the range of known suspended
122 - // levels. We should treat this as suspended work.
123 - findNextExpirationTimeToWorkOn(NoWork, root);
124 -}
125 -
126 -export function hasLowerPriorityWork(
127 - root: FiberRoot,
128 - erroredExpirationTime: ExpirationTime,
129 -): boolean {
130 - const latestPendingTime = root.latestPendingTime;
131 - const latestSuspendedTime = root.latestSuspendedTime;
132 - const latestPingedTime = root.latestPingedTime;
133 - return (
134 - (latestPendingTime !== NoWork &&
135 - latestPendingTime < erroredExpirationTime) ||
136 - (latestSuspendedTime !== NoWork &&
137 - latestSuspendedTime < erroredExpirationTime) ||
138 - (latestPingedTime !== NoWork && latestPingedTime < erroredExpirationTime)
139 - );
140 -}
141 -
142 -export function isPriorityLevelSuspended(
143 - root: FiberRoot,
144 - expirationTime: ExpirationTime,
145 -): boolean {
146 - const earliestSuspendedTime = root.earliestSuspendedTime;
147 - const latestSuspendedTime = root.latestSuspendedTime;
148 - return (
149 - earliestSuspendedTime !== NoWork &&
150 - expirationTime <= earliestSuspendedTime &&
151 - expirationTime >= latestSuspendedTime
152 - );
153 -}
154 -
155 -export function markSuspendedPriorityLevel(
156 - root: FiberRoot,
157 - suspendedTime: ExpirationTime,
158 -): void {
159 - root.didError = false;
160 - clearPing(root, suspendedTime);
161 -
162 - // First, check the known pending levels and update them if needed.
163 - const earliestPendingTime = root.earliestPendingTime;
164 - const latestPendingTime = root.latestPendingTime;
165 - if (earliestPendingTime === suspendedTime) {
166 - if (latestPendingTime === suspendedTime) {
167 - // Both known pending levels were suspended. Clear them.
168 - root.earliestPendingTime = root.latestPendingTime = NoWork;
169 - } else {
170 - // The earliest pending level was suspended. Clear by setting it to the
171 - // latest pending level.
172 - root.earliestPendingTime = latestPendingTime;
173 - }
174 - } else if (latestPendingTime === suspendedTime) {
175 - // The latest pending level was suspended. Clear by setting it to the
176 - // latest pending level.
177 - root.latestPendingTime = earliestPendingTime;
178 - }
179 -
180 - // Finally, update the known suspended levels.
181 - const earliestSuspendedTime = root.earliestSuspendedTime;
182 - const latestSuspendedTime = root.latestSuspendedTime;
183 - if (earliestSuspendedTime === NoWork) {
184 - // No other suspended levels.
185 - root.earliestSuspendedTime = root.latestSuspendedTime = suspendedTime;
186 - } else {
187 - if (earliestSuspendedTime < suspendedTime) {
188 - // This is the earliest suspended level.
189 - root.earliestSuspendedTime = suspendedTime;
190 - } else if (latestSuspendedTime > suspendedTime) {
191 - // This is the latest suspended level
192 - root.latestSuspendedTime = suspendedTime;
193 - }
194 - }
195 -
196 - findNextExpirationTimeToWorkOn(suspendedTime, root);
197 -}
198 -
199 -export function markPingedPriorityLevel(
200 - root: FiberRoot,
201 - pingedTime: ExpirationTime,
202 -): void {
203 - root.didError = false;
204 -
205 - // TODO: When we add back resuming, we need to ensure the progressed work
206 - // is thrown out and not reused during the restarted render. One way to
207 - // invalidate the progressed work is to restart at expirationTime + 1.
208 - const latestPingedTime = root.latestPingedTime;
209 - if (latestPingedTime === NoWork || latestPingedTime > pingedTime) {
210 - root.latestPingedTime = pingedTime;
211 - }
212 - findNextExpirationTimeToWorkOn(pingedTime, root);
213 -}
214 -
215 -function clearPing(root, completedTime) {
216 - const latestPingedTime = root.latestPingedTime;
217 - if (latestPingedTime >= completedTime) {
218 - root.latestPingedTime = NoWork;
219 - }
220 -}
221 -
222 -export function didExpireAtExpirationTime(
223 - root: FiberRoot,
224 - currentTime: ExpirationTime,
225 -): void {
226 - const expirationTime = root.expirationTime;
227 - if (expirationTime !== NoWork && currentTime <= expirationTime) {
228 - // The root has expired. Flush all work up to the current time.
229 - root.nextExpirationTimeToWorkOn = currentTime;
230 - }
231 -}
232 -
233 -function findNextExpirationTimeToWorkOn(completedExpirationTime, root) {
234 - const earliestSuspendedTime = root.earliestSuspendedTime;
235 - const latestSuspendedTime = root.latestSuspendedTime;
236 - const earliestPendingTime = root.earliestPendingTime;
237 - const latestPingedTime = root.latestPingedTime;
238 -
239 - // Work on the earliest pending time. Failing that, work on the latest
240 - // pinged time.
241 - let nextExpirationTimeToWorkOn =
242 - earliestPendingTime !== NoWork ? earliestPendingTime : latestPingedTime;
243 -
244 - // If there is no pending or pinged work, check if there's suspended work
245 - // that's lower priority than what we just completed.
246 - if (
247 - nextExpirationTimeToWorkOn === NoWork &&
248 - (completedExpirationTime === NoWork ||
249 - latestSuspendedTime < completedExpirationTime)
250 - ) {
251 - // The lowest priority suspended work is the work most likely to be
252 - // committed next. Let's start rendering it again, so that if it times out,
253 - // it's ready to commit.
254 - nextExpirationTimeToWorkOn = latestSuspendedTime;
255 - }
256 -
257 - let expirationTime = nextExpirationTimeToWorkOn;
258 - if (expirationTime !== NoWork && earliestSuspendedTime > expirationTime) {
259 - // Expire using the earliest known expiration time.
260 - expirationTime = earliestSuspendedTime;
261 - }
262 -
263 - root.nextExpirationTimeToWorkOn = nextExpirationTimeToWorkOn;
264 - root.expirationTime = expirationTime;
265 -}
packages/react-reconciler/src/ReactFiberRoot.js
+11 -50
@@ -16,10 +16,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
16 import {noTimeout} from './ReactFiberHostConfig';
17 import {createHostRootFiber} from './ReactFiber';
18 import {NoWork} from './ReactFiberExpirationTime';
19 -import {
20 - enableSchedulerTracing,
21 - enableNewScheduler,
22 -} from 'shared/ReactFeatureFlags';
19 +import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
20 import {unstable_getThreadID} from 'scheduler/tracing';
21
22 // TODO: This should be lifted into the renderer.
@@ -40,31 +37,11 @@ type BaseFiberRootProperties = {|
37 // The currently active root fiber. This is the mutable root of the tree.
38 current: Fiber,
39
43 - // The following priority levels are used to distinguish between 1)
44 - // uncommitted work, 2) uncommitted work that is suspended, and 3) uncommitted
45 - // work that may be unsuspended. We choose not to track each individual
46 - // pending level, trading granularity for performance.
47 - //
48 - // The earliest and latest priority levels that are suspended from committing.
49 - earliestSuspendedTime: ExpirationTime,
50 - latestSuspendedTime: ExpirationTime,
51 - // The earliest and latest priority levels that are not known to be suspended.
52 - earliestPendingTime: ExpirationTime,
53 - latestPendingTime: ExpirationTime,
54 - // The latest priority level that was pinged by a resolved promise and can
55 - // be retried.
56 - latestPingedTime: ExpirationTime,
57 -
40 pingCache:
41 | WeakMap<Thenable, Set<ExpirationTime>>
42 | Map<Thenable, Set<ExpirationTime>>
43 | null,
44
63 - // If an error is thrown, and there are no more updates in the queue, we try
64 - // rendering from the root one more time, synchronously, before handling
65 - // the error.
66 - didError: boolean,
67 -
45 pendingCommitExpirationTime: ExpirationTime,
46 // A finished work-in-progress HostRoot that's ready to be committed.
47 finishedWork: Fiber | null,
@@ -76,22 +53,19 @@ type BaseFiberRootProperties = {|
53 pendingContext: Object | null,
54 // Determines if we should attempt to hydrate on the initial mount
55 +hydrate: boolean,
79 - // Remaining expiration time on this root.
80 - // TODO: Lift this into the renderer
81 - nextExpirationTimeToWorkOn: ExpirationTime,
82 - expirationTime: ExpirationTime,
56 // List of top-level batches. This list indicates whether a commit should be
57 // deferred. Also contains completion callbacks.
58 // TODO: Lift this into the renderer
59 firstBatch: Batch | null,
87 - // Linked-list of roots
88 - nextScheduledRoot: FiberRoot | null,
89 -
90 - // New Scheduler fields
60 + // Node returned by Scheduler.scheduleCallback
61 callbackNode: *,
62 + // Expiration of the callback associated with this root
63 callbackExpirationTime: ExpirationTime,
64 + // The earliest pending expiration time that exists in the tree
65 firstPendingTime: ExpirationTime,
66 + // The latest pending expiration time that exists in the tree
67 lastPendingTime: ExpirationTime,
68 + // The time at which a suspended component pinged the root to render again
69 pingTime: ExpirationTime,
70 |};
71
@@ -127,24 +101,11 @@ function FiberRootNode(containerInfo, hydrate) {
101 this.pendingContext = null;
102 this.hydrate = hydrate;
103 this.firstBatch = null;
130 -
131 - if (enableNewScheduler) {
132 - this.callbackNode = null;
133 - this.callbackExpirationTime = NoWork;
134 - this.firstPendingTime = NoWork;
135 - this.lastPendingTime = NoWork;
136 - this.pingTime = NoWork;
137 - } else {
138 - this.earliestPendingTime = NoWork;
139 - this.latestPendingTime = NoWork;
140 - this.earliestSuspendedTime = NoWork;
141 - this.latestSuspendedTime = NoWork;
142 - this.latestPingedTime = NoWork;
143 - this.didError = false;
144 - this.nextExpirationTimeToWorkOn = NoWork;
145 - this.expirationTime = NoWork;
146 - this.nextScheduledRoot = null;
147 - }
104 + this.callbackNode = null;
105 + this.callbackExpirationTime = NoWork;
106 + this.firstPendingTime = NoWork;
107 + this.lastPendingTime = NoWork;
108 + this.pingTime = NoWork;
109
110 if (enableSchedulerTracing) {
111 this.interactionThreadID = unstable_getThreadID();
packages/react-reconciler/src/ReactFiberScheduler.js
+2228 -126
@@ -7,136 +7,2238 @@
7 * @flow
8 */
9
10 -import {enableNewScheduler} from 'shared/ReactFeatureFlags';
10 +import type {Fiber} from './ReactFiber';
11 +import type {FiberRoot} from './ReactFiberRoot';
12 +import type {ExpirationTime} from './ReactFiberExpirationTime';
13 +import type {
14 + ReactPriorityLevel,
15 + SchedulerCallback,
16 +} from './SchedulerWithReactIntegration';
17 +import type {Interaction} from 'scheduler/src/Tracing';
18
19 import {
13 - requestCurrentTime as requestCurrentTime_old,
14 - computeExpirationForFiber as computeExpirationForFiber_old,
15 - captureCommitPhaseError as captureCommitPhaseError_old,
16 - onUncaughtError as onUncaughtError_old,
17 - markRenderEventTime as markRenderEventTime_old,
18 - renderDidSuspend as renderDidSuspend_old,
19 - renderDidError as renderDidError_old,
20 - pingSuspendedRoot as pingSuspendedRoot_old,
21 - retryTimedOutBoundary as retryTimedOutBoundary_old,
22 - resolveRetryThenable as resolveRetryThenable_old,
23 - markLegacyErrorBoundaryAsFailed as markLegacyErrorBoundaryAsFailed_old,
24 - isAlreadyFailedLegacyErrorBoundary as isAlreadyFailedLegacyErrorBoundary_old,
25 - scheduleWork as scheduleWork_old,
26 - flushRoot as flushRoot_old,
27 - batchedUpdates as batchedUpdates_old,
28 - unbatchedUpdates as unbatchedUpdates_old,
29 - flushSync as flushSync_old,
30 - flushControlled as flushControlled_old,
31 - deferredUpdates as deferredUpdates_old,
32 - syncUpdates as syncUpdates_old,
33 - interactiveUpdates as interactiveUpdates_old,
34 - flushInteractiveUpdates as flushInteractiveUpdates_old,
35 - computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_old,
36 - flushPassiveEffects as flushPassiveEffects_old,
37 - warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_old,
38 -} from './ReactFiberScheduler.old';
20 + warnAboutDeprecatedLifecycles,
21 + enableUserTimingAPI,
22 + enableSuspenseServerRenderer,
23 + replayFailedUnitOfWorkWithInvokeGuardedCallback,
24 + enableProfilerTimer,
25 + disableYielding,
26 + enableSchedulerTracing,
27 +} from 'shared/ReactFeatureFlags';
28 +import ReactSharedInternals from 'shared/ReactSharedInternals';
29 +import invariant from 'shared/invariant';
30 +import warning from 'shared/warning';
31
32 import {
41 - requestCurrentTime as requestCurrentTime_new,
42 - computeExpirationForFiber as computeExpirationForFiber_new,
43 - captureCommitPhaseError as captureCommitPhaseError_new,
44 - onUncaughtError as onUncaughtError_new,
45 - markRenderEventTime as markRenderEventTime_new,
46 - renderDidSuspend as renderDidSuspend_new,
47 - renderDidError as renderDidError_new,
48 - pingSuspendedRoot as pingSuspendedRoot_new,
49 - retryTimedOutBoundary as retryTimedOutBoundary_new,
50 - resolveRetryThenable as resolveRetryThenable_new,
51 - markLegacyErrorBoundaryAsFailed as markLegacyErrorBoundaryAsFailed_new,
52 - isAlreadyFailedLegacyErrorBoundary as isAlreadyFailedLegacyErrorBoundary_new,
53 - scheduleWork as scheduleWork_new,
54 - flushRoot as flushRoot_new,
55 - batchedUpdates as batchedUpdates_new,
56 - unbatchedUpdates as unbatchedUpdates_new,
57 - flushSync as flushSync_new,
58 - flushControlled as flushControlled_new,
59 - deferredUpdates as deferredUpdates_new,
60 - syncUpdates as syncUpdates_new,
61 - interactiveUpdates as interactiveUpdates_new,
62 - flushInteractiveUpdates as flushInteractiveUpdates_new,
63 - computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_new,
64 - flushPassiveEffects as flushPassiveEffects_new,
65 - warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_new,
66 -} from './ReactFiberScheduler.new';
67 -
68 -export const requestCurrentTime = enableNewScheduler
69 - ? requestCurrentTime_new
70 - : requestCurrentTime_old;
71 -export const computeExpirationForFiber = enableNewScheduler
72 - ? computeExpirationForFiber_new
73 - : computeExpirationForFiber_old;
74 -export const captureCommitPhaseError = enableNewScheduler
75 - ? captureCommitPhaseError_new
76 - : captureCommitPhaseError_old;
77 -export const onUncaughtError = enableNewScheduler
78 - ? onUncaughtError_new
79 - : onUncaughtError_old;
80 -export const markRenderEventTime = enableNewScheduler
81 - ? markRenderEventTime_new
82 - : markRenderEventTime_old;
83 -export const renderDidSuspend = enableNewScheduler
84 - ? renderDidSuspend_new
85 - : renderDidSuspend_old;
86 -export const renderDidError = enableNewScheduler
87 - ? renderDidError_new
88 - : renderDidError_old;
89 -export const pingSuspendedRoot = enableNewScheduler
90 - ? pingSuspendedRoot_new
91 - : pingSuspendedRoot_old;
92 -export const retryTimedOutBoundary = enableNewScheduler
93 - ? retryTimedOutBoundary_new
94 - : retryTimedOutBoundary_old;
95 -export const resolveRetryThenable = enableNewScheduler
96 - ? resolveRetryThenable_new
97 - : resolveRetryThenable_old;
98 -export const markLegacyErrorBoundaryAsFailed = enableNewScheduler
99 - ? markLegacyErrorBoundaryAsFailed_new
100 - : markLegacyErrorBoundaryAsFailed_old;
101 -export const isAlreadyFailedLegacyErrorBoundary = enableNewScheduler
102 - ? isAlreadyFailedLegacyErrorBoundary_new
103 - : isAlreadyFailedLegacyErrorBoundary_old;
104 -export const scheduleWork = enableNewScheduler
105 - ? scheduleWork_new
106 - : scheduleWork_old;
107 -export const flushRoot = enableNewScheduler ? flushRoot_new : flushRoot_old;
108 -export const batchedUpdates = enableNewScheduler
109 - ? batchedUpdates_new
110 - : batchedUpdates_old;
111 -export const unbatchedUpdates = enableNewScheduler
112 - ? unbatchedUpdates_new
113 - : unbatchedUpdates_old;
114 -export const flushSync = enableNewScheduler ? flushSync_new : flushSync_old;
115 -export const flushControlled = enableNewScheduler
116 - ? flushControlled_new
117 - : flushControlled_old;
118 -export const deferredUpdates = enableNewScheduler
119 - ? deferredUpdates_new
120 - : deferredUpdates_old;
121 -export const syncUpdates = enableNewScheduler
122 - ? syncUpdates_new
123 - : syncUpdates_old;
124 -export const interactiveUpdates = enableNewScheduler
125 - ? interactiveUpdates_new
126 - : interactiveUpdates_old;
127 -export const flushInteractiveUpdates = enableNewScheduler
128 - ? flushInteractiveUpdates_new
129 - : flushInteractiveUpdates_old;
130 -export const computeUniqueAsyncExpiration = enableNewScheduler
131 - ? computeUniqueAsyncExpiration_new
132 - : computeUniqueAsyncExpiration_old;
133 -export const flushPassiveEffects = enableNewScheduler
134 - ? flushPassiveEffects_new
135 - : flushPassiveEffects_old;
136 -export const warnIfNotCurrentlyActingUpdatesInDev = enableNewScheduler
137 - ? warnIfNotCurrentlyActingUpdatesInDev_new
138 - : warnIfNotCurrentlyActingUpdatesInDev_old;
33 + scheduleCallback,
34 + cancelCallback,
35 + getCurrentPriorityLevel,
36 + runWithPriority,
37 + shouldYield,
38 + now,
39 + ImmediatePriority,
40 + UserBlockingPriority,
41 + NormalPriority,
42 + LowPriority,
43 + IdlePriority,
44 + flushImmediateQueue,
45 +} from './SchedulerWithReactIntegration';
46 +
47 +import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
48 +
49 +import {
50 + prepareForCommit,
51 + resetAfterCommit,
52 + scheduleTimeout,
53 + cancelTimeout,
54 + noTimeout,
55 +} from './ReactFiberHostConfig';
56 +
57 +import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
58 +import {NoContext, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
59 +import {
60 + HostRoot,
61 + ClassComponent,
62 + SuspenseComponent,
63 + DehydratedSuspenseComponent,
64 + FunctionComponent,
65 + ForwardRef,
66 + MemoComponent,
67 + SimpleMemoComponent,
68 +} from 'shared/ReactWorkTags';
69 +import {
70 + NoEffect,
71 + PerformedWork,
72 + Placement,
73 + Update,
74 + PlacementAndUpdate,
75 + Deletion,
76 + Ref,
77 + ContentReset,
78 + Snapshot,
79 + Callback,
80 + Passive,
81 + Incomplete,
82 + HostEffectMask,
83 +} from 'shared/ReactSideEffectTags';
84 +import {
85 + NoWork,
86 + Sync,
87 + Never,
88 + msToExpirationTime,
89 + expirationTimeToMs,
90 + computeInteractiveExpiration,
91 + computeAsyncExpiration,
92 + inferPriorityFromExpirationTime,
93 + LOW_PRIORITY_EXPIRATION,
94 +} from './ReactFiberExpirationTime';
95 +import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
96 +import {completeWork} from './ReactFiberCompleteWork';
97 +import {
98 + throwException,
99 + unwindWork,
100 + unwindInterruptedWork,
101 + createRootErrorUpdate,
102 + createClassErrorUpdate,
103 +} from './ReactFiberUnwindWork';
104 +import {
105 + commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
106 + commitLifeCycles as commitLayoutEffectOnFiber,
107 + commitPassiveHookEffects,
108 + commitPlacement,
109 + commitWork,
110 + commitDeletion,
111 + commitDetachRef,
112 + commitAttachRef,
113 + commitResetTextContent,
114 +} from './ReactFiberCommitWork';
115 +import {enqueueUpdate} from './ReactUpdateQueue';
116 +// TODO: Ahaha Andrew is bad at spellling
117 +import {resetContextDependences as resetContextDependencies} from './ReactFiberNewContext';
118 +import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
119 +import {createCapturedValue} from './ReactCapturedValue';
120 +
121 +import {
122 + recordCommitTime,
123 + startProfilerTimer,
124 + stopProfilerTimerIfRunningAndRecordDelta,
125 +} from './ReactProfilerTimer';
126 +
127 +// DEV stuff
128 +import warningWithoutStack from 'shared/warningWithoutStack';
129 +import getComponentName from 'shared/getComponentName';
130 +import ReactStrictModeWarnings from './ReactStrictModeWarnings';
131 +import {
132 + phase as ReactCurrentDebugFiberPhaseInDEV,
133 + resetCurrentFiber as resetCurrentDebugFiberInDEV,
134 + setCurrentFiber as setCurrentDebugFiberInDEV,
135 + getStackByFiberInDevAndProd,
136 +} from './ReactCurrentFiber';
137 +import {
138 + recordEffect,
139 + recordScheduleUpdate,
140 + startRequestCallbackTimer,
141 + stopRequestCallbackTimer,
142 + startWorkTimer,
143 + stopWorkTimer,
144 + stopFailedWorkTimer,
145 + startWorkLoopTimer,
146 + stopWorkLoopTimer,
147 + startCommitTimer,
148 + stopCommitTimer,
149 + startCommitSnapshotEffectsTimer,
150 + stopCommitSnapshotEffectsTimer,
151 + startCommitHostEffectsTimer,
152 + stopCommitHostEffectsTimer,
153 + startCommitLifeCyclesTimer,
154 + stopCommitLifeCyclesTimer,
155 +} from './ReactDebugFiberPerf';
156 +import {
157 + invokeGuardedCallback,
158 + hasCaughtError,
159 + clearCaughtError,
160 +} from 'shared/ReactErrorUtils';
161 +import {onCommitRoot} from './ReactFiberDevToolsHook';
162 +
163 +const ceil = Math.ceil;
164 +
165 +const {
166 + ReactCurrentDispatcher,
167 + ReactCurrentOwner,
168 + ReactShouldWarnActingUpdates,
169 +} = ReactSharedInternals;
170 +
171 +type WorkPhase = 0 | 1 | 2 | 3 | 4 | 5;
172 +const NotWorking = 0;
173 +const BatchedPhase = 1;
174 +const LegacyUnbatchedPhase = 2;
175 +const FlushSyncPhase = 3;
176 +const RenderPhase = 4;
177 +const CommitPhase = 5;
178 +
179 +type RootExitStatus = 0 | 1 | 2 | 3;
180 +const RootIncomplete = 0;
181 +const RootErrored = 1;
182 +const RootSuspended = 2;
183 +const RootCompleted = 3;
184
185 export type Thenable = {
141 - then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
186 + then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
187 };
188 +
189 +// The phase of work we're currently in
190 +let workPhase: WorkPhase = NotWorking;
191 +// The root we're working on
192 +let workInProgressRoot: FiberRoot | null = null;
193 +// The fiber we're working on
194 +let workInProgress: Fiber | null = null;
195 +// The expiration time we're rendering
196 +let renderExpirationTime: ExpirationTime = NoWork;
197 +// Whether to root completed, errored, suspended, etc.
198 +let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
199 +// Most recent event time among processed updates during this render.
200 +// This is conceptually a time stamp but expressed in terms of an ExpirationTime
201 +// because we deal mostly with expiration times in the hot path, so this avoids
202 +// the conversion happening in the hot path.
203 +let workInProgressRootMostRecentEventTime: ExpirationTime = Sync;
204 +
205 +let nextEffect: Fiber | null = null;
206 +let hasUncaughtError = false;
207 +let firstUncaughtError = null;
208 +let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
209 +
210 +let rootDoesHavePassiveEffects: boolean = false;
211 +let rootWithPendingPassiveEffects: FiberRoot | null = null;
212 +let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
213 +
214 +let rootsWithPendingDiscreteUpdates: Map<
215 + FiberRoot,
216 + ExpirationTime,
217 +> | null = null;
218 +
219 +// Use these to prevent an infinite loop of nested updates
220 +const NESTED_UPDATE_LIMIT = 50;
221 +let nestedUpdateCount: number = 0;
222 +let rootWithNestedUpdates: FiberRoot | null = null;
223 +
224 +const NESTED_PASSIVE_UPDATE_LIMIT = 50;
225 +let nestedPassiveUpdateCount: number = 0;
226 +
227 +let interruptedBy: Fiber | null = null;
228 +
229 +// Expiration times are computed by adding to the current time (the start
230 +// time). However, if two updates are scheduled within the same event, we
231 +// should treat their start times as simultaneous, even if the actual clock
232 +// time has advanced between the first and second call.
233 +
234 +// In other words, because expiration times determine how updates are batched,
235 +// we want all updates of like priority that occur within the same event to
236 +// receive the same expiration time. Otherwise we get tearing.
237 +let initialTimeMs: number = now();
238 +let currentEventTime: ExpirationTime = NoWork;
239 +
240 +export function requestCurrentTime() {
241 + if (workPhase === RenderPhase || workPhase === CommitPhase) {
242 + // We're inside React, so it's fine to read the actual time.
243 + return msToExpirationTime(now() - initialTimeMs);
244 + }
245 + // We're not inside React, so we may be in the middle of a browser event.
246 + if (currentEventTime !== NoWork) {
247 + // Use the same start time for all updates until we enter React again.
248 + return currentEventTime;
249 + }
250 + // This is the first update since React yielded. Compute a new start time.
251 + currentEventTime = msToExpirationTime(now() - initialTimeMs);
252 + return currentEventTime;
253 +}
254 +
255 +export function computeExpirationForFiber(
256 + currentTime: ExpirationTime,
257 + fiber: Fiber,
258 +): ExpirationTime {
259 + if ((fiber.mode & ConcurrentMode) === NoContext) {
260 + return Sync;
261 + }
262 +
263 + if (workPhase === RenderPhase) {
264 + // Use whatever time we're already rendering
265 + return renderExpirationTime;
266 + }
267 +
268 + // Compute an expiration time based on the Scheduler priority.
269 + let expirationTime;
270 + const priorityLevel = getCurrentPriorityLevel();
271 + switch (priorityLevel) {
272 + case ImmediatePriority:
273 + expirationTime = Sync;
274 + break;
275 + case UserBlockingPriority:
276 + // TODO: Rename this to computeUserBlockingExpiration
277 + expirationTime = computeInteractiveExpiration(currentTime);
278 + break;
279 + case NormalPriority:
280 + case LowPriority: // TODO: Handle LowPriority
281 + // TODO: Rename this to... something better.
282 + expirationTime = computeAsyncExpiration(currentTime);
283 + break;
284 + case IdlePriority:
285 + expirationTime = Never;
286 + break;
287 + default:
288 + invariant(false, 'Expected a valid priority level');
289 + }
290 +
291 + // If we're in the middle of rendering a tree, do not update at the same
292 + // expiration time that is already rendering.
293 + if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
294 + // This is a trick to move this update into a separate batch
295 + expirationTime -= 1;
296 + }
297 +
298 + return expirationTime;
299 +}
300 +
301 +let lastUniqueAsyncExpiration = NoWork;
302 +export function computeUniqueAsyncExpiration(): ExpirationTime {
303 + const currentTime = requestCurrentTime();
304 + let result = computeAsyncExpiration(currentTime);
305 + if (result <= lastUniqueAsyncExpiration) {
306 + // Since we assume the current time monotonically increases, we only hit
307 + // this branch when computeUniqueAsyncExpiration is fired multiple times
308 + // within a 200ms window (or whatever the async bucket size is).
309 + result -= 1;
310 + }
311 + lastUniqueAsyncExpiration = result;
312 + return result;
313 +}
314 +
315 +export function scheduleUpdateOnFiber(
316 + fiber: Fiber,
317 + expirationTime: ExpirationTime,
318 +) {
319 + checkForNestedUpdates();
320 + warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
321 +
322 + const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
323 + if (root === null) {
324 + warnAboutUpdateOnUnmountedFiberInDEV(fiber);
325 + return;
326 + }
327 +
328 + root.pingTime = NoWork;
329 +
330 + checkForInterruption(fiber, expirationTime);
331 + recordScheduleUpdate();
332 +
333 + if (expirationTime === Sync) {
334 + if (workPhase === LegacyUnbatchedPhase) {
335 + // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
336 + // root inside of batchedUpdates should be synchronous, but layout updates
337 + // should be deferred until the end of the batch.
338 + let callback = renderRoot(root, Sync, true);
339 + while (callback !== null) {
340 + callback = callback(true);
341 + }
342 + } else {
343 + scheduleCallbackForRoot(root, ImmediatePriority, Sync);
344 + if (workPhase === NotWorking) {
345 + // Flush the synchronous work now, wnless we're already working or inside
346 + // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
347 + // scheduleCallbackForFiber to preserve the ability to schedule a callback
348 + // without immediately flushing it. We only do this for user-initated
349 + // updates, to preserve historical behavior of sync mode.
350 + flushImmediateQueue();
351 + }
352 + }
353 + } else {
354 + // TODO: computeExpirationForFiber also reads the priority. Pass the
355 + // priority as an argument to that function and this one.
356 + const priorityLevel = getCurrentPriorityLevel();
357 + if (priorityLevel === UserBlockingPriority) {
358 + // This is the result of a discrete event. Track the lowest priority
359 + // discrete update per root so we can flush them early, if needed.
360 + if (rootsWithPendingDiscreteUpdates === null) {
361 + rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
362 + } else {
363 + const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
364 + if (
365 + lastDiscreteTime === undefined ||
366 + lastDiscreteTime > expirationTime
367 + ) {
368 + rootsWithPendingDiscreteUpdates.set(root, expirationTime);
369 + }
370 + }
371 + }
372 + scheduleCallbackForRoot(root, priorityLevel, expirationTime);
373 + }
374 +}
375 +export const scheduleWork = scheduleUpdateOnFiber;
376 +
377 +// This is split into a separate function so we can mark a fiber with pending
378 +// work without treating it as a typical update that originates from an event;
379 +// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
380 +// on a fiber.
381 +function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
382 + // Update the source fiber's expiration time
383 + if (fiber.expirationTime < expirationTime) {
384 + fiber.expirationTime = expirationTime;
385 + }
386 + let alternate = fiber.alternate;
387 + if (alternate !== null && alternate.expirationTime < expirationTime) {
388 + alternate.expirationTime = expirationTime;
389 + }
390 + // Walk the parent path to the root and update the child expiration time.
391 + let node = fiber.return;
392 + let root = null;
393 + if (node === null && fiber.tag === HostRoot) {
394 + root = fiber.stateNode;
395 + } else {
396 + while (node !== null) {
397 + alternate = node.alternate;
398 + if (node.childExpirationTime < expirationTime) {
399 + node.childExpirationTime = expirationTime;
400 + if (
401 + alternate !== null &&
402 + alternate.childExpirationTime < expirationTime
403 + ) {
404 + alternate.childExpirationTime = expirationTime;
405 + }
406 + } else if (
407 + alternate !== null &&
408 + alternate.childExpirationTime < expirationTime
409 + ) {
410 + alternate.childExpirationTime = expirationTime;
411 + }
412 + if (node.return === null && node.tag === HostRoot) {
413 + root = node.stateNode;
414 + break;
415 + }
416 + node = node.return;
417 + }
418 + }
419 +
420 + if (root !== null) {
421 + // Update the first and last pending expiration times in this root
422 + const firstPendingTime = root.firstPendingTime;
423 + if (expirationTime > firstPendingTime) {
424 + root.firstPendingTime = expirationTime;
425 + }
426 + const lastPendingTime = root.lastPendingTime;
427 + if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
428 + root.lastPendingTime = expirationTime;
429 + }
430 + }
431 +
432 + return root;
433 +}
434 +
435 +// Use this function, along with runRootCallback, to ensure that only a single
436 +// callback per root is scheduled. It's still possible to call renderRoot
437 +// directly, but scheduling via this function helps avoid excessive callbacks.
438 +// It works by storing the callback node and expiration time on the root. When a
439 +// new callback comes in, it compares the expiration time to determine if it
440 +// should cancel the previous one. It also relies on commitRoot scheduling a
441 +// callback to render the next level, because that means we don't need a
442 +// separate callback per expiration time.
443 +function scheduleCallbackForRoot(
444 + root: FiberRoot,
445 + priorityLevel: ReactPriorityLevel,
446 + expirationTime: ExpirationTime,
447 +) {
448 + const existingCallbackExpirationTime = root.callbackExpirationTime;
449 + if (existingCallbackExpirationTime < expirationTime) {
450 + // New callback has higher priority than the existing one.
451 + const existingCallbackNode = root.callbackNode;
452 + if (existingCallbackNode !== null) {
453 + cancelCallback(existingCallbackNode);
454 + }
455 + root.callbackExpirationTime = expirationTime;
456 + const options =
457 + expirationTime === Sync
458 + ? null
459 + : {timeout: expirationTimeToMs(expirationTime)};
460 + root.callbackNode = scheduleCallback(
461 + priorityLevel,
462 + runRootCallback.bind(
463 + null,
464 + root,
465 + renderRoot.bind(null, root, expirationTime),
466 + ),
467 + options,
468 + );
469 + if (
470 + enableUserTimingAPI &&
471 + expirationTime !== Sync &&
472 + workPhase !== RenderPhase &&
473 + workPhase !== CommitPhase
474 + ) {
475 + // Scheduled an async callback, and we're not already working. Add an
476 + // entry to the flamegraph that shows we're waiting for a callback
477 + // to fire.
478 + startRequestCallbackTimer();
479 + }
480 + }
481 +
482 + const timeoutHandle = root.timeoutHandle;
483 + if (timeoutHandle !== noTimeout) {
484 + // The root previous suspended and scheduled a timeout to commit a fallback
485 + // state. Now that we have additional work, cancel the timeout.
486 + root.timeoutHandle = noTimeout;
487 + // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
488 + cancelTimeout(timeoutHandle);
489 + }
490 +
491 + // Add the current set of interactions to the pending set associated with
492 + // this root.
493 + schedulePendingInteraction(root, expirationTime);
494 +}
495 +
496 +function runRootCallback(root, callback, isSync) {
497 + const prevCallbackNode = root.callbackNode;
498 + let continuation = null;
499 + try {
500 + continuation = callback(isSync);
501 + if (continuation !== null) {
502 + return runRootCallback.bind(null, root, continuation);
503 + } else {
504 + return null;
505 + }
506 + } finally {
507 + // If the callback exits without returning a continuation, remove the
508 + // corresponding callback node from the root. Unless the callback node
509 + // has changed, which implies that it was already cancelled by a high
510 + // priority update.
511 + if (continuation === null && prevCallbackNode === root.callbackNode) {
512 + root.callbackNode = null;
513 + root.callbackExpirationTime = NoWork;
514 + }
515 + }
516 +}
517 +
518 +export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
519 + if (workPhase === RenderPhase || workPhase === CommitPhase) {
520 + invariant(
521 + false,
522 + 'work.commit(): Cannot commit while already rendering. This likely ' +
523 + 'means you attempted to commit from inside a lifecycle method.',
524 + );
525 + }
526 + scheduleCallback(
527 + ImmediatePriority,
528 + renderRoot.bind(null, root, expirationTime),
529 + );
530 + flushImmediateQueue();
531 +}
532 +
533 +export function flushInteractiveUpdates() {
534 + if (workPhase === RenderPhase || workPhase === CommitPhase) {
535 + // Can't synchronously flush interactive updates if React is already
536 + // working. This is currently a no-op.
537 + // TODO: Should we fire a warning? This happens if you synchronously invoke
538 + // an input event inside an effect, like with `element.click()`.
539 + return;
540 + }
541 + flushPendingDiscreteUpdates();
542 +}
543 +
544 +function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {
545 + const firstBatch = root.firstBatch;
546 + if (
547 + firstBatch !== null &&
548 + firstBatch._defer &&
549 + firstBatch._expirationTime >= expirationTime
550 + ) {
551 + root.finishedWork = root.current.alternate;
552 + root.pendingCommitExpirationTime = expirationTime;
553 + scheduleCallback(NormalPriority, () => {
554 + firstBatch._onComplete();
555 + return null;
556 + });
557 + return true;
558 + } else {
559 + return false;
560 + }
561 +}
562 +
563 +export function deferredUpdates<A>(fn: () => A): A {
564 + // TODO: Remove in favor of Scheduler.next
565 + return runWithPriority(NormalPriority, fn);
566 +}
567 +
568 +export function interactiveUpdates<A, B, C, R>(
569 + fn: (A, B, C) => R,
570 + a: A,
571 + b: B,
572 + c: C,
573 +): R {
574 + if (workPhase === NotWorking) {
575 + // TODO: Remove this call. Instead of doing this automatically, the caller
576 + // should explicitly call flushInteractiveUpdates.
577 + flushPendingDiscreteUpdates();
578 + }
579 + return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));
580 +}
581 +
582 +export function syncUpdates<A, B, C, R>(
583 + fn: (A, B, C) => R,
584 + a: A,
585 + b: B,
586 + c: C,
587 +): R {
588 + return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
589 +}
590 +
591 +function flushPendingDiscreteUpdates() {
592 + if (rootsWithPendingDiscreteUpdates !== null) {
593 + // For each root with pending discrete updates, schedule a callback to
594 + // immediately flush them.
595 + const roots = rootsWithPendingDiscreteUpdates;
596 + rootsWithPendingDiscreteUpdates = null;
597 + roots.forEach((expirationTime, root) => {
598 + scheduleCallback(
599 + ImmediatePriority,
600 + renderRoot.bind(null, root, expirationTime),
601 + );
602 + });
603 + // Now flush the immediate queue.
604 + flushImmediateQueue();
605 + }
606 +}
607 +
608 +export function batchedUpdates<A, R>(fn: A => R, a: A): R {
609 + if (workPhase !== NotWorking) {
610 + // We're already working, or inside a batch, so batchedUpdates is a no-op.
611 + return fn(a);
612 + }
613 + workPhase = BatchedPhase;
614 + try {
615 + return fn(a);
616 + } finally {
617 + workPhase = NotWorking;
618 + // Flush the immediate callbacks that were scheduled during this batch
619 + flushImmediateQueue();
620 + }
621 +}
622 +
623 +export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
624 + if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {
625 + // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is
626 + // a no-op.
627 + return fn(a);
628 + }
629 + const prevWorkPhase = workPhase;
630 + workPhase = LegacyUnbatchedPhase;
631 + try {
632 + return fn(a);
633 + } finally {
634 + workPhase = prevWorkPhase;
635 + }
636 +}
637 +
638 +export function flushSync<A, R>(fn: A => R, a: A): R {
639 + if (workPhase === RenderPhase || workPhase === CommitPhase) {
640 + invariant(
641 + false,
642 + 'flushSync was called from inside a lifecycle method. It cannot be ' +
643 + 'called when React is already rendering.',
644 + );
645 + }
646 + const prevWorkPhase = workPhase;
647 + workPhase = FlushSyncPhase;
648 + try {
649 + return runWithPriority(ImmediatePriority, fn.bind(null, a));
650 + } finally {
651 + workPhase = prevWorkPhase;
652 + // Flush the immediate callbacks that were scheduled during this batch.
653 + // Note that this will happen even if batchedUpdates is higher up
654 + // the stack.
655 + flushImmediateQueue();
656 + }
657 +}
658 +
659 +export function flushControlled(fn: () => mixed): void {
660 + const prevWorkPhase = workPhase;
661 + workPhase = BatchedPhase;
662 + try {
663 + runWithPriority(ImmediatePriority, fn);
664 + } finally {
665 + workPhase = prevWorkPhase;
666 + if (workPhase === NotWorking) {
667 + // Flush the immediate callbacks that were scheduled during this batch
668 + flushImmediateQueue();
669 + }
670 + }
671 +}
672 +
673 +function prepareFreshStack(root, expirationTime) {
674 + root.pendingCommitExpirationTime = NoWork;
675 +
676 + if (workInProgress !== null) {
677 + let interruptedWork = workInProgress.return;
678 + while (interruptedWork !== null) {
679 + unwindInterruptedWork(interruptedWork);
680 + interruptedWork = interruptedWork.return;
681 + }
682 + }
683 + workInProgressRoot = root;
684 + workInProgress = createWorkInProgress(root.current, null, expirationTime);
685 + renderExpirationTime = expirationTime;
686 + workInProgressRootExitStatus = RootIncomplete;
687 + workInProgressRootMostRecentEventTime = Sync;
688 +
689 + if (__DEV__) {
690 + ReactStrictModeWarnings.discardPendingWarnings();
691 + }
692 +}
693 +
694 +function renderRoot(
695 + root: FiberRoot,
696 + expirationTime: ExpirationTime,
697 + isSync: boolean,
698 +): SchedulerCallback | null {
699 + invariant(
700 + workPhase !== RenderPhase && workPhase !== CommitPhase,
701 + 'Should not already be working.',
702 + );
703 +
704 + if (enableUserTimingAPI && expirationTime !== Sync) {
705 + const didExpire = isSync;
706 + const timeoutMs = expirationTimeToMs(expirationTime);
707 + stopRequestCallbackTimer(didExpire, timeoutMs);
708 + }
709 +
710 + if (root.firstPendingTime < expirationTime) {
711 + // If there's no work left at this expiration time, exit immediately. This
712 + // happens when multiple callbacks are scheduled for a single root, but an
713 + // earlier callback flushes the work of a later one.
714 + return null;
715 + }
716 +
717 + if (root.pendingCommitExpirationTime === expirationTime) {
718 + // There's already a pending commit at this expiration time.
719 + root.pendingCommitExpirationTime = NoWork;
720 + return commitRoot.bind(null, root, expirationTime);
721 + }
722 +
723 + flushPassiveEffects();
724 +
725 + // If the root or expiration time have changed, throw out the existing stack
726 + // and prepare a fresh one. Otherwise we'll continue where we left off.
727 + if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
728 + prepareFreshStack(root, expirationTime);
729 + startWorkOnPendingInteraction(root, expirationTime);
730 + }
731 +
732 + // If we have a work-in-progress fiber, it means there's still work to do
733 + // in this root.
734 + if (workInProgress !== null) {
735 + const prevWorkPhase = workPhase;
736 + workPhase = RenderPhase;
737 + let prevDispatcher = ReactCurrentDispatcher.current;
738 + if (prevDispatcher === null) {
739 + // The React isomorphic package does not include a default dispatcher.
740 + // Instead the first renderer will lazily attach one, in order to give
741 + // nicer error messages.
742 + prevDispatcher = ContextOnlyDispatcher;
743 + }
744 + ReactCurrentDispatcher.current = ContextOnlyDispatcher;
745 + let prevInteractions: Set<Interaction> | null = null;
746 + if (enableSchedulerTracing) {
747 + prevInteractions = __interactionsRef.current;
748 + __interactionsRef.current = root.memoizedInteractions;
749 + }
750 +
751 + startWorkLoopTimer(workInProgress);
752 +
753 + // TODO: Fork renderRoot into renderRootSync and renderRootAsync
754 + if (isSync) {
755 + if (expirationTime !== Sync) {
756 + // An async update expired. There may be other expired updates on
757 + // this root. We should render all the expired work in a
758 + // single batch.
759 + const currentTime = requestCurrentTime();
760 + if (currentTime < expirationTime) {
761 + // Restart at the current time.
762 + workPhase = prevWorkPhase;
763 + resetContextDependencies();
764 + ReactCurrentDispatcher.current = prevDispatcher;
765 + if (enableSchedulerTracing) {
766 + __interactionsRef.current = ((prevInteractions: any): Set<
767 + Interaction,
768 + >);
769 + }
770 + return renderRoot.bind(null, root, currentTime);
771 + }
772 + }
773 + } else {
774 + // Since we know we're in a React event, we can clear the current
775 + // event time. The next update will compute a new event time.
776 + currentEventTime = NoWork;
777 + }
778 +
779 + do {
780 + try {
781 + if (isSync) {
782 + workLoopSync();
783 + } else {
784 + workLoop();
785 + }
786 + break;
787 + } catch (thrownValue) {
788 + // Reset module-level state that was set during the render phase.
789 + resetContextDependencies();
790 + resetHooks();
791 +
792 + const sourceFiber = workInProgress;
793 + if (sourceFiber === null || sourceFiber.return === null) {
794 + // Expected to be working on a non-root fiber. This is a fatal error
795 + // because there's no ancestor that can handle it; the root is
796 + // supposed to capture all errors that weren't caught by an error
797 + // boundary.
798 + prepareFreshStack(root, expirationTime);
799 + workPhase = prevWorkPhase;
800 + throw thrownValue;
801 + }
802 +
803 + if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
804 + // Record the time spent rendering before an error was thrown. This
805 + // avoids inaccurate Profiler durations in the case of a
806 + // suspended render.
807 + stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
808 + }
809 +
810 + const returnFiber = sourceFiber.return;
811 + throwException(
812 + root,
813 + returnFiber,
814 + sourceFiber,
815 + thrownValue,
816 + renderExpirationTime,
817 + );
818 + workInProgress = completeUnitOfWork(sourceFiber);
819 + }
820 + } while (true);
821 +
822 + workPhase = prevWorkPhase;
823 + resetContextDependencies();
824 + ReactCurrentDispatcher.current = prevDispatcher;
825 + if (enableSchedulerTracing) {
826 + __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
827 + }
828 +
829 + if (workInProgress !== null) {
830 + // There's still work left over. Return a continuation.
831 + stopInterruptedWorkLoopTimer();
832 + if (expirationTime !== Sync) {
833 + startRequestCallbackTimer();
834 + }
835 + return renderRoot.bind(null, root, expirationTime);
836 + }
837 + }
838 +
839 + // We now have a consistent tree. The next step is either to commit it, or, if
840 + // something suspended, wait to commit it after a timeout.
841 + stopFinishedWorkLoopTimer();
842 +
843 + const isLocked = resolveLocksOnRoot(root, expirationTime);
844 + if (isLocked) {
845 + // This root has a lock that prevents it from committing. Exit. If we begin
846 + // work on the root again, without any intervening updates, it will finish
847 + // without doing additional work.
848 + return null;
849 + }
850 +
851 + // Set this to null to indicate there's no in-progress render.
852 + workInProgressRoot = null;
853 +
854 + switch (workInProgressRootExitStatus) {
855 + case RootIncomplete: {
856 + invariant(false, 'Should have a work-in-progress.');
857 + }
858 + // Flow knows about invariant, so it compains if I add a break statement,
859 + // but eslint doesn't know about invariant, so it complains if I do.
860 + // eslint-disable-next-line no-fallthrough
861 + case RootErrored: {
862 + // An error was thrown. First check if there is lower priority work
863 + // scheduled on this root.
864 + const lastPendingTime = root.lastPendingTime;
865 + if (root.lastPendingTime < expirationTime) {
866 + // There's lower priority work. Before raising the error, try rendering
867 + // at the lower priority to see if it fixes it. Use a continuation to
868 + // maintain the existing priority and position in the queue.
869 + return renderRoot.bind(null, root, lastPendingTime);
870 + }
871 + if (!isSync) {
872 + // If we're rendering asynchronously, it's possible the error was
873 + // caused by tearing due to a mutation during an event. Try rendering
874 + // one more time without yiedling to events.
875 + prepareFreshStack(root, expirationTime);
876 + scheduleCallback(
877 + ImmediatePriority,
878 + renderRoot.bind(null, root, expirationTime),
879 + );
880 + return null;
881 + }
882 + // If we're already rendering synchronously, commit the root in its
883 + // errored state.
884 + return commitRoot.bind(null, root, expirationTime);
885 + }
886 + case RootSuspended: {
887 + if (!isSync) {
888 + const lastPendingTime = root.lastPendingTime;
889 + if (root.lastPendingTime < expirationTime) {
890 + // There's lower priority work. It might be unsuspended. Try rendering
891 + // at that level.
892 + return renderRoot.bind(null, root, lastPendingTime);
893 + }
894 + // If workInProgressRootMostRecentEventTime is Sync, that means we didn't
895 + // track any event times. That can happen if we retried but nothing switched
896 + // from fallback to content. There's no reason to delay doing no work.
897 + if (workInProgressRootMostRecentEventTime !== Sync) {
898 + let msUntilTimeout = computeMsUntilTimeout(
899 + workInProgressRootMostRecentEventTime,
900 + expirationTime,
901 + );
902 + // Don't bother with a very short suspense time.
903 + if (msUntilTimeout > 10) {
904 + // The render is suspended, it hasn't timed out, and there's no lower
905 + // priority work to do. Instead of committing the fallback
906 + // immediately, wait for more data to arrive.
907 + root.timeoutHandle = scheduleTimeout(
908 + commitRoot.bind(null, root, expirationTime),
909 + msUntilTimeout,
910 + );
911 + return null;
912 + }
913 + }
914 + }
915 + // The work expired. Commit immediately.
916 + return commitRoot.bind(null, root, expirationTime);
917 + }
918 + case RootCompleted: {
919 + // The work completed. Ready to commit.
920 + return commitRoot.bind(null, root, expirationTime);
921 + }
922 + default: {
923 + invariant(false, 'Unknown root exit status.');
924 + }
925 + }
926 +}
927 +
928 +export function markRenderEventTime(expirationTime: ExpirationTime): void {
929 + if (expirationTime < workInProgressRootMostRecentEventTime) {
930 + workInProgressRootMostRecentEventTime = expirationTime;
931 + }
932 +}
933 +
934 +export function renderDidSuspend(): void {
935 + if (workInProgressRootExitStatus === RootIncomplete) {
936 + workInProgressRootExitStatus = RootSuspended;
937 + }
938 +}
939 +
940 +export function renderDidError() {
941 + if (
942 + workInProgressRootExitStatus === RootIncomplete ||
943 + workInProgressRootExitStatus === RootSuspended
944 + ) {
945 + workInProgressRootExitStatus = RootErrored;
946 + }
947 +}
948 +
949 +function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {
950 + // We don't know exactly when the update was scheduled, but we can infer an
951 + // approximate start time from the expiration time.
952 + const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
953 + return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION + initialTimeMs;
954 +}
955 +
956 +function workLoopSync() {
957 + // Already timed out, so perform work without checking if we need to yield.
958 + while (workInProgress !== null) {
959 + workInProgress = performUnitOfWork(workInProgress);
960 + }
961 +}
962 +
963 +function workLoop() {
964 + // Perform work until Scheduler asks us to yield
965 + while (workInProgress !== null && !shouldYield()) {
966 + workInProgress = performUnitOfWork(workInProgress);
967 + }
968 +}
969 +
970 +function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
971 + // The current, flushed, state of this fiber is the alternate. Ideally
972 + // nothing should rely on this, but relying on it here means that we don't
973 + // need an additional field on the work in progress.
974 + const current = unitOfWork.alternate;
975 +
976 + startWorkTimer(unitOfWork);
977 + setCurrentDebugFiberInDEV(unitOfWork);
978 +
979 + let next;
980 + if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {
981 + startProfilerTimer(unitOfWork);
982 + next = beginWork(current, unitOfWork, renderExpirationTime);
983 + stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
984 + } else {
985 + next = beginWork(current, unitOfWork, renderExpirationTime);
986 + }
987 +
988 + resetCurrentDebugFiberInDEV();
989 + unitOfWork.memoizedProps = unitOfWork.pendingProps;
990 + if (next === null) {
991 + // If this doesn't spawn new work, complete the current work.
992 + next = completeUnitOfWork(unitOfWork);
993 + }
994 +
995 + ReactCurrentOwner.current = null;
996 + return next;
997 +}
998 +
999 +function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1000 + // Attempt to complete the current unit of work, then move to the next
1001 + // sibling. If there are no more siblings, return to the parent fiber.
1002 + workInProgress = unitOfWork;
1003 + do {
1004 + // The current, flushed, state of this fiber is the alternate. Ideally
1005 + // nothing should rely on this, but relying on it here means that we don't
1006 + // need an additional field on the work in progress.
1007 + const current = workInProgress.alternate;
1008 + const returnFiber = workInProgress.return;
1009 +
1010 + // Check if the work completed or if something threw.
1011 + if ((workInProgress.effectTag & Incomplete) === NoEffect) {
1012 + setCurrentDebugFiberInDEV(workInProgress);
1013 + let next;
1014 + if (
1015 + !enableProfilerTimer ||
1016 + (workInProgress.mode & ProfileMode) === NoContext
1017 + ) {
1018 + next = completeWork(current, workInProgress, renderExpirationTime);
1019 + } else {
1020 + startProfilerTimer(workInProgress);
1021 + next = completeWork(current, workInProgress, renderExpirationTime);
1022 + // Update render duration assuming we didn't error.
1023 + stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1024 + }
1025 + stopWorkTimer(workInProgress);
1026 + resetCurrentDebugFiberInDEV();
1027 + resetChildExpirationTime(workInProgress);
1028 +
1029 + if (next !== null) {
1030 + // Completing this fiber spawned new work. Work on that next.
1031 + return next;
1032 + }
1033 +
1034 + if (
1035 + returnFiber !== null &&
1036 + // Do not append effects to parents if a sibling failed to complete
1037 + (returnFiber.effectTag & Incomplete) === NoEffect
1038 + ) {
1039 + // Append all the effects of the subtree and this fiber onto the effect
1040 + // list of the parent. The completion order of the children affects the
1041 + // side-effect order.
1042 + if (returnFiber.firstEffect === null) {
1043 + returnFiber.firstEffect = workInProgress.firstEffect;
1044 + }
1045 + if (workInProgress.lastEffect !== null) {
1046 + if (returnFiber.lastEffect !== null) {
1047 + returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1048 + }
1049 + returnFiber.lastEffect = workInProgress.lastEffect;
1050 + }
1051 +
1052 + // If this fiber had side-effects, we append it AFTER the children's
1053 + // side-effects. We can perform certain side-effects earlier if needed,
1054 + // by doing multiple passes over the effect list. We don't want to
1055 + // schedule our own side-effect on our own list because if end up
1056 + // reusing children we'll schedule this effect onto itself since we're
1057 + // at the end.
1058 + const effectTag = workInProgress.effectTag;
1059 +
1060 + // Skip both NoWork and PerformedWork tags when creating the effect
1061 + // list. PerformedWork effect is read by React DevTools but shouldn't be
1062 + // committed.
1063 + if (effectTag > PerformedWork) {
1064 + if (returnFiber.lastEffect !== null) {
1065 + returnFiber.lastEffect.nextEffect = workInProgress;
1066 + } else {
1067 + returnFiber.firstEffect = workInProgress;
1068 + }
1069 + returnFiber.lastEffect = workInProgress;
1070 + }
1071 + }
1072 + } else {
1073 + // This fiber did not complete because something threw. Pop values off
1074 + // the stack without entering the complete phase. If this is a boundary,
1075 + // capture values if possible.
1076 + const next = unwindWork(workInProgress, renderExpirationTime);
1077 +
1078 + // Because this fiber did not complete, don't reset its expiration time.
1079 +
1080 + if (
1081 + enableProfilerTimer &&
1082 + (workInProgress.mode & ProfileMode) !== NoContext
1083 + ) {
1084 + // Record the render duration for the fiber that errored.
1085 + stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1086 +
1087 + // Include the time spent working on failed children before continuing.
1088 + let actualDuration = workInProgress.actualDuration;
1089 + let child = workInProgress.child;
1090 + while (child !== null) {
1091 + actualDuration += child.actualDuration;
1092 + child = child.sibling;
1093 + }
1094 + workInProgress.actualDuration = actualDuration;
1095 + }
1096 +
1097 + if (next !== null) {
1098 + // If completing this work spawned new work, do that next. We'll come
1099 + // back here again.
1100 + // Since we're restarting, remove anything that is not a host effect
1101 + // from the effect tag.
1102 + // TODO: The name stopFailedWorkTimer is misleading because Suspense
1103 + // also captures and restarts.
1104 + stopFailedWorkTimer(workInProgress);
1105 + next.effectTag &= HostEffectMask;
1106 + return next;
1107 + }
1108 + stopWorkTimer(workInProgress);
1109 +
1110 + if (returnFiber !== null) {
1111 + // Mark the parent fiber as incomplete and clear its effect list.
1112 + returnFiber.firstEffect = returnFiber.lastEffect = null;
1113 + returnFiber.effectTag |= Incomplete;
1114 + }
1115 + }
1116 +
1117 + const siblingFiber = workInProgress.sibling;
1118 + if (siblingFiber !== null) {
1119 + // If there is more work to do in this returnFiber, do that next.
1120 + return siblingFiber;
1121 + }
1122 + // Otherwise, return to the parent
1123 + workInProgress = returnFiber;
1124 + } while (workInProgress !== null);
1125 +
1126 + // We've reached the root.
1127 + if (workInProgressRootExitStatus === RootIncomplete) {
1128 + workInProgressRootExitStatus = RootCompleted;
1129 + }
1130 + return null;
1131 +}
1132 +
1133 +function resetChildExpirationTime(completedWork: Fiber) {
1134 + if (
1135 + renderExpirationTime !== Never &&
1136 + completedWork.childExpirationTime === Never
1137 + ) {
1138 + // The children of this component are hidden. Don't bubble their
1139 + // expiration times.
1140 + return;
1141 + }
1142 +
1143 + let newChildExpirationTime = NoWork;
1144 +
1145 + // Bubble up the earliest expiration time.
1146 + if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {
1147 + // In profiling mode, resetChildExpirationTime is also used to reset
1148 + // profiler durations.
1149 + let actualDuration = completedWork.actualDuration;
1150 + let treeBaseDuration = completedWork.selfBaseDuration;
1151 +
1152 + // When a fiber is cloned, its actualDuration is reset to 0. This value will
1153 + // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1154 + // When work is done, it should bubble to the parent's actualDuration. If
1155 + // the fiber has not been cloned though, (meaning no work was done), then
1156 + // this value will reflect the amount of time spent working on a previous
1157 + // render. In that case it should not bubble. We determine whether it was
1158 + // cloned by comparing the child pointer.
1159 + const shouldBubbleActualDurations =
1160 + completedWork.alternate === null ||
1161 + completedWork.child !== completedWork.alternate.child;
1162 +
1163 + let child = completedWork.child;
1164 + while (child !== null) {
1165 + const childUpdateExpirationTime = child.expirationTime;
1166 + const childChildExpirationTime = child.childExpirationTime;
1167 + if (childUpdateExpirationTime > newChildExpirationTime) {
1168 + newChildExpirationTime = childUpdateExpirationTime;
1169 + }
1170 + if (childChildExpirationTime > newChildExpirationTime) {
1171 + newChildExpirationTime = childChildExpirationTime;
1172 + }
1173 + if (shouldBubbleActualDurations) {
1174 + actualDuration += child.actualDuration;
1175 + }
1176 + treeBaseDuration += child.treeBaseDuration;
1177 + child = child.sibling;
1178 + }
1179 + completedWork.actualDuration = actualDuration;
1180 + completedWork.treeBaseDuration = treeBaseDuration;
1181 + } else {
1182 + let child = completedWork.child;
1183 + while (child !== null) {
1184 + const childUpdateExpirationTime = child.expirationTime;
1185 + const childChildExpirationTime = child.childExpirationTime;
1186 + if (childUpdateExpirationTime > newChildExpirationTime) {
1187 + newChildExpirationTime = childUpdateExpirationTime;
1188 + }
1189 + if (childChildExpirationTime > newChildExpirationTime) {
1190 + newChildExpirationTime = childChildExpirationTime;
1191 + }
1192 + child = child.sibling;
1193 + }
1194 + }
1195 +
1196 + completedWork.childExpirationTime = newChildExpirationTime;
1197 +}
1198 +
1199 +function commitRoot(root, expirationTime) {
1200 + runWithPriority(
1201 + ImmediatePriority,
1202 + commitRootImpl.bind(null, root, expirationTime),
1203 + );
1204 + // If there are passive effects, schedule a callback to flush them. This goes
1205 + // outside commitRootImpl so that it inherits the priority of the render.
1206 + if (rootWithPendingPassiveEffects !== null) {
1207 + const priorityLevel = getCurrentPriorityLevel();
1208 + scheduleCallback(priorityLevel, () => {
1209 + flushPassiveEffects();
1210 + return null;
1211 + });
1212 + }
1213 + return null;
1214 +}
1215 +
1216 +function commitRootImpl(root, expirationTime) {
1217 + flushPassiveEffects();
1218 + flushRenderPhaseStrictModeWarningsInDEV();
1219 +
1220 + invariant(
1221 + workPhase !== RenderPhase && workPhase !== CommitPhase,
1222 + 'Should not already be working.',
1223 + );
1224 + const finishedWork = root.current.alternate;
1225 + invariant(finishedWork !== null, 'Should have a work-in-progress root.');
1226 +
1227 + // commitRoot never returns a continuation; it always finishes synchronously.
1228 + // So we can clear these now to allow a new callback to be scheduled.
1229 + root.callbackNode = null;
1230 + root.callbackExpirationTime = NoWork;
1231 +
1232 + startCommitTimer();
1233 +
1234 + // Update the first and last pending times on this root. The new first
1235 + // pending time is whatever is left on the root fiber.
1236 + const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
1237 + const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
1238 + const firstPendingTimeBeforeCommit =
1239 + childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
1240 + ? childExpirationTimeBeforeCommit
1241 + : updateExpirationTimeBeforeCommit;
1242 + root.firstPendingTime = firstPendingTimeBeforeCommit;
1243 + if (firstPendingTimeBeforeCommit < root.lastPendingTime) {
1244 + // This usually means we've finished all the work, but it can also happen
1245 + // when something gets downprioritized during render, like a hidden tree.
1246 + root.lastPendingTime = firstPendingTimeBeforeCommit;
1247 + }
1248 +
1249 + if (root === workInProgressRoot) {
1250 + // We can reset these now that they are finished.
1251 + workInProgressRoot = null;
1252 + workInProgress = null;
1253 + renderExpirationTime = NoWork;
1254 + } else {
1255 + // This indicates that the last root we worked on is not the same one that
1256 + // we're committing now. This most commonly happens when a suspended root
1257 + // times out.
1258 + }
1259 +
1260 + // Get the list of effects.
1261 + let firstEffect;
1262 + if (finishedWork.effectTag > PerformedWork) {
1263 + // A fiber's effect list consists only of its children, not itself. So if
1264 + // the root has an effect, we need to add it to the end of the list. The
1265 + // resulting list is the set that would belong to the root's parent, if it
1266 + // had one; that is, all the effects in the tree including the root.
1267 + if (finishedWork.lastEffect !== null) {
1268 + finishedWork.lastEffect.nextEffect = finishedWork;
1269 + firstEffect = finishedWork.firstEffect;
1270 + } else {
1271 + firstEffect = finishedWork;
1272 + }
1273 + } else {
1274 + // There is no effect on the root.
1275 + firstEffect = finishedWork.firstEffect;
1276 + }
1277 +
1278 + if (firstEffect !== null) {
1279 + const prevWorkPhase = workPhase;
1280 + workPhase = CommitPhase;
1281 + let prevInteractions: Set<Interaction> | null = null;
1282 + if (enableSchedulerTracing) {
1283 + prevInteractions = __interactionsRef.current;
1284 + __interactionsRef.current = root.memoizedInteractions;
1285 + }
1286 +
1287 + // Reset this to null before calling lifecycles
1288 + ReactCurrentOwner.current = null;
1289 +
1290 + // The commit phase is broken into several sub-phases. We do a separate pass
1291 + // of the effect list for each phase: all mutation effects come before all
1292 + // layout effects, and so on.
1293 +
1294 + // The first phase a "before mutation" phase. We use this phase to read the
1295 + // state of the host tree right before we mutate it. This is where
1296 + // getSnapshotBeforeUpdate is called.
1297 + startCommitSnapshotEffectsTimer();
1298 + prepareForCommit(root.containerInfo);
1299 + nextEffect = firstEffect;
1300 + do {
1301 + if (__DEV__) {
1302 + invokeGuardedCallback(null, commitBeforeMutationEffects, null);
1303 + if (hasCaughtError()) {
1304 + invariant(nextEffect !== null, 'Should be working on an effect.');
1305 + const error = clearCaughtError();
1306 + captureCommitPhaseError(nextEffect, error);
1307 + nextEffect = nextEffect.nextEffect;
1308 + }
1309 + } else {
1310 + try {
1311 + commitBeforeMutationEffects();
1312 + } catch (error) {
1313 + invariant(nextEffect !== null, 'Should be working on an effect.');
1314 + captureCommitPhaseError(nextEffect, error);
1315 + nextEffect = nextEffect.nextEffect;
1316 + }
1317 + }
1318 + } while (nextEffect !== null);
1319 + stopCommitSnapshotEffectsTimer();
1320 +
1321 + if (enableProfilerTimer) {
1322 + // Mark the current commit time to be shared by all Profilers in this
1323 + // batch. This enables them to be grouped later.
1324 + recordCommitTime();
1325 + }
1326 +
1327 + // The next phase is the mutation phase, where we mutate the host tree.
1328 + startCommitHostEffectsTimer();
1329 + nextEffect = firstEffect;
1330 + do {
1331 + if (__DEV__) {
1332 + invokeGuardedCallback(null, commitMutationEffects, null);
1333 + if (hasCaughtError()) {
1334 + invariant(nextEffect !== null, 'Should be working on an effect.');
1335 + const error = clearCaughtError();
1336 + captureCommitPhaseError(nextEffect, error);
1337 + nextEffect = nextEffect.nextEffect;
1338 + }
1339 + } else {
1340 + try {
1341 + commitMutationEffects();
1342 + } catch (error) {
1343 + invariant(nextEffect !== null, 'Should be working on an effect.');
1344 + captureCommitPhaseError(nextEffect, error);
1345 + nextEffect = nextEffect.nextEffect;
1346 + }
1347 + }
1348 + } while (nextEffect !== null);
1349 + stopCommitHostEffectsTimer();
1350 + resetAfterCommit(root.containerInfo);
1351 +
1352 + // The work-in-progress tree is now the current tree. This must come after
1353 + // the mutation phase, so that the previous tree is still current during
1354 + // componentWillUnmount, but before the layout phase, so that the finished
1355 + // work is current during componentDidMount/Update.
1356 + root.current = finishedWork;
1357 +
1358 + // The next phase is the layout phase, where we call effects that read
1359 + // the host tree after it's been mutated. The idiomatic use case for this is
1360 + // layout, but class component lifecycles also fire here for legacy reasons.
1361 + startCommitLifeCyclesTimer();
1362 + nextEffect = firstEffect;
1363 + do {
1364 + if (__DEV__) {
1365 + invokeGuardedCallback(
1366 + null,
1367 + commitLayoutEffects,
1368 + null,
1369 + root,
1370 + expirationTime,
1371 + );
1372 + if (hasCaughtError()) {
1373 + invariant(nextEffect !== null, 'Should be working on an effect.');
1374 + const error = clearCaughtError();
1375 + captureCommitPhaseError(nextEffect, error);
1376 + nextEffect = nextEffect.nextEffect;
1377 + }
1378 + } else {
1379 + try {
1380 + commitLayoutEffects(root, expirationTime);
1381 + } catch (error) {
1382 + invariant(nextEffect !== null, 'Should be working on an effect.');
1383 + captureCommitPhaseError(nextEffect, error);
1384 + nextEffect = nextEffect.nextEffect;
1385 + }
1386 + }
1387 + } while (nextEffect !== null);
1388 + stopCommitLifeCyclesTimer();
1389 +
1390 + nextEffect = null;
1391 +
1392 + if (enableSchedulerTracing) {
1393 + __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
1394 + }
1395 + workPhase = prevWorkPhase;
1396 + } else {
1397 + // No effects.
1398 + root.current = finishedWork;
1399 + // Measure these anyway so the flamegraph explicitly shows that there were
1400 + // no effects.
1401 + // TODO: Maybe there's a better way to report this.
1402 + startCommitSnapshotEffectsTimer();
1403 + stopCommitSnapshotEffectsTimer();
1404 + if (enableProfilerTimer) {
1405 + recordCommitTime();
1406 + }
1407 + startCommitHostEffectsTimer();
1408 + stopCommitHostEffectsTimer();
1409 + startCommitLifeCyclesTimer();
1410 + stopCommitLifeCyclesTimer();
1411 + }
1412 +
1413 + stopCommitTimer();
1414 +
1415 + if (rootDoesHavePassiveEffects) {
1416 + // This commit has passive effects. Stash a reference to them. But don't
1417 + // schedule a callback until after flushing layout work.
1418 + rootDoesHavePassiveEffects = false;
1419 + rootWithPendingPassiveEffects = root;
1420 + pendingPassiveEffectsExpirationTime = expirationTime;
1421 + } else {
1422 + if (enableSchedulerTracing) {
1423 + // If there are no passive effects, then we can complete the pending
1424 + // interactions. Otherwise, we'll wait until after the passive effects
1425 + // are flushed.
1426 + finishPendingInteractions(root, expirationTime);
1427 + }
1428 + }
1429 +
1430 + // Check if there's remaining work on this root
1431 + const remainingExpirationTime = root.firstPendingTime;
1432 + if (remainingExpirationTime !== NoWork) {
1433 + const currentTime = requestCurrentTime();
1434 + const priorityLevel = inferPriorityFromExpirationTime(
1435 + currentTime,
1436 + remainingExpirationTime,
1437 + );
1438 + scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);
1439 + } else {
1440 + // If there's no remaining work, we can clear the set of already failed
1441 + // error boundaries.
1442 + legacyErrorBoundariesThatAlreadyFailed = null;
1443 + }
1444 +
1445 + onCommitRoot(finishedWork.stateNode);
1446 +
1447 + if (remainingExpirationTime === Sync) {
1448 + // Count the number of times the root synchronously re-renders without
1449 + // finishing. If there are too many, it indicates an infinite update loop.
1450 + if (root === rootWithNestedUpdates) {
1451 + nestedUpdateCount++;
1452 + } else {
1453 + nestedUpdateCount = 0;
1454 + rootWithNestedUpdates = root;
1455 + }
1456 + } else {
1457 + nestedUpdateCount = 0;
1458 + }
1459 +
1460 + if (hasUncaughtError) {
1461 + hasUncaughtError = false;
1462 + const error = firstUncaughtError;
1463 + firstUncaughtError = null;
1464 + throw error;
1465 + }
1466 +
1467 + if (workPhase === LegacyUnbatchedPhase) {
1468 + // This is a legacy edge case. We just committed the initial mount of
1469 + // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
1470 + // synchronously, but layout updates should be deferred until the end
1471 + // of the batch.
1472 + return null;
1473 + }
1474 +
1475 + // If layout work was scheduled, flush it now.
1476 + flushImmediateQueue();
1477 + return null;
1478 +}
1479 +
1480 +function commitBeforeMutationEffects() {
1481 + while (nextEffect !== null) {
1482 + if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
1483 + setCurrentDebugFiberInDEV(nextEffect);
1484 + recordEffect();
1485 +
1486 + const current = nextEffect.alternate;
1487 + commitBeforeMutationEffectOnFiber(current, nextEffect);
1488 +
1489 + resetCurrentDebugFiberInDEV();
1490 + }
1491 + nextEffect = nextEffect.nextEffect;
1492 + }
1493 +}
1494 +
1495 +function commitMutationEffects() {
1496 + // TODO: Should probably move the bulk of this function to commitWork.
1497 + while (nextEffect !== null) {
1498 + setCurrentDebugFiberInDEV(nextEffect);
1499 +
1500 + const effectTag = nextEffect.effectTag;
1501 +
1502 + if (effectTag & ContentReset) {
1503 + commitResetTextContent(nextEffect);
1504 + }
1505 +
1506 + if (effectTag & Ref) {
1507 + const current = nextEffect.alternate;
1508 + if (current !== null) {
1509 + commitDetachRef(current);
1510 + }
1511 + }
1512 +
1513 + // The following switch statement is only concerned about placement,
1514 + // updates, and deletions. To avoid needing to add a case for every possible
1515 + // bitmap value, we remove the secondary effects from the effect tag and
1516 + // switch on that value.
1517 + let primaryEffectTag = effectTag & (Placement | Update | Deletion);
1518 + switch (primaryEffectTag) {
1519 + case Placement: {
1520 + commitPlacement(nextEffect);
1521 + // Clear the "placement" from effect tag so that we know that this is
1522 + // inserted, before any life-cycles like componentDidMount gets called.
1523 + // TODO: findDOMNode doesn't rely on this any more but isMounted does
1524 + // and isMounted is deprecated anyway so we should be able to kill this.
1525 + nextEffect.effectTag &= ~Placement;
1526 + break;
1527 + }
1528 + case PlacementAndUpdate: {
1529 + // Placement
1530 + commitPlacement(nextEffect);
1531 + // Clear the "placement" from effect tag so that we know that this is
1532 + // inserted, before any life-cycles like componentDidMount gets called.
1533 + nextEffect.effectTag &= ~Placement;
1534 +
1535 + // Update
1536 + const current = nextEffect.alternate;
1537 + commitWork(current, nextEffect);
1538 + break;
1539 + }
1540 + case Update: {
1541 + const current = nextEffect.alternate;
1542 + commitWork(current, nextEffect);
1543 + break;
1544 + }
1545 + case Deletion: {
1546 + commitDeletion(nextEffect);
1547 + break;
1548 + }
1549 + }
1550 +
1551 + // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
1552 + recordEffect();
1553 +
1554 + resetCurrentDebugFiberInDEV();
1555 + nextEffect = nextEffect.nextEffect;
1556 + }
1557 +}
1558 +
1559 +function commitLayoutEffects(
1560 + root: FiberRoot,
1561 + committedExpirationTime: ExpirationTime,
1562 +) {
1563 + // TODO: Should probably move the bulk of this function to commitWork.
1564 + while (nextEffect !== null) {
1565 + setCurrentDebugFiberInDEV(nextEffect);
1566 +
1567 + const effectTag = nextEffect.effectTag;
1568 +
1569 + if (effectTag & (Update | Callback)) {
1570 + recordEffect();
1571 + const current = nextEffect.alternate;
1572 + commitLayoutEffectOnFiber(
1573 + root,
1574 + current,
1575 + nextEffect,
1576 + committedExpirationTime,
1577 + );
1578 + }
1579 +
1580 + if (effectTag & Ref) {
1581 + recordEffect();
1582 + commitAttachRef(nextEffect);
1583 + }
1584 +
1585 + if (effectTag & Passive) {
1586 + rootDoesHavePassiveEffects = true;
1587 + }
1588 +
1589 + resetCurrentDebugFiberInDEV();
1590 + nextEffect = nextEffect.nextEffect;
1591 + }
1592 +}
1593 +
1594 +export function flushPassiveEffects() {
1595 + if (rootWithPendingPassiveEffects === null) {
1596 + return false;
1597 + }
1598 + const root = rootWithPendingPassiveEffects;
1599 + const expirationTime = pendingPassiveEffectsExpirationTime;
1600 + rootWithPendingPassiveEffects = null;
1601 + pendingPassiveEffectsExpirationTime = NoWork;
1602 +
1603 + let prevInteractions: Set<Interaction> | null = null;
1604 + if (enableSchedulerTracing) {
1605 + prevInteractions = __interactionsRef.current;
1606 + __interactionsRef.current = root.memoizedInteractions;
1607 + }
1608 +
1609 + invariant(
1610 + workPhase !== RenderPhase && workPhase !== CommitPhase,
1611 + 'Cannot flush passive effects while already rendering.',
1612 + );
1613 + const prevWorkPhase = workPhase;
1614 + workPhase = CommitPhase;
1615 +
1616 + // Note: This currently assumes there are no passive effects on the root
1617 + // fiber, because the root is not part of its own effect list. This could
1618 + // change in the future.
1619 + let effect = root.current.firstEffect;
1620 + while (effect !== null) {
1621 + if (__DEV__) {
1622 + setCurrentDebugFiberInDEV(effect);
1623 + invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
1624 + if (hasCaughtError()) {
1625 + invariant(effect !== null, 'Should be working on an effect.');
1626 + const error = clearCaughtError();
1627 + captureCommitPhaseError(effect, error);
1628 + }
1629 + resetCurrentDebugFiberInDEV();
1630 + } else {
1631 + try {
1632 + commitPassiveHookEffects(effect);
1633 + } catch (error) {
1634 + invariant(effect !== null, 'Should be working on an effect.');
1635 + captureCommitPhaseError(effect, error);
1636 + }
1637 + }
1638 + effect = effect.nextEffect;
1639 + }
1640 +
1641 + if (enableSchedulerTracing) {
1642 + __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
1643 + finishPendingInteractions(root, expirationTime);
1644 + }
1645 +
1646 + workPhase = prevWorkPhase;
1647 + flushImmediateQueue();
1648 +
1649 + // If additional passive effects were scheduled, increment a counter. If this
1650 + // exceeds the limit, we'll fire a warning.
1651 + nestedPassiveUpdateCount =
1652 + rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
1653 +
1654 + return true;
1655 +}
1656 +
1657 +export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
1658 + return (
1659 + legacyErrorBoundariesThatAlreadyFailed !== null &&
1660 + legacyErrorBoundariesThatAlreadyFailed.has(instance)
1661 + );
1662 +}
1663 +
1664 +export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
1665 + if (legacyErrorBoundariesThatAlreadyFailed === null) {
1666 + legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
1667 + } else {
1668 + legacyErrorBoundariesThatAlreadyFailed.add(instance);
1669 + }
1670 +}
1671 +
1672 +function prepareToThrowUncaughtError(error: mixed) {
1673 + if (!hasUncaughtError) {
1674 + hasUncaughtError = true;
1675 + firstUncaughtError = error;
1676 + }
1677 +}
1678 +export const onUncaughtError = prepareToThrowUncaughtError;
1679 +
1680 +function captureCommitPhaseErrorOnRoot(
1681 + rootFiber: Fiber,
1682 + sourceFiber: Fiber,
1683 + error: mixed,
1684 +) {
1685 + const errorInfo = createCapturedValue(error, sourceFiber);
1686 + const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
1687 + enqueueUpdate(rootFiber, update);
1688 + const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
1689 + if (root !== null) {
1690 + scheduleCallbackForRoot(root, ImmediatePriority, Sync);
1691 + }
1692 +}
1693 +
1694 +export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
1695 + if (sourceFiber.tag === HostRoot) {
1696 + // Error was thrown at the root. There is no parent, so the root
1697 + // itself should capture it.
1698 + captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
1699 + return;
1700 + }
1701 +
1702 + let fiber = sourceFiber.return;
1703 + while (fiber !== null) {
1704 + if (fiber.tag === HostRoot) {
1705 + captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
1706 + return;
1707 + } else if (fiber.tag === ClassComponent) {
1708 + const ctor = fiber.type;
1709 + const instance = fiber.stateNode;
1710 + if (
1711 + typeof ctor.getDerivedStateFromError === 'function' ||
1712 + (typeof instance.componentDidCatch === 'function' &&
1713 + !isAlreadyFailedLegacyErrorBoundary(instance))
1714 + ) {
1715 + const errorInfo = createCapturedValue(error, sourceFiber);
1716 + const update = createClassErrorUpdate(
1717 + fiber,
1718 + errorInfo,
1719 + // TODO: This is always sync
1720 + Sync,
1721 + );
1722 + enqueueUpdate(fiber, update);
1723 + const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
1724 + if (root !== null) {
1725 + scheduleCallbackForRoot(root, ImmediatePriority, Sync);
1726 + }
1727 + return;
1728 + }
1729 + }
1730 + fiber = fiber.return;
1731 + }
1732 +}
1733 +
1734 +export function pingSuspendedRoot(
1735 + root: FiberRoot,
1736 + thenable: Thenable,
1737 + suspendedTime: ExpirationTime,
1738 +) {
1739 + const pingCache = root.pingCache;
1740 + if (pingCache !== null) {
1741 + // The thenable resolved, so we no longer need to memoize, because it will
1742 + // never be thrown again.
1743 + pingCache.delete(thenable);
1744 + }
1745 +
1746 + if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
1747 + // Received a ping at the same priority level at which we're currently
1748 + // rendering. Restart from the root. Don't need to schedule a ping because
1749 + // we're already working on this tree.
1750 + prepareFreshStack(root, renderExpirationTime);
1751 + return;
1752 + }
1753 +
1754 + const lastPendingTime = root.lastPendingTime;
1755 + if (lastPendingTime < suspendedTime) {
1756 + // The root is no longer suspended at this time.
1757 + return;
1758 + }
1759 +
1760 + const pingTime = root.pingTime;
1761 + if (pingTime !== NoWork && pingTime < suspendedTime) {
1762 + // There's already a lower priority ping scheduled.
1763 + return;
1764 + }
1765 +
1766 + // Mark the time at which this ping was scheduled.
1767 + root.pingTime = suspendedTime;
1768 +
1769 + const currentTime = requestCurrentTime();
1770 + const priorityLevel = inferPriorityFromExpirationTime(
1771 + currentTime,
1772 + suspendedTime,
1773 + );
1774 + scheduleCallbackForRoot(root, priorityLevel, suspendedTime);
1775 +}
1776 +
1777 +export function retryTimedOutBoundary(boundaryFiber: Fiber) {
1778 + // The boundary fiber (a Suspense component) previously timed out and was
1779 + // rendered in its fallback state. One of the promises that suspended it has
1780 + // resolved, which means at least part of the tree was likely unblocked. Try
1781 + // rendering again, at a new expiration time.
1782 + const currentTime = requestCurrentTime();
1783 + const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
1784 + // TODO: Special case idle priority?
1785 + const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);
1786 + const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
1787 + if (root !== null) {
1788 + scheduleCallbackForRoot(root, priorityLevel, retryTime);
1789 + }
1790 +}
1791 +
1792 +export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
1793 + let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
1794 + if (enableSuspenseServerRenderer) {
1795 + switch (boundaryFiber.tag) {
1796 + case SuspenseComponent:
1797 + retryCache = boundaryFiber.stateNode;
1798 + break;
1799 + case DehydratedSuspenseComponent:
1800 + retryCache = boundaryFiber.memoizedState;
1801 + break;
1802 + default:
1803 + invariant(
1804 + false,
1805 + 'Pinged unknown suspense boundary type. ' +
1806 + 'This is probably a bug in React.',
1807 + );
1808 + }
1809 + } else {
1810 + retryCache = boundaryFiber.stateNode;
1811 + }
1812 +
1813 + if (retryCache !== null) {
1814 + // The thenable resolved, so we no longer need to memoize, because it will
1815 + // never be thrown again.
1816 + retryCache.delete(thenable);
1817 + }
1818 +
1819 + retryTimedOutBoundary(boundaryFiber);
1820 +}
1821 +
1822 +// Computes the next Just Noticeable Difference (JND) boundary.
1823 +// The theory is that a person can't tell the difference between small differences in time.
1824 +// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
1825 +// difference in the experience. However, waiting for longer might mean that we can avoid
1826 +// showing an intermediate loading state. The longer we have already waited, the harder it
1827 +// is to tell small differences in time. Therefore, the longer we've already waited,
1828 +// the longer we can wait additionally. At some point we have to give up though.
1829 +// We pick a train model where the next boundary commits at a consistent schedule.
1830 +// These particular numbers are vague estimates. We expect to adjust them based on research.
1831 +function jnd(timeElapsed: number) {
1832 + return timeElapsed < 120
1833 + ? 120
1834 + : timeElapsed < 480
1835 + ? 480
1836 + : timeElapsed < 1080
1837 + ? 1080
1838 + : timeElapsed < 1920
1839 + ? 1920
1840 + : timeElapsed < 3000
1841 + ? 3000
1842 + : timeElapsed < 4320
1843 + ? 4320
1844 + : ceil(timeElapsed / 1960) * 1960;
1845 +}
1846 +
1847 +function computeMsUntilTimeout(
1848 + mostRecentEventTime: ExpirationTime,
1849 + committedExpirationTime: ExpirationTime,
1850 +) {
1851 + if (disableYielding) {
1852 + // Timeout immediately when yielding is disabled.
1853 + return 0;
1854 + }
1855 +
1856 + const eventTimeMs: number = inferTimeFromExpirationTime(mostRecentEventTime);
1857 + const currentTimeMs: number = now();
1858 + const timeElapsed = currentTimeMs - eventTimeMs;
1859 +
1860 + let msUntilTimeout = jnd(timeElapsed) - timeElapsed;
1861 +
1862 + // Compute the time until this render pass would expire.
1863 + const timeUntilExpirationMs =
1864 + expirationTimeToMs(committedExpirationTime) + initialTimeMs - currentTimeMs;
1865 +
1866 + // Clamp the timeout to the expiration time.
1867 + // TODO: Once the event time is exact instead of inferred from expiration time
1868 + // we don't need this.
1869 + if (timeUntilExpirationMs < msUntilTimeout) {
1870 + msUntilTimeout = timeUntilExpirationMs;
1871 + }
1872 +
1873 + // This is the value that is passed to `setTimeout`.
1874 + return msUntilTimeout;
1875 +}
1876 +
1877 +function checkForNestedUpdates() {
1878 + if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1879 + nestedUpdateCount = 0;
1880 + rootWithNestedUpdates = null;
1881 + invariant(
1882 + false,
1883 + 'Maximum update depth exceeded. This can happen when a component ' +
1884 + 'repeatedly calls setState inside componentWillUpdate or ' +
1885 + 'componentDidUpdate. React limits the number of nested updates to ' +
1886 + 'prevent infinite loops.',
1887 + );
1888 + }
1889 +
1890 + if (__DEV__) {
1891 + if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
1892 + nestedPassiveUpdateCount = 0;
1893 + warning(
1894 + false,
1895 + 'Maximum update depth exceeded. This can happen when a component ' +
1896 + "calls setState inside useEffect, but useEffect either doesn't " +
1897 + 'have a dependency array, or one of the dependencies changes on ' +
1898 + 'every render.',
1899 + );
1900 + }
1901 + }
1902 +}
1903 +
1904 +function flushRenderPhaseStrictModeWarningsInDEV() {
1905 + if (__DEV__) {
1906 + ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
1907 + ReactStrictModeWarnings.flushLegacyContextWarning();
1908 +
1909 + if (warnAboutDeprecatedLifecycles) {
1910 + ReactStrictModeWarnings.flushPendingDeprecationWarnings();
1911 + }
1912 + }
1913 +}
1914 +
1915 +function stopFinishedWorkLoopTimer() {
1916 + const didCompleteRoot = true;
1917 + stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1918 + interruptedBy = null;
1919 +}
1920 +
1921 +function stopInterruptedWorkLoopTimer() {
1922 + // TODO: Track which fiber caused the interruption.
1923 + const didCompleteRoot = false;
1924 + stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1925 + interruptedBy = null;
1926 +}
1927 +
1928 +function checkForInterruption(
1929 + fiberThatReceivedUpdate: Fiber,
1930 + updateExpirationTime: ExpirationTime,
1931 +) {
1932 + if (
1933 + enableUserTimingAPI &&
1934 + workInProgressRoot !== null &&
1935 + updateExpirationTime > renderExpirationTime
1936 + ) {
1937 + interruptedBy = fiberThatReceivedUpdate;
1938 + }
1939 +}
1940 +
1941 +let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
1942 +function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
1943 + if (__DEV__) {
1944 + const tag = fiber.tag;
1945 + if (
1946 + tag !== HostRoot &&
1947 + tag !== ClassComponent &&
1948 + tag !== FunctionComponent &&
1949 + tag !== ForwardRef &&
1950 + tag !== MemoComponent &&
1951 + tag !== SimpleMemoComponent
1952 + ) {
1953 + // Only warn for user-defined components, not internal ones like Suspense.
1954 + return;
1955 + }
1956 + // We show the whole stack but dedupe on the top component's name because
1957 + // the problematic code almost always lies inside that component.
1958 + const componentName = getComponentName(fiber.type) || 'ReactComponent';
1959 + if (didWarnStateUpdateForUnmountedComponent !== null) {
1960 + if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
1961 + return;
1962 + }
1963 + didWarnStateUpdateForUnmountedComponent.add(componentName);
1964 + } else {
1965 + didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
1966 + }
1967 + warningWithoutStack(
1968 + false,
1969 + "Can't perform a React state update on an unmounted component. This " +
1970 + 'is a no-op, but it indicates a memory leak in your application. To ' +
1971 + 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
1972 + tag === ClassComponent
1973 + ? 'the componentWillUnmount method'
1974 + : 'a useEffect cleanup function',
1975 + getStackByFiberInDevAndProd(fiber),
1976 + );
1977 + }
1978 +}
1979 +
1980 +let beginWork;
1981 +if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1982 + let dummyFiber = null;
1983 + beginWork = (current, unitOfWork, expirationTime) => {
1984 + // If a component throws an error, we replay it again in a synchronously
1985 + // dispatched event, so that the debugger will treat it as an uncaught
1986 + // error See ReactErrorUtils for more information.
1987 +
1988 + // Before entering the begin phase, copy the work-in-progress onto a dummy
1989 + // fiber. If beginWork throws, we'll use this to reset the state.
1990 + const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
1991 + dummyFiber,
1992 + unitOfWork,
1993 + );
1994 + try {
1995 + return originalBeginWork(current, unitOfWork, expirationTime);
1996 + } catch (originalError) {
1997 + if (
1998 + originalError !== null &&
1999 + typeof originalError === 'object' &&
2000 + typeof originalError.then === 'function'
2001 + ) {
2002 + // Don't replay promises. Treat everything else like an error.
2003 + throw originalError;
2004 + }
2005 +
2006 + // Keep this code in sync with renderRoot; any changes here must have
2007 + // corresponding changes there.
2008 + resetContextDependencies();
2009 + resetHooks();
2010 +
2011 + // Unwind the failed stack frame
2012 + unwindInterruptedWork(unitOfWork);
2013 +
2014 + // Restore the original properties of the fiber.
2015 + assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
2016 +
2017 + if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
2018 + // Reset the profiler timer.
2019 + startProfilerTimer(unitOfWork);
2020 + }
2021 +
2022 + // Run beginWork again.
2023 + invokeGuardedCallback(
2024 + null,
2025 + originalBeginWork,
2026 + null,
2027 + current,
2028 + unitOfWork,
2029 + expirationTime,
2030 + );
2031 +
2032 + if (hasCaughtError()) {
2033 + const replayError = clearCaughtError();
2034 + // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
2035 + // Rethrow this error instead of the original one.
2036 + throw replayError;
2037 + } else {
2038 + // This branch is reachable if the render phase is impure.
2039 + throw originalError;
2040 + }
2041 + }
2042 + };
2043 +} else {
2044 + beginWork = originalBeginWork;
2045 +}
2046 +
2047 +let didWarnAboutUpdateInRender = false;
2048 +let didWarnAboutUpdateInGetChildContext = false;
2049 +function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
2050 + if (__DEV__) {
2051 + if (fiber.tag === ClassComponent) {
2052 + switch (ReactCurrentDebugFiberPhaseInDEV) {
2053 + case 'getChildContext':
2054 + if (didWarnAboutUpdateInGetChildContext) {
2055 + return;
2056 + }
2057 + warningWithoutStack(
2058 + false,
2059 + 'setState(...): Cannot call setState() inside getChildContext()',
2060 + );
2061 + didWarnAboutUpdateInGetChildContext = true;
2062 + break;
2063 + case 'render':
2064 + if (didWarnAboutUpdateInRender) {
2065 + return;
2066 + }
2067 + warningWithoutStack(
2068 + false,
2069 + 'Cannot update during an existing state transition (such as ' +
2070 + 'within `render`). Render methods should be a pure function of ' +
2071 + 'props and state.',
2072 + );
2073 + didWarnAboutUpdateInRender = true;
2074 + break;
2075 + }
2076 + }
2077 + }
2078 +}
2079 +
2080 +function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2081 + if (__DEV__) {
2082 + if (
2083 + workPhase === NotWorking &&
2084 + ReactShouldWarnActingUpdates.current === false
2085 + ) {
2086 + warningWithoutStack(
2087 + false,
2088 + 'An update to %s inside a test was not wrapped in act(...).\n\n' +
2089 + 'When testing, code that causes React state updates should be ' +
2090 + 'wrapped into act(...):\n\n' +
2091 + 'act(() => {\n' +
2092 + ' /* fire events that update state */\n' +
2093 + '});\n' +
2094 + '/* assert on the output */\n\n' +
2095 + "This ensures that you're testing the behavior the user would see " +
2096 + 'in the browser.' +
2097 + ' Learn more at https://fb.me/react-wrap-tests-with-act' +
2098 + '%s',
2099 + getComponentName(fiber.type),
2100 + getStackByFiberInDevAndProd(fiber),
2101 + );
2102 + }
2103 + }
2104 +}
2105 +
2106 +export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2107 +
2108 +function computeThreadID(root, expirationTime) {
2109 + // Interaction threads are unique per root and expiration time.
2110 + return expirationTime * 1000 + root.interactionThreadID;
2111 +}
2112 +
2113 +function schedulePendingInteraction(root, expirationTime) {
2114 + // This is called when work is scheduled on a root. It sets up a pending
2115 + // interaction, which is completed once the work commits.
2116 + if (!enableSchedulerTracing) {
2117 + return;
2118 + }
2119 +
2120 + const interactions = __interactionsRef.current;
2121 + if (interactions.size > 0) {
2122 + const pendingInteractionMap = root.pendingInteractionMap;
2123 + const pendingInteractions = pendingInteractionMap.get(expirationTime);
2124 + if (pendingInteractions != null) {
2125 + interactions.forEach(interaction => {
2126 + if (!pendingInteractions.has(interaction)) {
2127 + // Update the pending async work count for previously unscheduled interaction.
2128 + interaction.__count++;
2129 + }
2130 +
2131 + pendingInteractions.add(interaction);
2132 + });
2133 + } else {
2134 + pendingInteractionMap.set(expirationTime, new Set(interactions));
2135 +
2136 + // Update the pending async work count for the current interactions.
2137 + interactions.forEach(interaction => {
2138 + interaction.__count++;
2139 + });
2140 + }
2141 +
2142 + const subscriber = __subscriberRef.current;
2143 + if (subscriber !== null) {
2144 + const threadID = computeThreadID(root, expirationTime);
2145 + subscriber.onWorkScheduled(interactions, threadID);
2146 + }
2147 + }
2148 +}
2149 +
2150 +function startWorkOnPendingInteraction(root, expirationTime) {
2151 + // This is called when new work is started on a root.
2152 + if (!enableSchedulerTracing) {
2153 + return;
2154 + }
2155 +
2156 + // Determine which interactions this batch of work currently includes, So that
2157 + // we can accurately attribute time spent working on it, And so that cascading
2158 + // work triggered during the render phase will be associated with it.
2159 + const interactions: Set<Interaction> = new Set();
2160 + root.pendingInteractionMap.forEach(
2161 + (scheduledInteractions, scheduledExpirationTime) => {
2162 + if (scheduledExpirationTime >= expirationTime) {
2163 + scheduledInteractions.forEach(interaction =>
2164 + interactions.add(interaction),
2165 + );
2166 + }
2167 + },
2168 + );
2169 +
2170 + // Store the current set of interactions on the FiberRoot for a few reasons:
2171 + // We can re-use it in hot functions like renderRoot() without having to
2172 + // recalculate it. We will also use it in commitWork() to pass to any Profiler
2173 + // onRender() hooks. This also provides DevTools with a way to access it when
2174 + // the onCommitRoot() hook is called.
2175 + root.memoizedInteractions = interactions;
2176 +
2177 + if (interactions.size > 0) {
2178 + const subscriber = __subscriberRef.current;
2179 + if (subscriber !== null) {
2180 + const threadID = computeThreadID(root, expirationTime);
2181 + try {
2182 + subscriber.onWorkStarted(interactions, threadID);
2183 + } catch (error) {
2184 + // If the subscriber throws, rethrow it in a separate task
2185 + scheduleCallback(ImmediatePriority, () => {
2186 + throw error;
2187 + });
2188 + }
2189 + }
2190 + }
2191 +}
2192 +
2193 +function finishPendingInteractions(root, committedExpirationTime) {
2194 + if (!enableSchedulerTracing) {
2195 + return;
2196 + }
2197 +
2198 + const earliestRemainingTimeAfterCommit = root.firstPendingTime;
2199 +
2200 + let subscriber;
2201 +
2202 + try {
2203 + subscriber = __subscriberRef.current;
2204 + if (subscriber !== null && root.memoizedInteractions.size > 0) {
2205 + const threadID = computeThreadID(root, committedExpirationTime);
2206 + subscriber.onWorkStopped(root.memoizedInteractions, threadID);
2207 + }
2208 + } catch (error) {
2209 + // If the subscriber throws, rethrow it in a separate task
2210 + scheduleCallback(ImmediatePriority, () => {
2211 + throw error;
2212 + });
2213 + } finally {
2214 + // Clear completed interactions from the pending Map.
2215 + // Unless the render was suspended or cascading work was scheduled,
2216 + // In which case– leave pending interactions until the subsequent render.
2217 + const pendingInteractionMap = root.pendingInteractionMap;
2218 + pendingInteractionMap.forEach(
2219 + (scheduledInteractions, scheduledExpirationTime) => {
2220 + // Only decrement the pending interaction count if we're done.
2221 + // If there's still work at the current priority,
2222 + // That indicates that we are waiting for suspense data.
2223 + if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
2224 + pendingInteractionMap.delete(scheduledExpirationTime);
2225 +
2226 + scheduledInteractions.forEach(interaction => {
2227 + interaction.__count--;
2228 +
2229 + if (subscriber !== null && interaction.__count === 0) {
2230 + try {
2231 + subscriber.onInteractionScheduledWorkCompleted(interaction);
2232 + } catch (error) {
2233 + // If the subscriber throws, rethrow it in a separate task
2234 + scheduleCallback(ImmediatePriority, () => {
2235 + throw error;
2236 + });
2237 + }
2238 + }
2239 + });
2240 + }
2241 + },
2242 + );
2243 + }
2244 +}
packages/react-reconciler/src/ReactFiberScheduler.new.js deleted
-2244
@@ -1,2244 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {Fiber} from './ReactFiber';
11 -import type {FiberRoot} from './ReactFiberRoot';
12 -import type {ExpirationTime} from './ReactFiberExpirationTime';
13 -import type {
14 - ReactPriorityLevel,
15 - SchedulerCallback,
16 -} from './SchedulerWithReactIntegration';
17 -import type {Interaction} from 'scheduler/src/Tracing';
18 -
19 -import {
20 - warnAboutDeprecatedLifecycles,
21 - enableUserTimingAPI,
22 - enableSuspenseServerRenderer,
23 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
24 - enableProfilerTimer,
25 - disableYielding,
26 - enableSchedulerTracing,
27 -} from 'shared/ReactFeatureFlags';
28 -import ReactSharedInternals from 'shared/ReactSharedInternals';
29 -import invariant from 'shared/invariant';
30 -import warning from 'shared/warning';
31 -
32 -import {
33 - scheduleCallback,
34 - cancelCallback,
35 - getCurrentPriorityLevel,
36 - runWithPriority,
37 - shouldYield,
38 - now,
39 - ImmediatePriority,
40 - UserBlockingPriority,
41 - NormalPriority,
42 - LowPriority,
43 - IdlePriority,
44 - flushImmediateQueue,
45 -} from './SchedulerWithReactIntegration';
46 -
47 -import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
48 -
49 -import {
50 - prepareForCommit,
51 - resetAfterCommit,
52 - scheduleTimeout,
53 - cancelTimeout,
54 - noTimeout,
55 -} from './ReactFiberHostConfig';
56 -
57 -import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
58 -import {NoContext, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
59 -import {
60 - HostRoot,
61 - ClassComponent,
62 - SuspenseComponent,
63 - DehydratedSuspenseComponent,
64 - FunctionComponent,
65 - ForwardRef,
66 - MemoComponent,
67 - SimpleMemoComponent,
68 -} from 'shared/ReactWorkTags';
69 -import {
70 - NoEffect,
71 - PerformedWork,
72 - Placement,
73 - Update,
74 - PlacementAndUpdate,
75 - Deletion,
76 - Ref,
77 - ContentReset,
78 - Snapshot,
79 - Callback,
80 - Passive,
81 - Incomplete,
82 - HostEffectMask,
83 -} from 'shared/ReactSideEffectTags';
84 -import {
85 - NoWork,
86 - Sync,
87 - Never,
88 - msToExpirationTime,
89 - expirationTimeToMs,
90 - computeInteractiveExpiration,
91 - computeAsyncExpiration,
92 - inferPriorityFromExpirationTime,
93 - LOW_PRIORITY_EXPIRATION,
94 -} from './ReactFiberExpirationTime';
95 -import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
96 -import {completeWork} from './ReactFiberCompleteWork';
97 -import {
98 - throwException,
99 - unwindWork,
100 - unwindInterruptedWork,
101 - createRootErrorUpdate,
102 - createClassErrorUpdate,
103 -} from './ReactFiberUnwindWork';
104 -import {
105 - commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
106 - commitLifeCycles as commitLayoutEffectOnFiber,
107 - commitPassiveHookEffects,
108 - commitPlacement,
109 - commitWork,
110 - commitDeletion,
111 - commitDetachRef,
112 - commitAttachRef,
113 - commitResetTextContent,
114 -} from './ReactFiberCommitWork';
115 -import {enqueueUpdate} from './ReactUpdateQueue';
116 -// TODO: Ahaha Andrew is bad at spellling
117 -import {resetContextDependences as resetContextDependencies} from './ReactFiberNewContext';
118 -import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
119 -import {createCapturedValue} from './ReactCapturedValue';
120 -
121 -import {
122 - recordCommitTime,
123 - startProfilerTimer,
124 - stopProfilerTimerIfRunningAndRecordDelta,
125 -} from './ReactProfilerTimer';
126 -
127 -// DEV stuff
128 -import warningWithoutStack from 'shared/warningWithoutStack';
129 -import getComponentName from 'shared/getComponentName';
130 -import ReactStrictModeWarnings from './ReactStrictModeWarnings';
131 -import {
132 - phase as ReactCurrentDebugFiberPhaseInDEV,
133 - resetCurrentFiber as resetCurrentDebugFiberInDEV,
134 - setCurrentFiber as setCurrentDebugFiberInDEV,
135 - getStackByFiberInDevAndProd,
136 -} from './ReactCurrentFiber';
137 -import {
138 - recordEffect,
139 - recordScheduleUpdate,
140 - startRequestCallbackTimer,
141 - stopRequestCallbackTimer,
142 - startWorkTimer,
143 - stopWorkTimer,
144 - stopFailedWorkTimer,
145 - startWorkLoopTimer,
146 - stopWorkLoopTimer,
147 - startCommitTimer,
148 - stopCommitTimer,
149 - startCommitSnapshotEffectsTimer,
150 - stopCommitSnapshotEffectsTimer,
151 - startCommitHostEffectsTimer,
152 - stopCommitHostEffectsTimer,
153 - startCommitLifeCyclesTimer,
154 - stopCommitLifeCyclesTimer,
155 -} from './ReactDebugFiberPerf';
156 -import {
157 - invokeGuardedCallback,
158 - hasCaughtError,
159 - clearCaughtError,
160 -} from 'shared/ReactErrorUtils';
161 -import {onCommitRoot} from './ReactFiberDevToolsHook';
162 -
163 -const ceil = Math.ceil;
164 -
165 -const {
166 - ReactCurrentDispatcher,
167 - ReactCurrentOwner,
168 - ReactShouldWarnActingUpdates,
169 -} = ReactSharedInternals;
170 -
171 -type WorkPhase = 0 | 1 | 2 | 3 | 4 | 5;
172 -const NotWorking = 0;
173 -const BatchedPhase = 1;
174 -const LegacyUnbatchedPhase = 2;
175 -const FlushSyncPhase = 3;
176 -const RenderPhase = 4;
177 -const CommitPhase = 5;
178 -
179 -type RootExitStatus = 0 | 1 | 2 | 3;
180 -const RootIncomplete = 0;
181 -const RootErrored = 1;
182 -const RootSuspended = 2;
183 -const RootCompleted = 3;
184 -
185 -export type Thenable = {
186 - then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
187 -};
188 -
189 -// The phase of work we're currently in
190 -let workPhase: WorkPhase = NotWorking;
191 -// The root we're working on
192 -let workInProgressRoot: FiberRoot | null = null;
193 -// The fiber we're working on
194 -let workInProgress: Fiber | null = null;
195 -// The expiration time we're rendering
196 -let renderExpirationTime: ExpirationTime = NoWork;
197 -// Whether to root completed, errored, suspended, etc.
198 -let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
199 -// Most recent event time among processed updates during this render.
200 -// This is conceptually a time stamp but expressed in terms of an ExpirationTime
201 -// because we deal mostly with expiration times in the hot path, so this avoids
202 -// the conversion happening in the hot path.
203 -let workInProgressRootMostRecentEventTime: ExpirationTime = Sync;
204 -
205 -let nextEffect: Fiber | null = null;
206 -let hasUncaughtError = false;
207 -let firstUncaughtError = null;
208 -let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
209 -
210 -let rootDoesHavePassiveEffects: boolean = false;
211 -let rootWithPendingPassiveEffects: FiberRoot | null = null;
212 -let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
213 -
214 -let rootsWithPendingDiscreteUpdates: Map<
215 - FiberRoot,
216 - ExpirationTime,
217 -> | null = null;
218 -
219 -// Use these to prevent an infinite loop of nested updates
220 -const NESTED_UPDATE_LIMIT = 50;
221 -let nestedUpdateCount: number = 0;
222 -let rootWithNestedUpdates: FiberRoot | null = null;
223 -
224 -const NESTED_PASSIVE_UPDATE_LIMIT = 50;
225 -let nestedPassiveUpdateCount: number = 0;
226 -
227 -let interruptedBy: Fiber | null = null;
228 -
229 -// Expiration times are computed by adding to the current time (the start
230 -// time). However, if two updates are scheduled within the same event, we
231 -// should treat their start times as simultaneous, even if the actual clock
232 -// time has advanced between the first and second call.
233 -
234 -// In other words, because expiration times determine how updates are batched,
235 -// we want all updates of like priority that occur within the same event to
236 -// receive the same expiration time. Otherwise we get tearing.
237 -let initialTimeMs: number = now();
238 -let currentEventTime: ExpirationTime = NoWork;
239 -
240 -export function requestCurrentTime() {
241 - if (workPhase === RenderPhase || workPhase === CommitPhase) {
242 - // We're inside React, so it's fine to read the actual time.
243 - return msToExpirationTime(now() - initialTimeMs);
244 - }
245 - // We're not inside React, so we may be in the middle of a browser event.
246 - if (currentEventTime !== NoWork) {
247 - // Use the same start time for all updates until we enter React again.
248 - return currentEventTime;
249 - }
250 - // This is the first update since React yielded. Compute a new start time.
251 - currentEventTime = msToExpirationTime(now() - initialTimeMs);
252 - return currentEventTime;
253 -}
254 -
255 -export function computeExpirationForFiber(
256 - currentTime: ExpirationTime,
257 - fiber: Fiber,
258 -): ExpirationTime {
259 - if ((fiber.mode & ConcurrentMode) === NoContext) {
260 - return Sync;
261 - }
262 -
263 - if (workPhase === RenderPhase) {
264 - // Use whatever time we're already rendering
265 - return renderExpirationTime;
266 - }
267 -
268 - // Compute an expiration time based on the Scheduler priority.
269 - let expirationTime;
270 - const priorityLevel = getCurrentPriorityLevel();
271 - switch (priorityLevel) {
272 - case ImmediatePriority:
273 - expirationTime = Sync;
274 - break;
275 - case UserBlockingPriority:
276 - // TODO: Rename this to computeUserBlockingExpiration
277 - expirationTime = computeInteractiveExpiration(currentTime);
278 - break;
279 - case NormalPriority:
280 - case LowPriority: // TODO: Handle LowPriority
281 - // TODO: Rename this to... something better.
282 - expirationTime = computeAsyncExpiration(currentTime);
283 - break;
284 - case IdlePriority:
285 - expirationTime = Never;
286 - break;
287 - default:
288 - invariant(false, 'Expected a valid priority level');
289 - }
290 -
291 - // If we're in the middle of rendering a tree, do not update at the same
292 - // expiration time that is already rendering.
293 - if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
294 - // This is a trick to move this update into a separate batch
295 - expirationTime -= 1;
296 - }
297 -
298 - return expirationTime;
299 -}
300 -
301 -let lastUniqueAsyncExpiration = NoWork;
302 -export function computeUniqueAsyncExpiration(): ExpirationTime {
303 - const currentTime = requestCurrentTime();
304 - let result = computeAsyncExpiration(currentTime);
305 - if (result <= lastUniqueAsyncExpiration) {
306 - // Since we assume the current time monotonically increases, we only hit
307 - // this branch when computeUniqueAsyncExpiration is fired multiple times
308 - // within a 200ms window (or whatever the async bucket size is).
309 - result -= 1;
310 - }
311 - lastUniqueAsyncExpiration = result;
312 - return result;
313 -}
314 -
315 -export function scheduleUpdateOnFiber(
316 - fiber: Fiber,
317 - expirationTime: ExpirationTime,
318 -) {
319 - checkForNestedUpdates();
320 - warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
321 -
322 - const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
323 - if (root === null) {
324 - warnAboutUpdateOnUnmountedFiberInDEV(fiber);
325 - return;
326 - }
327 -
328 - root.pingTime = NoWork;
329 -
330 - checkForInterruption(fiber, expirationTime);
331 - recordScheduleUpdate();
332 -
333 - if (expirationTime === Sync) {
334 - if (workPhase === LegacyUnbatchedPhase) {
335 - // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
336 - // root inside of batchedUpdates should be synchronous, but layout updates
337 - // should be deferred until the end of the batch.
338 - let callback = renderRoot(root, Sync, true);
339 - while (callback !== null) {
340 - callback = callback(true);
341 - }
342 - } else {
343 - scheduleCallbackForRoot(root, ImmediatePriority, Sync);
344 - if (workPhase === NotWorking) {
345 - // Flush the synchronous work now, wnless we're already working or inside
346 - // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
347 - // scheduleCallbackForFiber to preserve the ability to schedule a callback
348 - // without immediately flushing it. We only do this for user-initated
349 - // updates, to preserve historical behavior of sync mode.
350 - flushImmediateQueue();
351 - }
352 - }
353 - } else {
354 - // TODO: computeExpirationForFiber also reads the priority. Pass the
355 - // priority as an argument to that function and this one.
356 - const priorityLevel = getCurrentPriorityLevel();
357 - if (priorityLevel === UserBlockingPriority) {
358 - // This is the result of a discrete event. Track the lowest priority
359 - // discrete update per root so we can flush them early, if needed.
360 - if (rootsWithPendingDiscreteUpdates === null) {
361 - rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
362 - } else {
363 - const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
364 - if (
365 - lastDiscreteTime === undefined ||
366 - lastDiscreteTime > expirationTime
367 - ) {
368 - rootsWithPendingDiscreteUpdates.set(root, expirationTime);
369 - }
370 - }
371 - }
372 - scheduleCallbackForRoot(root, priorityLevel, expirationTime);
373 - }
374 -}
375 -export const scheduleWork = scheduleUpdateOnFiber;
376 -
377 -// This is split into a separate function so we can mark a fiber with pending
378 -// work without treating it as a typical update that originates from an event;
379 -// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
380 -// on a fiber.
381 -function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
382 - // Update the source fiber's expiration time
383 - if (fiber.expirationTime < expirationTime) {
384 - fiber.expirationTime = expirationTime;
385 - }
386 - let alternate = fiber.alternate;
387 - if (alternate !== null && alternate.expirationTime < expirationTime) {
388 - alternate.expirationTime = expirationTime;
389 - }
390 - // Walk the parent path to the root and update the child expiration time.
391 - let node = fiber.return;
392 - let root = null;
393 - if (node === null && fiber.tag === HostRoot) {
394 - root = fiber.stateNode;
395 - } else {
396 - while (node !== null) {
397 - alternate = node.alternate;
398 - if (node.childExpirationTime < expirationTime) {
399 - node.childExpirationTime = expirationTime;
400 - if (
401 - alternate !== null &&
402 - alternate.childExpirationTime < expirationTime
403 - ) {
404 - alternate.childExpirationTime = expirationTime;
405 - }
406 - } else if (
407 - alternate !== null &&
408 - alternate.childExpirationTime < expirationTime
409 - ) {
410 - alternate.childExpirationTime = expirationTime;
411 - }
412 - if (node.return === null && node.tag === HostRoot) {
413 - root = node.stateNode;
414 - break;
415 - }
416 - node = node.return;
417 - }
418 - }
419 -
420 - if (root !== null) {
421 - // Update the first and last pending expiration times in this root
422 - const firstPendingTime = root.firstPendingTime;
423 - if (expirationTime > firstPendingTime) {
424 - root.firstPendingTime = expirationTime;
425 - }
426 - const lastPendingTime = root.lastPendingTime;
427 - if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
428 - root.lastPendingTime = expirationTime;
429 - }
430 - }
431 -
432 - return root;
433 -}
434 -
435 -// Use this function, along with runRootCallback, to ensure that only a single
436 -// callback per root is scheduled. It's still possible to call renderRoot
437 -// directly, but scheduling via this function helps avoid excessive callbacks.
438 -// It works by storing the callback node and expiration time on the root. When a
439 -// new callback comes in, it compares the expiration time to determine if it
440 -// should cancel the previous one. It also relies on commitRoot scheduling a
441 -// callback to render the next level, because that means we don't need a
442 -// separate callback per expiration time.
443 -function scheduleCallbackForRoot(
444 - root: FiberRoot,
445 - priorityLevel: ReactPriorityLevel,
446 - expirationTime: ExpirationTime,
447 -) {
448 - const existingCallbackExpirationTime = root.callbackExpirationTime;
449 - if (existingCallbackExpirationTime < expirationTime) {
450 - // New callback has higher priority than the existing one.
451 - const existingCallbackNode = root.callbackNode;
452 - if (existingCallbackNode !== null) {
453 - cancelCallback(existingCallbackNode);
454 - }
455 - root.callbackExpirationTime = expirationTime;
456 - const options =
457 - expirationTime === Sync
458 - ? null
459 - : {timeout: expirationTimeToMs(expirationTime)};
460 - root.callbackNode = scheduleCallback(
461 - priorityLevel,
462 - runRootCallback.bind(
463 - null,
464 - root,
465 - renderRoot.bind(null, root, expirationTime),
466 - ),
467 - options,
468 - );
469 - if (
470 - enableUserTimingAPI &&
471 - expirationTime !== Sync &&
472 - workPhase !== RenderPhase &&
473 - workPhase !== CommitPhase
474 - ) {
475 - // Scheduled an async callback, and we're not already working. Add an
476 - // entry to the flamegraph that shows we're waiting for a callback
477 - // to fire.
478 - startRequestCallbackTimer();
479 - }
480 - }
481 -
482 - const timeoutHandle = root.timeoutHandle;
483 - if (timeoutHandle !== noTimeout) {
484 - // The root previous suspended and scheduled a timeout to commit a fallback
485 - // state. Now that we have additional work, cancel the timeout.
486 - root.timeoutHandle = noTimeout;
487 - // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
488 - cancelTimeout(timeoutHandle);
489 - }
490 -
491 - // Add the current set of interactions to the pending set associated with
492 - // this root.
493 - schedulePendingInteraction(root, expirationTime);
494 -}
495 -
496 -function runRootCallback(root, callback, isSync) {
497 - const prevCallbackNode = root.callbackNode;
498 - let continuation = null;
499 - try {
500 - continuation = callback(isSync);
501 - if (continuation !== null) {
502 - return runRootCallback.bind(null, root, continuation);
503 - } else {
504 - return null;
505 - }
506 - } finally {
507 - // If the callback exits without returning a continuation, remove the
508 - // corresponding callback node from the root. Unless the callback node
509 - // has changed, which implies that it was already cancelled by a high
510 - // priority update.
511 - if (continuation === null && prevCallbackNode === root.callbackNode) {
512 - root.callbackNode = null;
513 - root.callbackExpirationTime = NoWork;
514 - }
515 - }
516 -}
517 -
518 -export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
519 - if (workPhase === RenderPhase || workPhase === CommitPhase) {
520 - invariant(
521 - false,
522 - 'work.commit(): Cannot commit while already rendering. This likely ' +
523 - 'means you attempted to commit from inside a lifecycle method.',
524 - );
525 - }
526 - scheduleCallback(
527 - ImmediatePriority,
528 - renderRoot.bind(null, root, expirationTime),
529 - );
530 - flushImmediateQueue();
531 -}
532 -
533 -export function flushInteractiveUpdates() {
534 - if (workPhase === RenderPhase || workPhase === CommitPhase) {
535 - // Can't synchronously flush interactive updates if React is already
536 - // working. This is currently a no-op.
537 - // TODO: Should we fire a warning? This happens if you synchronously invoke
538 - // an input event inside an effect, like with `element.click()`.
539 - return;
540 - }
541 - flushPendingDiscreteUpdates();
542 -}
543 -
544 -function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {
545 - const firstBatch = root.firstBatch;
546 - if (
547 - firstBatch !== null &&
548 - firstBatch._defer &&
549 - firstBatch._expirationTime >= expirationTime
550 - ) {
551 - root.finishedWork = root.current.alternate;
552 - root.pendingCommitExpirationTime = expirationTime;
553 - scheduleCallback(NormalPriority, () => {
554 - firstBatch._onComplete();
555 - return null;
556 - });
557 - return true;
558 - } else {
559 - return false;
560 - }
561 -}
562 -
563 -export function deferredUpdates<A>(fn: () => A): A {
564 - // TODO: Remove in favor of Scheduler.next
565 - return runWithPriority(NormalPriority, fn);
566 -}
567 -
568 -export function interactiveUpdates<A, B, C, R>(
569 - fn: (A, B, C) => R,
570 - a: A,
571 - b: B,
572 - c: C,
573 -): R {
574 - if (workPhase === NotWorking) {
575 - // TODO: Remove this call. Instead of doing this automatically, the caller
576 - // should explicitly call flushInteractiveUpdates.
577 - flushPendingDiscreteUpdates();
578 - }
579 - return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));
580 -}
581 -
582 -export function syncUpdates<A, B, C, R>(
583 - fn: (A, B, C) => R,
584 - a: A,
585 - b: B,
586 - c: C,
587 -): R {
588 - return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
589 -}
590 -
591 -function flushPendingDiscreteUpdates() {
592 - if (rootsWithPendingDiscreteUpdates !== null) {
593 - // For each root with pending discrete updates, schedule a callback to
594 - // immediately flush them.
595 - const roots = rootsWithPendingDiscreteUpdates;
596 - rootsWithPendingDiscreteUpdates = null;
597 - roots.forEach((expirationTime, root) => {
598 - scheduleCallback(
599 - ImmediatePriority,
600 - renderRoot.bind(null, root, expirationTime),
601 - );
602 - });
603 - // Now flush the immediate queue.
604 - flushImmediateQueue();
605 - }
606 -}
607 -
608 -export function batchedUpdates<A, R>(fn: A => R, a: A): R {
609 - if (workPhase !== NotWorking) {
610 - // We're already working, or inside a batch, so batchedUpdates is a no-op.
611 - return fn(a);
612 - }
613 - workPhase = BatchedPhase;
614 - try {
615 - return fn(a);
616 - } finally {
617 - workPhase = NotWorking;
618 - // Flush the immediate callbacks that were scheduled during this batch
619 - flushImmediateQueue();
620 - }
621 -}
622 -
623 -export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
624 - if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {
625 - // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is
626 - // a no-op.
627 - return fn(a);
628 - }
629 - const prevWorkPhase = workPhase;
630 - workPhase = LegacyUnbatchedPhase;
631 - try {
632 - return fn(a);
633 - } finally {
634 - workPhase = prevWorkPhase;
635 - }
636 -}
637 -
638 -export function flushSync<A, R>(fn: A => R, a: A): R {
639 - if (workPhase === RenderPhase || workPhase === CommitPhase) {
640 - invariant(
641 - false,
642 - 'flushSync was called from inside a lifecycle method. It cannot be ' +
643 - 'called when React is already rendering.',
644 - );
645 - }
646 - const prevWorkPhase = workPhase;
647 - workPhase = FlushSyncPhase;
648 - try {
649 - return runWithPriority(ImmediatePriority, fn.bind(null, a));
650 - } finally {
651 - workPhase = prevWorkPhase;
652 - // Flush the immediate callbacks that were scheduled during this batch.
653 - // Note that this will happen even if batchedUpdates is higher up
654 - // the stack.
655 - flushImmediateQueue();
656 - }
657 -}
658 -
659 -export function flushControlled(fn: () => mixed): void {
660 - const prevWorkPhase = workPhase;
661 - workPhase = BatchedPhase;
662 - try {
663 - runWithPriority(ImmediatePriority, fn);
664 - } finally {
665 - workPhase = prevWorkPhase;
666 - if (workPhase === NotWorking) {
667 - // Flush the immediate callbacks that were scheduled during this batch
668 - flushImmediateQueue();
669 - }
670 - }
671 -}
672 -
673 -function prepareFreshStack(root, expirationTime) {
674 - root.pendingCommitExpirationTime = NoWork;
675 -
676 - if (workInProgress !== null) {
677 - let interruptedWork = workInProgress.return;
678 - while (interruptedWork !== null) {
679 - unwindInterruptedWork(interruptedWork);
680 - interruptedWork = interruptedWork.return;
681 - }
682 - }
683 - workInProgressRoot = root;
684 - workInProgress = createWorkInProgress(root.current, null, expirationTime);
685 - renderExpirationTime = expirationTime;
686 - workInProgressRootExitStatus = RootIncomplete;
687 - workInProgressRootMostRecentEventTime = Sync;
688 -
689 - if (__DEV__) {
690 - ReactStrictModeWarnings.discardPendingWarnings();
691 - }
692 -}
693 -
694 -function renderRoot(
695 - root: FiberRoot,
696 - expirationTime: ExpirationTime,
697 - isSync: boolean,
698 -): SchedulerCallback | null {
699 - invariant(
700 - workPhase !== RenderPhase && workPhase !== CommitPhase,
701 - 'Should not already be working.',
702 - );
703 -
704 - if (enableUserTimingAPI && expirationTime !== Sync) {
705 - const didExpire = isSync;
706 - const timeoutMs = expirationTimeToMs(expirationTime);
707 - stopRequestCallbackTimer(didExpire, timeoutMs);
708 - }
709 -
710 - if (root.firstPendingTime < expirationTime) {
711 - // If there's no work left at this expiration time, exit immediately. This
712 - // happens when multiple callbacks are scheduled for a single root, but an
713 - // earlier callback flushes the work of a later one.
714 - return null;
715 - }
716 -
717 - if (root.pendingCommitExpirationTime === expirationTime) {
718 - // There's already a pending commit at this expiration time.
719 - root.pendingCommitExpirationTime = NoWork;
720 - return commitRoot.bind(null, root, expirationTime);
721 - }
722 -
723 - flushPassiveEffects();
724 -
725 - // If the root or expiration time have changed, throw out the existing stack
726 - // and prepare a fresh one. Otherwise we'll continue where we left off.
727 - if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
728 - prepareFreshStack(root, expirationTime);
729 - startWorkOnPendingInteraction(root, expirationTime);
730 - }
731 -
732 - // If we have a work-in-progress fiber, it means there's still work to do
733 - // in this root.
734 - if (workInProgress !== null) {
735 - const prevWorkPhase = workPhase;
736 - workPhase = RenderPhase;
737 - let prevDispatcher = ReactCurrentDispatcher.current;
738 - if (prevDispatcher === null) {
739 - // The React isomorphic package does not include a default dispatcher.
740 - // Instead the first renderer will lazily attach one, in order to give
741 - // nicer error messages.
742 - prevDispatcher = ContextOnlyDispatcher;
743 - }
744 - ReactCurrentDispatcher.current = ContextOnlyDispatcher;
745 - let prevInteractions: Set<Interaction> | null = null;
746 - if (enableSchedulerTracing) {
747 - prevInteractions = __interactionsRef.current;
748 - __interactionsRef.current = root.memoizedInteractions;
749 - }
750 -
751 - startWorkLoopTimer(workInProgress);
752 -
753 - // TODO: Fork renderRoot into renderRootSync and renderRootAsync
754 - if (isSync) {
755 - if (expirationTime !== Sync) {
756 - // An async update expired. There may be other expired updates on
757 - // this root. We should render all the expired work in a
758 - // single batch.
759 - const currentTime = requestCurrentTime();
760 - if (currentTime < expirationTime) {
761 - // Restart at the current time.
762 - workPhase = prevWorkPhase;
763 - resetContextDependencies();
764 - ReactCurrentDispatcher.current = prevDispatcher;
765 - if (enableSchedulerTracing) {
766 - __interactionsRef.current = ((prevInteractions: any): Set<
767 - Interaction,
768 - >);
769 - }
770 - return renderRoot.bind(null, root, currentTime);
771 - }
772 - }
773 - } else {
774 - // Since we know we're in a React event, we can clear the current
775 - // event time. The next update will compute a new event time.
776 - currentEventTime = NoWork;
777 - }
778 -
779 - do {
780 - try {
781 - if (isSync) {
782 - workLoopSync();
783 - } else {
784 - workLoop();
785 - }
786 - break;
787 - } catch (thrownValue) {
788 - // Reset module-level state that was set during the render phase.
789 - resetContextDependencies();
790 - resetHooks();
791 -
792 - const sourceFiber = workInProgress;
793 - if (sourceFiber === null || sourceFiber.return === null) {
794 - // Expected to be working on a non-root fiber. This is a fatal error
795 - // because there's no ancestor that can handle it; the root is
796 - // supposed to capture all errors that weren't caught by an error
797 - // boundary.
798 - prepareFreshStack(root, expirationTime);
799 - workPhase = prevWorkPhase;
800 - throw thrownValue;
801 - }
802 -
803 - if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
804 - // Record the time spent rendering before an error was thrown. This
805 - // avoids inaccurate Profiler durations in the case of a
806 - // suspended render.
807 - stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
808 - }
809 -
810 - const returnFiber = sourceFiber.return;
811 - throwException(
812 - root,
813 - returnFiber,
814 - sourceFiber,
815 - thrownValue,
816 - renderExpirationTime,
817 - );
818 - workInProgress = completeUnitOfWork(sourceFiber);
819 - }
820 - } while (true);
821 -
822 - workPhase = prevWorkPhase;
823 - resetContextDependencies();
824 - ReactCurrentDispatcher.current = prevDispatcher;
825 - if (enableSchedulerTracing) {
826 - __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
827 - }
828 -
829 - if (workInProgress !== null) {
830 - // There's still work left over. Return a continuation.
831 - stopInterruptedWorkLoopTimer();
832 - if (expirationTime !== Sync) {
833 - startRequestCallbackTimer();
834 - }
835 - return renderRoot.bind(null, root, expirationTime);
836 - }
837 - }
838 -
839 - // We now have a consistent tree. The next step is either to commit it, or, if
840 - // something suspended, wait to commit it after a timeout.
841 - stopFinishedWorkLoopTimer();
842 -
843 - const isLocked = resolveLocksOnRoot(root, expirationTime);
844 - if (isLocked) {
845 - // This root has a lock that prevents it from committing. Exit. If we begin
846 - // work on the root again, without any intervening updates, it will finish
847 - // without doing additional work.
848 - return null;
849 - }
850 -
851 - // Set this to null to indicate there's no in-progress render.
852 - workInProgressRoot = null;
853 -
854 - switch (workInProgressRootExitStatus) {
855 - case RootIncomplete: {
856 - invariant(false, 'Should have a work-in-progress.');
857 - }
858 - // Flow knows about invariant, so it compains if I add a break statement,
859 - // but eslint doesn't know about invariant, so it complains if I do.
860 - // eslint-disable-next-line no-fallthrough
861 - case RootErrored: {
862 - // An error was thrown. First check if there is lower priority work
863 - // scheduled on this root.
864 - const lastPendingTime = root.lastPendingTime;
865 - if (root.lastPendingTime < expirationTime) {
866 - // There's lower priority work. Before raising the error, try rendering
867 - // at the lower priority to see if it fixes it. Use a continuation to
868 - // maintain the existing priority and position in the queue.
869 - return renderRoot.bind(null, root, lastPendingTime);
870 - }
871 - if (!isSync) {
872 - // If we're rendering asynchronously, it's possible the error was
873 - // caused by tearing due to a mutation during an event. Try rendering
874 - // one more time without yiedling to events.
875 - prepareFreshStack(root, expirationTime);
876 - scheduleCallback(
877 - ImmediatePriority,
878 - renderRoot.bind(null, root, expirationTime),
879 - );
880 - return null;
881 - }
882 - // If we're already rendering synchronously, commit the root in its
883 - // errored state.
884 - return commitRoot.bind(null, root, expirationTime);
885 - }
886 - case RootSuspended: {
887 - if (!isSync) {
888 - const lastPendingTime = root.lastPendingTime;
889 - if (root.lastPendingTime < expirationTime) {
890 - // There's lower priority work. It might be unsuspended. Try rendering
891 - // at that level.
892 - return renderRoot.bind(null, root, lastPendingTime);
893 - }
894 - // If workInProgressRootMostRecentEventTime is Sync, that means we didn't
895 - // track any event times. That can happen if we retried but nothing switched
896 - // from fallback to content. There's no reason to delay doing no work.
897 - if (workInProgressRootMostRecentEventTime !== Sync) {
898 - let msUntilTimeout = computeMsUntilTimeout(
899 - workInProgressRootMostRecentEventTime,
900 - expirationTime,
901 - );
902 - // Don't bother with a very short suspense time.
903 - if (msUntilTimeout > 10) {
904 - // The render is suspended, it hasn't timed out, and there's no lower
905 - // priority work to do. Instead of committing the fallback
906 - // immediately, wait for more data to arrive.
907 - root.timeoutHandle = scheduleTimeout(
908 - commitRoot.bind(null, root, expirationTime),
909 - msUntilTimeout,
910 - );
911 - return null;
912 - }
913 - }
914 - }
915 - // The work expired. Commit immediately.
916 - return commitRoot.bind(null, root, expirationTime);
917 - }
918 - case RootCompleted: {
919 - // The work completed. Ready to commit.
920 - return commitRoot.bind(null, root, expirationTime);
921 - }
922 - default: {
923 - invariant(false, 'Unknown root exit status.');
924 - }
925 - }
926 -}
927 -
928 -export function markRenderEventTime(expirationTime: ExpirationTime): void {
929 - if (expirationTime < workInProgressRootMostRecentEventTime) {
930 - workInProgressRootMostRecentEventTime = expirationTime;
931 - }
932 -}
933 -
934 -export function renderDidSuspend(): void {
935 - if (workInProgressRootExitStatus === RootIncomplete) {
936 - workInProgressRootExitStatus = RootSuspended;
937 - }
938 -}
939 -
940 -export function renderDidError() {
941 - if (
942 - workInProgressRootExitStatus === RootIncomplete ||
943 - workInProgressRootExitStatus === RootSuspended
944 - ) {
945 - workInProgressRootExitStatus = RootErrored;
946 - }
947 -}
948 -
949 -function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {
950 - // We don't know exactly when the update was scheduled, but we can infer an
951 - // approximate start time from the expiration time.
952 - const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
953 - return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION + initialTimeMs;
954 -}
955 -
956 -function workLoopSync() {
957 - // Already timed out, so perform work without checking if we need to yield.
958 - while (workInProgress !== null) {
959 - workInProgress = performUnitOfWork(workInProgress);
960 - }
961 -}
962 -
963 -function workLoop() {
964 - // Perform work until Scheduler asks us to yield
965 - while (workInProgress !== null && !shouldYield()) {
966 - workInProgress = performUnitOfWork(workInProgress);
967 - }
968 -}
969 -
970 -function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
971 - // The current, flushed, state of this fiber is the alternate. Ideally
972 - // nothing should rely on this, but relying on it here means that we don't
973 - // need an additional field on the work in progress.
974 - const current = unitOfWork.alternate;
975 -
976 - startWorkTimer(unitOfWork);
977 - setCurrentDebugFiberInDEV(unitOfWork);
978 -
979 - let next;
980 - if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {
981 - startProfilerTimer(unitOfWork);
982 - next = beginWork(current, unitOfWork, renderExpirationTime);
983 - stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
984 - } else {
985 - next = beginWork(current, unitOfWork, renderExpirationTime);
986 - }
987 -
988 - resetCurrentDebugFiberInDEV();
989 - unitOfWork.memoizedProps = unitOfWork.pendingProps;
990 - if (next === null) {
991 - // If this doesn't spawn new work, complete the current work.
992 - next = completeUnitOfWork(unitOfWork);
993 - }
994 -
995 - ReactCurrentOwner.current = null;
996 - return next;
997 -}
998 -
999 -function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1000 - // Attempt to complete the current unit of work, then move to the next
1001 - // sibling. If there are no more siblings, return to the parent fiber.
1002 - workInProgress = unitOfWork;
1003 - do {
1004 - // The current, flushed, state of this fiber is the alternate. Ideally
1005 - // nothing should rely on this, but relying on it here means that we don't
1006 - // need an additional field on the work in progress.
1007 - const current = workInProgress.alternate;
1008 - const returnFiber = workInProgress.return;
1009 -
1010 - // Check if the work completed or if something threw.
1011 - if ((workInProgress.effectTag & Incomplete) === NoEffect) {
1012 - setCurrentDebugFiberInDEV(workInProgress);
1013 - let next;
1014 - if (
1015 - !enableProfilerTimer ||
1016 - (workInProgress.mode & ProfileMode) === NoContext
1017 - ) {
1018 - next = completeWork(current, workInProgress, renderExpirationTime);
1019 - } else {
1020 - startProfilerTimer(workInProgress);
1021 - next = completeWork(current, workInProgress, renderExpirationTime);
1022 - // Update render duration assuming we didn't error.
1023 - stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1024 - }
1025 - stopWorkTimer(workInProgress);
1026 - resetCurrentDebugFiberInDEV();
1027 - resetChildExpirationTime(workInProgress);
1028 -
1029 - if (next !== null) {
1030 - // Completing this fiber spawned new work. Work on that next.
1031 - return next;
1032 - }
1033 -
1034 - if (
1035 - returnFiber !== null &&
1036 - // Do not append effects to parents if a sibling failed to complete
1037 - (returnFiber.effectTag & Incomplete) === NoEffect
1038 - ) {
1039 - // Append all the effects of the subtree and this fiber onto the effect
1040 - // list of the parent. The completion order of the children affects the
1041 - // side-effect order.
1042 - if (returnFiber.firstEffect === null) {
1043 - returnFiber.firstEffect = workInProgress.firstEffect;
1044 - }
1045 - if (workInProgress.lastEffect !== null) {
1046 - if (returnFiber.lastEffect !== null) {
1047 - returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1048 - }
1049 - returnFiber.lastEffect = workInProgress.lastEffect;
1050 - }
1051 -
1052 - // If this fiber had side-effects, we append it AFTER the children's
1053 - // side-effects. We can perform certain side-effects earlier if needed,
1054 - // by doing multiple passes over the effect list. We don't want to
1055 - // schedule our own side-effect on our own list because if end up
1056 - // reusing children we'll schedule this effect onto itself since we're
1057 - // at the end.
1058 - const effectTag = workInProgress.effectTag;
1059 -
1060 - // Skip both NoWork and PerformedWork tags when creating the effect
1061 - // list. PerformedWork effect is read by React DevTools but shouldn't be
1062 - // committed.
1063 - if (effectTag > PerformedWork) {
1064 - if (returnFiber.lastEffect !== null) {
1065 - returnFiber.lastEffect.nextEffect = workInProgress;
1066 - } else {
1067 - returnFiber.firstEffect = workInProgress;
1068 - }
1069 - returnFiber.lastEffect = workInProgress;
1070 - }
1071 - }
1072 - } else {
1073 - // This fiber did not complete because something threw. Pop values off
1074 - // the stack without entering the complete phase. If this is a boundary,
1075 - // capture values if possible.
1076 - const next = unwindWork(workInProgress, renderExpirationTime);
1077 -
1078 - // Because this fiber did not complete, don't reset its expiration time.
1079 -
1080 - if (
1081 - enableProfilerTimer &&
1082 - (workInProgress.mode & ProfileMode) !== NoContext
1083 - ) {
1084 - // Record the render duration for the fiber that errored.
1085 - stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1086 -
1087 - // Include the time spent working on failed children before continuing.
1088 - let actualDuration = workInProgress.actualDuration;
1089 - let child = workInProgress.child;
1090 - while (child !== null) {
1091 - actualDuration += child.actualDuration;
1092 - child = child.sibling;
1093 - }
1094 - workInProgress.actualDuration = actualDuration;
1095 - }
1096 -
1097 - if (next !== null) {
1098 - // If completing this work spawned new work, do that next. We'll come
1099 - // back here again.
1100 - // Since we're restarting, remove anything that is not a host effect
1101 - // from the effect tag.
1102 - // TODO: The name stopFailedWorkTimer is misleading because Suspense
1103 - // also captures and restarts.
1104 - stopFailedWorkTimer(workInProgress);
1105 - next.effectTag &= HostEffectMask;
1106 - return next;
1107 - }
1108 - stopWorkTimer(workInProgress);
1109 -
1110 - if (returnFiber !== null) {
1111 - // Mark the parent fiber as incomplete and clear its effect list.
1112 - returnFiber.firstEffect = returnFiber.lastEffect = null;
1113 - returnFiber.effectTag |= Incomplete;
1114 - }
1115 - }
1116 -
1117 - const siblingFiber = workInProgress.sibling;
1118 - if (siblingFiber !== null) {
1119 - // If there is more work to do in this returnFiber, do that next.
1120 - return siblingFiber;
1121 - }
1122 - // Otherwise, return to the parent
1123 - workInProgress = returnFiber;
1124 - } while (workInProgress !== null);
1125 -
1126 - // We've reached the root.
1127 - if (workInProgressRootExitStatus === RootIncomplete) {
1128 - workInProgressRootExitStatus = RootCompleted;
1129 - }
1130 - return null;
1131 -}
1132 -
1133 -function resetChildExpirationTime(completedWork: Fiber) {
1134 - if (
1135 - renderExpirationTime !== Never &&
1136 - completedWork.childExpirationTime === Never
1137 - ) {
1138 - // The children of this component are hidden. Don't bubble their
1139 - // expiration times.
1140 - return;
1141 - }
1142 -
1143 - let newChildExpirationTime = NoWork;
1144 -
1145 - // Bubble up the earliest expiration time.
1146 - if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {
1147 - // In profiling mode, resetChildExpirationTime is also used to reset
1148 - // profiler durations.
1149 - let actualDuration = completedWork.actualDuration;
1150 - let treeBaseDuration = completedWork.selfBaseDuration;
1151 -
1152 - // When a fiber is cloned, its actualDuration is reset to 0. This value will
1153 - // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1154 - // When work is done, it should bubble to the parent's actualDuration. If
1155 - // the fiber has not been cloned though, (meaning no work was done), then
1156 - // this value will reflect the amount of time spent working on a previous
1157 - // render. In that case it should not bubble. We determine whether it was
1158 - // cloned by comparing the child pointer.
1159 - const shouldBubbleActualDurations =
1160 - completedWork.alternate === null ||
1161 - completedWork.child !== completedWork.alternate.child;
1162 -
1163 - let child = completedWork.child;
1164 - while (child !== null) {
1165 - const childUpdateExpirationTime = child.expirationTime;
1166 - const childChildExpirationTime = child.childExpirationTime;
1167 - if (childUpdateExpirationTime > newChildExpirationTime) {
1168 - newChildExpirationTime = childUpdateExpirationTime;
1169 - }
1170 - if (childChildExpirationTime > newChildExpirationTime) {
1171 - newChildExpirationTime = childChildExpirationTime;
1172 - }
1173 - if (shouldBubbleActualDurations) {
1174 - actualDuration += child.actualDuration;
1175 - }
1176 - treeBaseDuration += child.treeBaseDuration;
1177 - child = child.sibling;
1178 - }
1179 - completedWork.actualDuration = actualDuration;
1180 - completedWork.treeBaseDuration = treeBaseDuration;
1181 - } else {
1182 - let child = completedWork.child;
1183 - while (child !== null) {
1184 - const childUpdateExpirationTime = child.expirationTime;
1185 - const childChildExpirationTime = child.childExpirationTime;
1186 - if (childUpdateExpirationTime > newChildExpirationTime) {
1187 - newChildExpirationTime = childUpdateExpirationTime;
1188 - }
1189 - if (childChildExpirationTime > newChildExpirationTime) {
1190 - newChildExpirationTime = childChildExpirationTime;
1191 - }
1192 - child = child.sibling;
1193 - }
1194 - }
1195 -
1196 - completedWork.childExpirationTime = newChildExpirationTime;
1197 -}
1198 -
1199 -function commitRoot(root, expirationTime) {
1200 - runWithPriority(
1201 - ImmediatePriority,
1202 - commitRootImpl.bind(null, root, expirationTime),
1203 - );
1204 - // If there are passive effects, schedule a callback to flush them. This goes
1205 - // outside commitRootImpl so that it inherits the priority of the render.
1206 - if (rootWithPendingPassiveEffects !== null) {
1207 - const priorityLevel = getCurrentPriorityLevel();
1208 - scheduleCallback(priorityLevel, () => {
1209 - flushPassiveEffects();
1210 - return null;
1211 - });
1212 - }
1213 - return null;
1214 -}
1215 -
1216 -function commitRootImpl(root, expirationTime) {
1217 - flushPassiveEffects();
1218 - flushRenderPhaseStrictModeWarningsInDEV();
1219 -
1220 - invariant(
1221 - workPhase !== RenderPhase && workPhase !== CommitPhase,
1222 - 'Should not already be working.',
1223 - );
1224 - const finishedWork = root.current.alternate;
1225 - invariant(finishedWork !== null, 'Should have a work-in-progress root.');
1226 -
1227 - // commitRoot never returns a continuation; it always finishes synchronously.
1228 - // So we can clear these now to allow a new callback to be scheduled.
1229 - root.callbackNode = null;
1230 - root.callbackExpirationTime = NoWork;
1231 -
1232 - startCommitTimer();
1233 -
1234 - // Update the first and last pending times on this root. The new first
1235 - // pending time is whatever is left on the root fiber.
1236 - const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
1237 - const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
1238 - const firstPendingTimeBeforeCommit =
1239 - childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
1240 - ? childExpirationTimeBeforeCommit
1241 - : updateExpirationTimeBeforeCommit;
1242 - root.firstPendingTime = firstPendingTimeBeforeCommit;
1243 - if (firstPendingTimeBeforeCommit < root.lastPendingTime) {
1244 - // This usually means we've finished all the work, but it can also happen
1245 - // when something gets downprioritized during render, like a hidden tree.
1246 - root.lastPendingTime = firstPendingTimeBeforeCommit;
1247 - }
1248 -
1249 - if (root === workInProgressRoot) {
1250 - // We can reset these now that they are finished.
1251 - workInProgressRoot = null;
1252 - workInProgress = null;
1253 - renderExpirationTime = NoWork;
1254 - } else {
1255 - // This indicates that the last root we worked on is not the same one that
1256 - // we're committing now. This most commonly happens when a suspended root
1257 - // times out.
1258 - }
1259 -
1260 - // Get the list of effects.
1261 - let firstEffect;
1262 - if (finishedWork.effectTag > PerformedWork) {
1263 - // A fiber's effect list consists only of its children, not itself. So if
1264 - // the root has an effect, we need to add it to the end of the list. The
1265 - // resulting list is the set that would belong to the root's parent, if it
1266 - // had one; that is, all the effects in the tree including the root.
1267 - if (finishedWork.lastEffect !== null) {
1268 - finishedWork.lastEffect.nextEffect = finishedWork;
1269 - firstEffect = finishedWork.firstEffect;
1270 - } else {
1271 - firstEffect = finishedWork;
1272 - }
1273 - } else {
1274 - // There is no effect on the root.
1275 - firstEffect = finishedWork.firstEffect;
1276 - }
1277 -
1278 - if (firstEffect !== null) {
1279 - const prevWorkPhase = workPhase;
1280 - workPhase = CommitPhase;
1281 - let prevInteractions: Set<Interaction> | null = null;
1282 - if (enableSchedulerTracing) {
1283 - prevInteractions = __interactionsRef.current;
1284 - __interactionsRef.current = root.memoizedInteractions;
1285 - }
1286 -
1287 - // Reset this to null before calling lifecycles
1288 - ReactCurrentOwner.current = null;
1289 -
1290 - // The commit phase is broken into several sub-phases. We do a separate pass
1291 - // of the effect list for each phase: all mutation effects come before all
1292 - // layout effects, and so on.
1293 -
1294 - // The first phase a "before mutation" phase. We use this phase to read the
1295 - // state of the host tree right before we mutate it. This is where
1296 - // getSnapshotBeforeUpdate is called.
1297 - startCommitSnapshotEffectsTimer();
1298 - prepareForCommit(root.containerInfo);
1299 - nextEffect = firstEffect;
1300 - do {
1301 - if (__DEV__) {
1302 - invokeGuardedCallback(null, commitBeforeMutationEffects, null);
1303 - if (hasCaughtError()) {
1304 - invariant(nextEffect !== null, 'Should be working on an effect.');
1305 - const error = clearCaughtError();
1306 - captureCommitPhaseError(nextEffect, error);
1307 - nextEffect = nextEffect.nextEffect;
1308 - }
1309 - } else {
1310 - try {
1311 - commitBeforeMutationEffects();
1312 - } catch (error) {
1313 - invariant(nextEffect !== null, 'Should be working on an effect.');
1314 - captureCommitPhaseError(nextEffect, error);
1315 - nextEffect = nextEffect.nextEffect;
1316 - }
1317 - }
1318 - } while (nextEffect !== null);
1319 - stopCommitSnapshotEffectsTimer();
1320 -
1321 - if (enableProfilerTimer) {
1322 - // Mark the current commit time to be shared by all Profilers in this
1323 - // batch. This enables them to be grouped later.
1324 - recordCommitTime();
1325 - }
1326 -
1327 - // The next phase is the mutation phase, where we mutate the host tree.
1328 - startCommitHostEffectsTimer();
1329 - nextEffect = firstEffect;
1330 - do {
1331 - if (__DEV__) {
1332 - invokeGuardedCallback(null, commitMutationEffects, null);
1333 - if (hasCaughtError()) {
1334 - invariant(nextEffect !== null, 'Should be working on an effect.');
1335 - const error = clearCaughtError();
1336 - captureCommitPhaseError(nextEffect, error);
1337 - nextEffect = nextEffect.nextEffect;
1338 - }
1339 - } else {
1340 - try {
1341 - commitMutationEffects();
1342 - } catch (error) {
1343 - invariant(nextEffect !== null, 'Should be working on an effect.');
1344 - captureCommitPhaseError(nextEffect, error);
1345 - nextEffect = nextEffect.nextEffect;
1346 - }
1347 - }
1348 - } while (nextEffect !== null);
1349 - stopCommitHostEffectsTimer();
1350 - resetAfterCommit(root.containerInfo);
1351 -
1352 - // The work-in-progress tree is now the current tree. This must come after
1353 - // the mutation phase, so that the previous tree is still current during
1354 - // componentWillUnmount, but before the layout phase, so that the finished
1355 - // work is current during componentDidMount/Update.
1356 - root.current = finishedWork;
1357 -
1358 - // The next phase is the layout phase, where we call effects that read
1359 - // the host tree after it's been mutated. The idiomatic use case for this is
1360 - // layout, but class component lifecycles also fire here for legacy reasons.
1361 - startCommitLifeCyclesTimer();
1362 - nextEffect = firstEffect;
1363 - do {
1364 - if (__DEV__) {
1365 - invokeGuardedCallback(
1366 - null,
1367 - commitLayoutEffects,
1368 - null,
1369 - root,
1370 - expirationTime,
1371 - );
1372 - if (hasCaughtError()) {
1373 - invariant(nextEffect !== null, 'Should be working on an effect.');
1374 - const error = clearCaughtError();
1375 - captureCommitPhaseError(nextEffect, error);
1376 - nextEffect = nextEffect.nextEffect;
1377 - }
1378 - } else {
1379 - try {
1380 - commitLayoutEffects(root, expirationTime);
1381 - } catch (error) {
1382 - invariant(nextEffect !== null, 'Should be working on an effect.');
1383 - captureCommitPhaseError(nextEffect, error);
1384 - nextEffect = nextEffect.nextEffect;
1385 - }
1386 - }
1387 - } while (nextEffect !== null);
1388 - stopCommitLifeCyclesTimer();
1389 -
1390 - nextEffect = null;
1391 -
1392 - if (enableSchedulerTracing) {
1393 - __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
1394 - }
1395 - workPhase = prevWorkPhase;
1396 - } else {
1397 - // No effects.
1398 - root.current = finishedWork;
1399 - // Measure these anyway so the flamegraph explicitly shows that there were
1400 - // no effects.
1401 - // TODO: Maybe there's a better way to report this.
1402 - startCommitSnapshotEffectsTimer();
1403 - stopCommitSnapshotEffectsTimer();
1404 - if (enableProfilerTimer) {
1405 - recordCommitTime();
1406 - }
1407 - startCommitHostEffectsTimer();
1408 - stopCommitHostEffectsTimer();
1409 - startCommitLifeCyclesTimer();
1410 - stopCommitLifeCyclesTimer();
1411 - }
1412 -
1413 - stopCommitTimer();
1414 -
1415 - if (rootDoesHavePassiveEffects) {
1416 - // This commit has passive effects. Stash a reference to them. But don't
1417 - // schedule a callback until after flushing layout work.
1418 - rootDoesHavePassiveEffects = false;
1419 - rootWithPendingPassiveEffects = root;
1420 - pendingPassiveEffectsExpirationTime = expirationTime;
1421 - } else {
1422 - if (enableSchedulerTracing) {
1423 - // If there are no passive effects, then we can complete the pending
1424 - // interactions. Otherwise, we'll wait until after the passive effects
1425 - // are flushed.
1426 - finishPendingInteractions(root, expirationTime);
1427 - }
1428 - }
1429 -
1430 - // Check if there's remaining work on this root
1431 - const remainingExpirationTime = root.firstPendingTime;
1432 - if (remainingExpirationTime !== NoWork) {
1433 - const currentTime = requestCurrentTime();
1434 - const priorityLevel = inferPriorityFromExpirationTime(
1435 - currentTime,
1436 - remainingExpirationTime,
1437 - );
1438 - scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);
1439 - } else {
1440 - // If there's no remaining work, we can clear the set of already failed
1441 - // error boundaries.
1442 - legacyErrorBoundariesThatAlreadyFailed = null;
1443 - }
1444 -
1445 - onCommitRoot(finishedWork.stateNode);
1446 -
1447 - if (remainingExpirationTime === Sync) {
1448 - // Count the number of times the root synchronously re-renders without
1449 - // finishing. If there are too many, it indicates an infinite update loop.
1450 - if (root === rootWithNestedUpdates) {
1451 - nestedUpdateCount++;
1452 - } else {
1453 - nestedUpdateCount = 0;
1454 - rootWithNestedUpdates = root;
1455 - }
1456 - } else {
1457 - nestedUpdateCount = 0;
1458 - }
1459 -
1460 - if (hasUncaughtError) {
1461 - hasUncaughtError = false;
1462 - const error = firstUncaughtError;
1463 - firstUncaughtError = null;
1464 - throw error;
1465 - }
1466 -
1467 - if (workPhase === LegacyUnbatchedPhase) {
1468 - // This is a legacy edge case. We just committed the initial mount of
1469 - // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
1470 - // synchronously, but layout updates should be deferred until the end
1471 - // of the batch.
1472 - return null;
1473 - }
1474 -
1475 - // If layout work was scheduled, flush it now.
1476 - flushImmediateQueue();
1477 - return null;
1478 -}
1479 -
1480 -function commitBeforeMutationEffects() {
1481 - while (nextEffect !== null) {
1482 - if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
1483 - setCurrentDebugFiberInDEV(nextEffect);
1484 - recordEffect();
1485 -
1486 - const current = nextEffect.alternate;
1487 - commitBeforeMutationEffectOnFiber(current, nextEffect);
1488 -
1489 - resetCurrentDebugFiberInDEV();
1490 - }
1491 - nextEffect = nextEffect.nextEffect;
1492 - }
1493 -}
1494 -
1495 -function commitMutationEffects() {
1496 - // TODO: Should probably move the bulk of this function to commitWork.
1497 - while (nextEffect !== null) {
1498 - setCurrentDebugFiberInDEV(nextEffect);
1499 -
1500 - const effectTag = nextEffect.effectTag;
1501 -
1502 - if (effectTag & ContentReset) {
1503 - commitResetTextContent(nextEffect);
1504 - }
1505 -
1506 - if (effectTag & Ref) {
1507 - const current = nextEffect.alternate;
1508 - if (current !== null) {
1509 - commitDetachRef(current);
1510 - }
1511 - }
1512 -
1513 - // The following switch statement is only concerned about placement,
1514 - // updates, and deletions. To avoid needing to add a case for every possible
1515 - // bitmap value, we remove the secondary effects from the effect tag and
1516 - // switch on that value.
1517 - let primaryEffectTag = effectTag & (Placement | Update | Deletion);
1518 - switch (primaryEffectTag) {
1519 - case Placement: {
1520 - commitPlacement(nextEffect);
1521 - // Clear the "placement" from effect tag so that we know that this is
1522 - // inserted, before any life-cycles like componentDidMount gets called.
1523 - // TODO: findDOMNode doesn't rely on this any more but isMounted does
1524 - // and isMounted is deprecated anyway so we should be able to kill this.
1525 - nextEffect.effectTag &= ~Placement;
1526 - break;
1527 - }
1528 - case PlacementAndUpdate: {
1529 - // Placement
1530 - commitPlacement(nextEffect);
1531 - // Clear the "placement" from effect tag so that we know that this is
1532 - // inserted, before any life-cycles like componentDidMount gets called.
1533 - nextEffect.effectTag &= ~Placement;
1534 -
1535 - // Update
1536 - const current = nextEffect.alternate;
1537 - commitWork(current, nextEffect);
1538 - break;
1539 - }
1540 - case Update: {
1541 - const current = nextEffect.alternate;
1542 - commitWork(current, nextEffect);
1543 - break;
1544 - }
1545 - case Deletion: {
1546 - commitDeletion(nextEffect);
1547 - break;
1548 - }
1549 - }
1550 -
1551 - // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
1552 - recordEffect();
1553 -
1554 - resetCurrentDebugFiberInDEV();
1555 - nextEffect = nextEffect.nextEffect;
1556 - }
1557 -}
1558 -
1559 -function commitLayoutEffects(
1560 - root: FiberRoot,
1561 - committedExpirationTime: ExpirationTime,
1562 -) {
1563 - // TODO: Should probably move the bulk of this function to commitWork.
1564 - while (nextEffect !== null) {
1565 - setCurrentDebugFiberInDEV(nextEffect);
1566 -
1567 - const effectTag = nextEffect.effectTag;
1568 -
1569 - if (effectTag & (Update | Callback)) {
1570 - recordEffect();
1571 - const current = nextEffect.alternate;
1572 - commitLayoutEffectOnFiber(
1573 - root,
1574 - current,
1575 - nextEffect,
1576 - committedExpirationTime,
1577 - );
1578 - }
1579 -
1580 - if (effectTag & Ref) {
1581 - recordEffect();
1582 - commitAttachRef(nextEffect);
1583 - }
1584 -
1585 - if (effectTag & Passive) {
1586 - rootDoesHavePassiveEffects = true;
1587 - }
1588 -
1589 - resetCurrentDebugFiberInDEV();
1590 - nextEffect = nextEffect.nextEffect;
1591 - }
1592 -}
1593 -
1594 -export function flushPassiveEffects() {
1595 - if (rootWithPendingPassiveEffects === null) {
1596 - return false;
1597 - }
1598 - const root = rootWithPendingPassiveEffects;
1599 - const expirationTime = pendingPassiveEffectsExpirationTime;
1600 - rootWithPendingPassiveEffects = null;
1601 - pendingPassiveEffectsExpirationTime = NoWork;
1602 -
1603 - let prevInteractions: Set<Interaction> | null = null;
1604 - if (enableSchedulerTracing) {
1605 - prevInteractions = __interactionsRef.current;
1606 - __interactionsRef.current = root.memoizedInteractions;
1607 - }
1608 -
1609 - invariant(
1610 - workPhase !== RenderPhase && workPhase !== CommitPhase,
1611 - 'Cannot flush passive effects while already rendering.',
1612 - );
1613 - const prevWorkPhase = workPhase;
1614 - workPhase = CommitPhase;
1615 -
1616 - // Note: This currently assumes there are no passive effects on the root
1617 - // fiber, because the root is not part of its own effect list. This could
1618 - // change in the future.
1619 - let effect = root.current.firstEffect;
1620 - while (effect !== null) {
1621 - if (__DEV__) {
1622 - setCurrentDebugFiberInDEV(effect);
1623 - invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
1624 - if (hasCaughtError()) {
1625 - invariant(effect !== null, 'Should be working on an effect.');
1626 - const error = clearCaughtError();
1627 - captureCommitPhaseError(effect, error);
1628 - }
1629 - resetCurrentDebugFiberInDEV();
1630 - } else {
1631 - try {
1632 - commitPassiveHookEffects(effect);
1633 - } catch (error) {
1634 - invariant(effect !== null, 'Should be working on an effect.');
1635 - captureCommitPhaseError(effect, error);
1636 - }
1637 - }
1638 - effect = effect.nextEffect;
1639 - }
1640 -
1641 - if (enableSchedulerTracing) {
1642 - __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
1643 - finishPendingInteractions(root, expirationTime);
1644 - }
1645 -
1646 - workPhase = prevWorkPhase;
1647 - flushImmediateQueue();
1648 -
1649 - // If additional passive effects were scheduled, increment a counter. If this
1650 - // exceeds the limit, we'll fire a warning.
1651 - nestedPassiveUpdateCount =
1652 - rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
1653 -
1654 - return true;
1655 -}
1656 -
1657 -export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
1658 - return (
1659 - legacyErrorBoundariesThatAlreadyFailed !== null &&
1660 - legacyErrorBoundariesThatAlreadyFailed.has(instance)
1661 - );
1662 -}
1663 -
1664 -export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
1665 - if (legacyErrorBoundariesThatAlreadyFailed === null) {
1666 - legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
1667 - } else {
1668 - legacyErrorBoundariesThatAlreadyFailed.add(instance);
1669 - }
1670 -}
1671 -
1672 -function prepareToThrowUncaughtError(error: mixed) {
1673 - if (!hasUncaughtError) {
1674 - hasUncaughtError = true;
1675 - firstUncaughtError = error;
1676 - }
1677 -}
1678 -export const onUncaughtError = prepareToThrowUncaughtError;
1679 -
1680 -function captureCommitPhaseErrorOnRoot(
1681 - rootFiber: Fiber,
1682 - sourceFiber: Fiber,
1683 - error: mixed,
1684 -) {
1685 - const errorInfo = createCapturedValue(error, sourceFiber);
1686 - const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
1687 - enqueueUpdate(rootFiber, update);
1688 - const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
1689 - if (root !== null) {
1690 - scheduleCallbackForRoot(root, ImmediatePriority, Sync);
1691 - }
1692 -}
1693 -
1694 -export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
1695 - if (sourceFiber.tag === HostRoot) {
1696 - // Error was thrown at the root. There is no parent, so the root
1697 - // itself should capture it.
1698 - captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
1699 - return;
1700 - }
1701 -
1702 - let fiber = sourceFiber.return;
1703 - while (fiber !== null) {
1704 - if (fiber.tag === HostRoot) {
1705 - captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
1706 - return;
1707 - } else if (fiber.tag === ClassComponent) {
1708 - const ctor = fiber.type;
1709 - const instance = fiber.stateNode;
1710 - if (
1711 - typeof ctor.getDerivedStateFromError === 'function' ||
1712 - (typeof instance.componentDidCatch === 'function' &&
1713 - !isAlreadyFailedLegacyErrorBoundary(instance))
1714 - ) {
1715 - const errorInfo = createCapturedValue(error, sourceFiber);
1716 - const update = createClassErrorUpdate(
1717 - fiber,
1718 - errorInfo,
1719 - // TODO: This is always sync
1720 - Sync,
1721 - );
1722 - enqueueUpdate(fiber, update);
1723 - const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
1724 - if (root !== null) {
1725 - scheduleCallbackForRoot(root, ImmediatePriority, Sync);
1726 - }
1727 - return;
1728 - }
1729 - }
1730 - fiber = fiber.return;
1731 - }
1732 -}
1733 -
1734 -export function pingSuspendedRoot(
1735 - root: FiberRoot,
1736 - thenable: Thenable,
1737 - suspendedTime: ExpirationTime,
1738 -) {
1739 - const pingCache = root.pingCache;
1740 - if (pingCache !== null) {
1741 - // The thenable resolved, so we no longer need to memoize, because it will
1742 - // never be thrown again.
1743 - pingCache.delete(thenable);
1744 - }
1745 -
1746 - if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
1747 - // Received a ping at the same priority level at which we're currently
1748 - // rendering. Restart from the root. Don't need to schedule a ping because
1749 - // we're already working on this tree.
1750 - prepareFreshStack(root, renderExpirationTime);
1751 - return;
1752 - }
1753 -
1754 - const lastPendingTime = root.lastPendingTime;
1755 - if (lastPendingTime < suspendedTime) {
1756 - // The root is no longer suspended at this time.
1757 - return;
1758 - }
1759 -
1760 - const pingTime = root.pingTime;
1761 - if (pingTime !== NoWork && pingTime < suspendedTime) {
1762 - // There's already a lower priority ping scheduled.
1763 - return;
1764 - }
1765 -
1766 - // Mark the time at which this ping was scheduled.
1767 - root.pingTime = suspendedTime;
1768 -
1769 - const currentTime = requestCurrentTime();
1770 - const priorityLevel = inferPriorityFromExpirationTime(
1771 - currentTime,
1772 - suspendedTime,
1773 - );
1774 - scheduleCallbackForRoot(root, priorityLevel, suspendedTime);
1775 -}
1776 -
1777 -export function retryTimedOutBoundary(boundaryFiber: Fiber) {
1778 - // The boundary fiber (a Suspense component) previously timed out and was
1779 - // rendered in its fallback state. One of the promises that suspended it has
1780 - // resolved, which means at least part of the tree was likely unblocked. Try
1781 - // rendering again, at a new expiration time.
1782 - const currentTime = requestCurrentTime();
1783 - const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
1784 - // TODO: Special case idle priority?
1785 - const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);
1786 - const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
1787 - if (root !== null) {
1788 - scheduleCallbackForRoot(root, priorityLevel, retryTime);
1789 - }
1790 -}
1791 -
1792 -export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
1793 - let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
1794 - if (enableSuspenseServerRenderer) {
1795 - switch (boundaryFiber.tag) {
1796 - case SuspenseComponent:
1797 - retryCache = boundaryFiber.stateNode;
1798 - break;
1799 - case DehydratedSuspenseComponent:
1800 - retryCache = boundaryFiber.memoizedState;
1801 - break;
1802 - default:
1803 - invariant(
1804 - false,
1805 - 'Pinged unknown suspense boundary type. ' +
1806 - 'This is probably a bug in React.',
1807 - );
1808 - }
1809 - } else {
1810 - retryCache = boundaryFiber.stateNode;
1811 - }
1812 -
1813 - if (retryCache !== null) {
1814 - // The thenable resolved, so we no longer need to memoize, because it will
1815 - // never be thrown again.
1816 - retryCache.delete(thenable);
1817 - }
1818 -
1819 - retryTimedOutBoundary(boundaryFiber);
1820 -}
1821 -
1822 -// Computes the next Just Noticeable Difference (JND) boundary.
1823 -// The theory is that a person can't tell the difference between small differences in time.
1824 -// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
1825 -// difference in the experience. However, waiting for longer might mean that we can avoid
1826 -// showing an intermediate loading state. The longer we have already waited, the harder it
1827 -// is to tell small differences in time. Therefore, the longer we've already waited,
1828 -// the longer we can wait additionally. At some point we have to give up though.
1829 -// We pick a train model where the next boundary commits at a consistent schedule.
1830 -// These particular numbers are vague estimates. We expect to adjust them based on research.
1831 -function jnd(timeElapsed: number) {
1832 - return timeElapsed < 120
1833 - ? 120
1834 - : timeElapsed < 480
1835 - ? 480
1836 - : timeElapsed < 1080
1837 - ? 1080
1838 - : timeElapsed < 1920
1839 - ? 1920
1840 - : timeElapsed < 3000
1841 - ? 3000
1842 - : timeElapsed < 4320
1843 - ? 4320
1844 - : ceil(timeElapsed / 1960) * 1960;
1845 -}
1846 -
1847 -function computeMsUntilTimeout(
1848 - mostRecentEventTime: ExpirationTime,
1849 - committedExpirationTime: ExpirationTime,
1850 -) {
1851 - if (disableYielding) {
1852 - // Timeout immediately when yielding is disabled.
1853 - return 0;
1854 - }
1855 -
1856 - const eventTimeMs: number = inferTimeFromExpirationTime(mostRecentEventTime);
1857 - const currentTimeMs: number = now();
1858 - const timeElapsed = currentTimeMs - eventTimeMs;
1859 -
1860 - let msUntilTimeout = jnd(timeElapsed) - timeElapsed;
1861 -
1862 - // Compute the time until this render pass would expire.
1863 - const timeUntilExpirationMs =
1864 - expirationTimeToMs(committedExpirationTime) + initialTimeMs - currentTimeMs;
1865 -
1866 - // Clamp the timeout to the expiration time.
1867 - // TODO: Once the event time is exact instead of inferred from expiration time
1868 - // we don't need this.
1869 - if (timeUntilExpirationMs < msUntilTimeout) {
1870 - msUntilTimeout = timeUntilExpirationMs;
1871 - }
1872 -
1873 - // This is the value that is passed to `setTimeout`.
1874 - return msUntilTimeout;
1875 -}
1876 -
1877 -function checkForNestedUpdates() {
1878 - if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1879 - nestedUpdateCount = 0;
1880 - rootWithNestedUpdates = null;
1881 - invariant(
1882 - false,
1883 - 'Maximum update depth exceeded. This can happen when a component ' +
1884 - 'repeatedly calls setState inside componentWillUpdate or ' +
1885 - 'componentDidUpdate. React limits the number of nested updates to ' +
1886 - 'prevent infinite loops.',
1887 - );
1888 - }
1889 -
1890 - if (__DEV__) {
1891 - if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
1892 - nestedPassiveUpdateCount = 0;
1893 - warning(
1894 - false,
1895 - 'Maximum update depth exceeded. This can happen when a component ' +
1896 - "calls setState inside useEffect, but useEffect either doesn't " +
1897 - 'have a dependency array, or one of the dependencies changes on ' +
1898 - 'every render.',
1899 - );
1900 - }
1901 - }
1902 -}
1903 -
1904 -function flushRenderPhaseStrictModeWarningsInDEV() {
1905 - if (__DEV__) {
1906 - ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
1907 - ReactStrictModeWarnings.flushLegacyContextWarning();
1908 -
1909 - if (warnAboutDeprecatedLifecycles) {
1910 - ReactStrictModeWarnings.flushPendingDeprecationWarnings();
1911 - }
1912 - }
1913 -}
1914 -
1915 -function stopFinishedWorkLoopTimer() {
1916 - const didCompleteRoot = true;
1917 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1918 - interruptedBy = null;
1919 -}
1920 -
1921 -function stopInterruptedWorkLoopTimer() {
1922 - // TODO: Track which fiber caused the interruption.
1923 - const didCompleteRoot = false;
1924 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1925 - interruptedBy = null;
1926 -}
1927 -
1928 -function checkForInterruption(
1929 - fiberThatReceivedUpdate: Fiber,
1930 - updateExpirationTime: ExpirationTime,
1931 -) {
1932 - if (
1933 - enableUserTimingAPI &&
1934 - workInProgressRoot !== null &&
1935 - updateExpirationTime > renderExpirationTime
1936 - ) {
1937 - interruptedBy = fiberThatReceivedUpdate;
1938 - }
1939 -}
1940 -
1941 -let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
1942 -function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
1943 - if (__DEV__) {
1944 - const tag = fiber.tag;
1945 - if (
1946 - tag !== HostRoot &&
1947 - tag !== ClassComponent &&
1948 - tag !== FunctionComponent &&
1949 - tag !== ForwardRef &&
1950 - tag !== MemoComponent &&
1951 - tag !== SimpleMemoComponent
1952 - ) {
1953 - // Only warn for user-defined components, not internal ones like Suspense.
1954 - return;
1955 - }
1956 - // We show the whole stack but dedupe on the top component's name because
1957 - // the problematic code almost always lies inside that component.
1958 - const componentName = getComponentName(fiber.type) || 'ReactComponent';
1959 - if (didWarnStateUpdateForUnmountedComponent !== null) {
1960 - if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
1961 - return;
1962 - }
1963 - didWarnStateUpdateForUnmountedComponent.add(componentName);
1964 - } else {
1965 - didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
1966 - }
1967 - warningWithoutStack(
1968 - false,
1969 - "Can't perform a React state update on an unmounted component. This " +
1970 - 'is a no-op, but it indicates a memory leak in your application. To ' +
1971 - 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
1972 - tag === ClassComponent
1973 - ? 'the componentWillUnmount method'
1974 - : 'a useEffect cleanup function',
1975 - getStackByFiberInDevAndProd(fiber),
1976 - );
1977 - }
1978 -}
1979 -
1980 -let beginWork;
1981 -if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1982 - let dummyFiber = null;
1983 - beginWork = (current, unitOfWork, expirationTime) => {
1984 - // If a component throws an error, we replay it again in a synchronously
1985 - // dispatched event, so that the debugger will treat it as an uncaught
1986 - // error See ReactErrorUtils for more information.
1987 -
1988 - // Before entering the begin phase, copy the work-in-progress onto a dummy
1989 - // fiber. If beginWork throws, we'll use this to reset the state.
1990 - const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
1991 - dummyFiber,
1992 - unitOfWork,
1993 - );
1994 - try {
1995 - return originalBeginWork(current, unitOfWork, expirationTime);
1996 - } catch (originalError) {
1997 - if (
1998 - originalError !== null &&
1999 - typeof originalError === 'object' &&
2000 - typeof originalError.then === 'function'
2001 - ) {
2002 - // Don't replay promises. Treat everything else like an error.
2003 - throw originalError;
2004 - }
2005 -
2006 - // Keep this code in sync with renderRoot; any changes here must have
2007 - // corresponding changes there.
2008 - resetContextDependencies();
2009 - resetHooks();
2010 -
2011 - // Unwind the failed stack frame
2012 - unwindInterruptedWork(unitOfWork);
2013 -
2014 - // Restore the original properties of the fiber.
2015 - assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
2016 -
2017 - if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
2018 - // Reset the profiler timer.
2019 - startProfilerTimer(unitOfWork);
2020 - }
2021 -
2022 - // Run beginWork again.
2023 - invokeGuardedCallback(
2024 - null,
2025 - originalBeginWork,
2026 - null,
2027 - current,
2028 - unitOfWork,
2029 - expirationTime,
2030 - );
2031 -
2032 - if (hasCaughtError()) {
2033 - const replayError = clearCaughtError();
2034 - // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
2035 - // Rethrow this error instead of the original one.
2036 - throw replayError;
2037 - } else {
2038 - // This branch is reachable if the render phase is impure.
2039 - throw originalError;
2040 - }
2041 - }
2042 - };
2043 -} else {
2044 - beginWork = originalBeginWork;
2045 -}
2046 -
2047 -let didWarnAboutUpdateInRender = false;
2048 -let didWarnAboutUpdateInGetChildContext = false;
2049 -function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
2050 - if (__DEV__) {
2051 - if (fiber.tag === ClassComponent) {
2052 - switch (ReactCurrentDebugFiberPhaseInDEV) {
2053 - case 'getChildContext':
2054 - if (didWarnAboutUpdateInGetChildContext) {
2055 - return;
2056 - }
2057 - warningWithoutStack(
2058 - false,
2059 - 'setState(...): Cannot call setState() inside getChildContext()',
2060 - );
2061 - didWarnAboutUpdateInGetChildContext = true;
2062 - break;
2063 - case 'render':
2064 - if (didWarnAboutUpdateInRender) {
2065 - return;
2066 - }
2067 - warningWithoutStack(
2068 - false,
2069 - 'Cannot update during an existing state transition (such as ' +
2070 - 'within `render`). Render methods should be a pure function of ' +
2071 - 'props and state.',
2072 - );
2073 - didWarnAboutUpdateInRender = true;
2074 - break;
2075 - }
2076 - }
2077 - }
2078 -}
2079 -
2080 -function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2081 - if (__DEV__) {
2082 - if (
2083 - workPhase === NotWorking &&
2084 - ReactShouldWarnActingUpdates.current === false
2085 - ) {
2086 - warningWithoutStack(
2087 - false,
2088 - 'An update to %s inside a test was not wrapped in act(...).\n\n' +
2089 - 'When testing, code that causes React state updates should be ' +
2090 - 'wrapped into act(...):\n\n' +
2091 - 'act(() => {\n' +
2092 - ' /* fire events that update state */\n' +
2093 - '});\n' +
2094 - '/* assert on the output */\n\n' +
2095 - "This ensures that you're testing the behavior the user would see " +
2096 - 'in the browser.' +
2097 - ' Learn more at https://fb.me/react-wrap-tests-with-act' +
2098 - '%s',
2099 - getComponentName(fiber.type),
2100 - getStackByFiberInDevAndProd(fiber),
2101 - );
2102 - }
2103 - }
2104 -}
2105 -
2106 -export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2107 -
2108 -function computeThreadID(root, expirationTime) {
2109 - // Interaction threads are unique per root and expiration time.
2110 - return expirationTime * 1000 + root.interactionThreadID;
2111 -}
2112 -
2113 -function schedulePendingInteraction(root, expirationTime) {
2114 - // This is called when work is scheduled on a root. It sets up a pending
2115 - // interaction, which is completed once the work commits.
2116 - if (!enableSchedulerTracing) {
2117 - return;
2118 - }
2119 -
2120 - const interactions = __interactionsRef.current;
2121 - if (interactions.size > 0) {
2122 - const pendingInteractionMap = root.pendingInteractionMap;
2123 - const pendingInteractions = pendingInteractionMap.get(expirationTime);
2124 - if (pendingInteractions != null) {
2125 - interactions.forEach(interaction => {
2126 - if (!pendingInteractions.has(interaction)) {
2127 - // Update the pending async work count for previously unscheduled interaction.
2128 - interaction.__count++;
2129 - }
2130 -
2131 - pendingInteractions.add(interaction);
2132 - });
2133 - } else {
2134 - pendingInteractionMap.set(expirationTime, new Set(interactions));
2135 -
2136 - // Update the pending async work count for the current interactions.
2137 - interactions.forEach(interaction => {
2138 - interaction.__count++;
2139 - });
2140 - }
2141 -
2142 - const subscriber = __subscriberRef.current;
2143 - if (subscriber !== null) {
2144 - const threadID = computeThreadID(root, expirationTime);
2145 - subscriber.onWorkScheduled(interactions, threadID);
2146 - }
2147 - }
2148 -}
2149 -
2150 -function startWorkOnPendingInteraction(root, expirationTime) {
2151 - // This is called when new work is started on a root.
2152 - if (!enableSchedulerTracing) {
2153 - return;
2154 - }
2155 -
2156 - // Determine which interactions this batch of work currently includes, So that
2157 - // we can accurately attribute time spent working on it, And so that cascading
2158 - // work triggered during the render phase will be associated with it.
2159 - const interactions: Set<Interaction> = new Set();
2160 - root.pendingInteractionMap.forEach(
2161 - (scheduledInteractions, scheduledExpirationTime) => {
2162 - if (scheduledExpirationTime >= expirationTime) {
2163 - scheduledInteractions.forEach(interaction =>
2164 - interactions.add(interaction),
2165 - );
2166 - }
2167 - },
2168 - );
2169 -
2170 - // Store the current set of interactions on the FiberRoot for a few reasons:
2171 - // We can re-use it in hot functions like renderRoot() without having to
2172 - // recalculate it. We will also use it in commitWork() to pass to any Profiler
2173 - // onRender() hooks. This also provides DevTools with a way to access it when
2174 - // the onCommitRoot() hook is called.
2175 - root.memoizedInteractions = interactions;
2176 -
2177 - if (interactions.size > 0) {
2178 - const subscriber = __subscriberRef.current;
2179 - if (subscriber !== null) {
2180 - const threadID = computeThreadID(root, expirationTime);
2181 - try {
2182 - subscriber.onWorkStarted(interactions, threadID);
2183 - } catch (error) {
2184 - // If the subscriber throws, rethrow it in a separate task
2185 - scheduleCallback(ImmediatePriority, () => {
2186 - throw error;
2187 - });
2188 - }
2189 - }
2190 - }
2191 -}
2192 -
2193 -function finishPendingInteractions(root, committedExpirationTime) {
2194 - if (!enableSchedulerTracing) {
2195 - return;
2196 - }
2197 -
2198 - const earliestRemainingTimeAfterCommit = root.firstPendingTime;
2199 -
2200 - let subscriber;
2201 -
2202 - try {
2203 - subscriber = __subscriberRef.current;
2204 - if (subscriber !== null && root.memoizedInteractions.size > 0) {
2205 - const threadID = computeThreadID(root, committedExpirationTime);
2206 - subscriber.onWorkStopped(root.memoizedInteractions, threadID);
2207 - }
2208 - } catch (error) {
2209 - // If the subscriber throws, rethrow it in a separate task
2210 - scheduleCallback(ImmediatePriority, () => {
2211 - throw error;
2212 - });
2213 - } finally {
2214 - // Clear completed interactions from the pending Map.
2215 - // Unless the render was suspended or cascading work was scheduled,
2216 - // In which case– leave pending interactions until the subsequent render.
2217 - const pendingInteractionMap = root.pendingInteractionMap;
2218 - pendingInteractionMap.forEach(
2219 - (scheduledInteractions, scheduledExpirationTime) => {
2220 - // Only decrement the pending interaction count if we're done.
2221 - // If there's still work at the current priority,
2222 - // That indicates that we are waiting for suspense data.
2223 - if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
2224 - pendingInteractionMap.delete(scheduledExpirationTime);
2225 -
2226 - scheduledInteractions.forEach(interaction => {
2227 - interaction.__count--;
2228 -
2229 - if (subscriber !== null && interaction.__count === 0) {
2230 - try {
2231 - subscriber.onInteractionScheduledWorkCompleted(interaction);
2232 - } catch (error) {
2233 - // If the subscriber throws, rethrow it in a separate task
2234 - scheduleCallback(ImmediatePriority, () => {
2235 - throw error;
2236 - });
2237 - }
2238 - }
2239 - });
2240 - }
2241 - },
2242 - );
2243 - }
2244 -}
packages/react-reconciler/src/ReactFiberScheduler.old.js deleted
-2723
@@ -1,2723 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {Fiber} from './ReactFiber';
11 -import type {Batch, FiberRoot} from './ReactFiberRoot';
12 -import type {ExpirationTime} from './ReactFiberExpirationTime';
13 -import type {Interaction} from 'scheduler/src/Tracing';
14 -
15 -// Intentionally not named imports because Rollup would use dynamic dispatch for
16 -// CommonJS interop named imports.
17 -import * as Scheduler from 'scheduler';
18 -import {
19 - __interactionsRef,
20 - __subscriberRef,
21 - unstable_wrap as Scheduler_tracing_wrap,
22 -} from 'scheduler/tracing';
23 -import {
24 - invokeGuardedCallback,
25 - hasCaughtError,
26 - clearCaughtError,
27 -} from 'shared/ReactErrorUtils';
28 -import ReactSharedInternals from 'shared/ReactSharedInternals';
29 -import ReactStrictModeWarnings from './ReactStrictModeWarnings';
30 -import {
31 - NoEffect,
32 - PerformedWork,
33 - Placement,
34 - Update,
35 - Snapshot,
36 - PlacementAndUpdate,
37 - Deletion,
38 - ContentReset,
39 - Callback,
40 - DidCapture,
41 - Ref,
42 - Incomplete,
43 - HostEffectMask,
44 - Passive,
45 -} from 'shared/ReactSideEffectTags';
46 -import {
47 - ClassComponent,
48 - HostComponent,
49 - ContextProvider,
50 - ForwardRef,
51 - FunctionComponent,
52 - HostPortal,
53 - HostRoot,
54 - MemoComponent,
55 - SimpleMemoComponent,
56 - SuspenseComponent,
57 - DehydratedSuspenseComponent,
58 -} from 'shared/ReactWorkTags';
59 -import {
60 - enableSchedulerTracing,
61 - enableProfilerTimer,
62 - enableUserTimingAPI,
63 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
64 - warnAboutDeprecatedLifecycles,
65 - enableSuspenseServerRenderer,
66 - disableYielding,
67 -} from 'shared/ReactFeatureFlags';
68 -import getComponentName from 'shared/getComponentName';
69 -import invariant from 'shared/invariant';
70 -import warning from 'shared/warning';
71 -import warningWithoutStack from 'shared/warningWithoutStack';
72 -
73 -import ReactFiberInstrumentation from './ReactFiberInstrumentation';
74 -import {
75 - getStackByFiberInDevAndProd,
76 - phase as ReactCurrentFiberPhase,
77 - resetCurrentFiber,
78 - setCurrentFiber,
79 -} from './ReactCurrentFiber';
80 -import {
81 - prepareForCommit,
82 - resetAfterCommit,
83 - scheduleTimeout,
84 - cancelTimeout,
85 - noTimeout,
86 -} from './ReactFiberHostConfig';
87 -import {
88 - markPendingPriorityLevel,
89 - markCommittedPriorityLevels,
90 - markSuspendedPriorityLevel,
91 - markPingedPriorityLevel,
92 - hasLowerPriorityWork,
93 - isPriorityLevelSuspended,
94 - didExpireAtExpirationTime,
95 -} from './ReactFiberPendingPriority';
96 -import {
97 - recordEffect,
98 - recordScheduleUpdate,
99 - startRequestCallbackTimer,
100 - stopRequestCallbackTimer,
101 - startWorkTimer,
102 - stopWorkTimer,
103 - stopFailedWorkTimer,
104 - startWorkLoopTimer,
105 - stopWorkLoopTimer,
106 - startCommitTimer,
107 - stopCommitTimer,
108 - startCommitSnapshotEffectsTimer,
109 - stopCommitSnapshotEffectsTimer,
110 - startCommitHostEffectsTimer,
111 - stopCommitHostEffectsTimer,
112 - startCommitLifeCyclesTimer,
113 - stopCommitLifeCyclesTimer,
114 -} from './ReactDebugFiberPerf';
115 -import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
116 -import {onCommitRoot} from './ReactFiberDevToolsHook';
117 -import {
118 - NoWork,
119 - Sync,
120 - Never,
121 - msToExpirationTime,
122 - expirationTimeToMs,
123 - computeAsyncExpiration,
124 - computeInteractiveExpiration,
125 - LOW_PRIORITY_EXPIRATION,
126 -} from './ReactFiberExpirationTime';
127 -import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
128 -import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
129 -import {createCapturedValue} from './ReactCapturedValue';
130 -import {
131 - isContextProvider as isLegacyContextProvider,
132 - popTopLevelContextObject as popTopLevelLegacyContextObject,
133 - popContext as popLegacyContext,
134 -} from './ReactFiberContext';
135 -import {popProvider, resetContextDependences} from './ReactFiberNewContext';
136 -import {resetHooks} from './ReactFiberHooks';
137 -import {popHostContext, popHostContainer} from './ReactFiberHostContext';
138 -import {
139 - recordCommitTime,
140 - startProfilerTimer,
141 - stopProfilerTimerIfRunningAndRecordDelta,
142 -} from './ReactProfilerTimer';
143 -import {
144 - checkThatStackIsEmpty,
145 - resetStackAfterFatalErrorInDev,
146 -} from './ReactFiberStack';
147 -import {beginWork} from './ReactFiberBeginWork';
148 -import {completeWork} from './ReactFiberCompleteWork';
149 -import {
150 - throwException,
151 - unwindWork,
152 - unwindInterruptedWork,
153 - createRootErrorUpdate,
154 - createClassErrorUpdate,
155 -} from './ReactFiberUnwindWork';
156 -import {
157 - commitBeforeMutationLifeCycles,
158 - commitResetTextContent,
159 - commitPlacement,
160 - commitDeletion,
161 - commitWork,
162 - commitLifeCycles,
163 - commitAttachRef,
164 - commitDetachRef,
165 - commitPassiveHookEffects,
166 -} from './ReactFiberCommitWork';
167 -import {ContextOnlyDispatcher} from './ReactFiberHooks';
168 -
169 -// Intentionally not named imports because Rollup would use dynamic dispatch for
170 -// CommonJS interop named imports.
171 -const {
172 - unstable_scheduleCallback: scheduleCallback,
173 - unstable_cancelCallback: cancelCallback,
174 - unstable_shouldYield: shouldYield,
175 - unstable_now: now,
176 - unstable_getCurrentPriorityLevel: getCurrentPriorityLevel,
177 - unstable_NormalPriority: NormalPriority,
178 -} = Scheduler;
179 -
180 -export type Thenable = {
181 - then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
182 -};
183 -
184 -const {
185 - ReactCurrentDispatcher,
186 - ReactCurrentOwner,
187 - ReactShouldWarnActingUpdates,
188 -} = ReactSharedInternals;
189 -
190 -let didWarnAboutStateTransition;
191 -let didWarnSetStateChildContext;
192 -let warnAboutUpdateOnUnmounted;
193 -let warnAboutInvalidUpdates;
194 -
195 -if (enableSchedulerTracing) {
196 - // Provide explicit error message when production+profiling bundle of e.g. react-dom
197 - // is used with production (non-profiling) bundle of scheduler/tracing
198 - invariant(
199 - __interactionsRef != null && __interactionsRef.current != null,
200 - 'It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) ' +
201 - 'without also replacing the `scheduler/tracing` module with `scheduler/tracing-profiling`. ' +
202 - 'Your bundler might have a setting for aliasing both modules. ' +
203 - 'Learn more at http://fb.me/react-profiling',
204 - );
205 -}
206 -
207 -if (__DEV__) {
208 - didWarnAboutStateTransition = false;
209 - didWarnSetStateChildContext = false;
210 - const didWarnStateUpdateForUnmountedComponent = {};
211 -
212 - warnAboutUpdateOnUnmounted = function(fiber: Fiber, isClass: boolean) {
213 - // We show the whole stack but dedupe on the top component's name because
214 - // the problematic code almost always lies inside that component.
215 - const componentName = getComponentName(fiber.type) || 'ReactComponent';
216 - if (didWarnStateUpdateForUnmountedComponent[componentName]) {
217 - return;
218 - }
219 - warningWithoutStack(
220 - false,
221 - "Can't perform a React state update on an unmounted component. This " +
222 - 'is a no-op, but it indicates a memory leak in your application. To ' +
223 - 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
224 - isClass
225 - ? 'the componentWillUnmount method'
226 - : 'a useEffect cleanup function',
227 - getStackByFiberInDevAndProd(fiber),
228 - );
229 - didWarnStateUpdateForUnmountedComponent[componentName] = true;
230 - };
231 -
232 - warnAboutInvalidUpdates = function(instance: React$Component<any>) {
233 - switch (ReactCurrentFiberPhase) {
234 - case 'getChildContext':
235 - if (didWarnSetStateChildContext) {
236 - return;
237 - }
238 - warningWithoutStack(
239 - false,
240 - 'setState(...): Cannot call setState() inside getChildContext()',
241 - );
242 - didWarnSetStateChildContext = true;
243 - break;
244 - case 'render':
245 - if (didWarnAboutStateTransition) {
246 - return;
247 - }
248 - warningWithoutStack(
249 - false,
250 - 'Cannot update during an existing state transition (such as within ' +
251 - '`render`). Render methods should be a pure function of props and state.',
252 - );
253 - didWarnAboutStateTransition = true;
254 - break;
255 - }
256 - };
257 -}
258 -
259 -// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
260 -let lastUniqueAsyncExpiration: number = Sync - 1;
261 -
262 -// Represents the expiration time that incoming updates should use. (If this
263 -// is NoWork, use the default strategy: async updates in async mode, sync
264 -// updates in sync mode.)
265 -let expirationContext: ExpirationTime = NoWork;
266 -
267 -let isWorking: boolean = false;
268 -
269 -// The next work in progress fiber that we're currently working on.
270 -let nextUnitOfWork: Fiber | null = null;
271 -let nextRoot: FiberRoot | null = null;
272 -// The time at which we're currently rendering work.
273 -let nextRenderExpirationTime: ExpirationTime = NoWork;
274 -let mostRecentEventTime: ExpirationTime = Sync;
275 -let nextRenderDidSuspend: boolean = false;
276 -let nextRenderDidError: boolean = false;
277 -
278 -// The next fiber with an effect that we're currently committing.
279 -let nextEffect: Fiber | null = null;
280 -
281 -let isCommitting: boolean = false;
282 -let rootWithPendingPassiveEffects: FiberRoot | null = null;
283 -let passiveEffectCallbackHandle: * = null;
284 -let passiveEffectCallback: * = null;
285 -
286 -let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
287 -
288 -// Used for performance tracking.
289 -let interruptedBy: Fiber | null = null;
290 -
291 -let stashedWorkInProgressProperties;
292 -let replayUnitOfWork;
293 -let mayReplayFailedUnitOfWork;
294 -let isReplayingFailedUnitOfWork;
295 -let originalReplayError;
296 -let rethrowOriginalError;
297 -if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
298 - stashedWorkInProgressProperties = null;
299 - mayReplayFailedUnitOfWork = true;
300 - isReplayingFailedUnitOfWork = false;
301 - originalReplayError = null;
302 - replayUnitOfWork = (
303 - failedUnitOfWork: Fiber,
304 - thrownValue: mixed,
305 - isYieldy: boolean,
306 - ) => {
307 - if (
308 - thrownValue !== null &&
309 - typeof thrownValue === 'object' &&
310 - typeof thrownValue.then === 'function'
311 - ) {
312 - // Don't replay promises. Treat everything else like an error.
313 - // TODO: Need to figure out a different strategy if/when we add
314 - // support for catching other types.
315 - return;
316 - }
317 -
318 - // Restore the original state of the work-in-progress
319 - if (stashedWorkInProgressProperties === null) {
320 - // This should never happen. Don't throw because this code is DEV-only.
321 - warningWithoutStack(
322 - false,
323 - 'Could not replay rendering after an error. This is likely a bug in React. ' +
324 - 'Please file an issue.',
325 - );
326 - return;
327 - }
328 - assignFiberPropertiesInDEV(
329 - failedUnitOfWork,
330 - stashedWorkInProgressProperties,
331 - );
332 -
333 - switch (failedUnitOfWork.tag) {
334 - case HostRoot:
335 - popHostContainer(failedUnitOfWork);
336 - popTopLevelLegacyContextObject(failedUnitOfWork);
337 - break;
338 - case HostComponent:
339 - popHostContext(failedUnitOfWork);
340 - break;
341 - case ClassComponent: {
342 - const Component = failedUnitOfWork.type;
343 - if (isLegacyContextProvider(Component)) {
344 - popLegacyContext(failedUnitOfWork);
345 - }
346 - break;
347 - }
348 - case HostPortal:
349 - popHostContainer(failedUnitOfWork);
350 - break;
351 - case ContextProvider:
352 - popProvider(failedUnitOfWork);
353 - break;
354 - }
355 - // Replay the begin phase.
356 - isReplayingFailedUnitOfWork = true;
357 - originalReplayError = thrownValue;
358 - invokeGuardedCallback(null, workLoop, null, isYieldy);
359 - isReplayingFailedUnitOfWork = false;
360 - originalReplayError = null;
361 - if (hasCaughtError()) {
362 - const replayError = clearCaughtError();
363 - if (replayError != null && thrownValue != null) {
364 - try {
365 - // Reading the expando property is intentionally
366 - // inside `try` because it might be a getter or Proxy.
367 - if (replayError._suppressLogging) {
368 - // Also suppress logging for the original error.
369 - (thrownValue: any)._suppressLogging = true;
370 - }
371 - } catch (inner) {
372 - // Ignore.
373 - }
374 - }
375 - } else {
376 - // If the begin phase did not fail the second time, set this pointer
377 - // back to the original value.
378 - nextUnitOfWork = failedUnitOfWork;
379 - }
380 - };
381 - rethrowOriginalError = () => {
382 - throw originalReplayError;
383 - };
384 -}
385 -
386 -function resetStack() {
387 - if (nextUnitOfWork !== null) {
388 - let interruptedWork = nextUnitOfWork.return;
389 - while (interruptedWork !== null) {
390 - unwindInterruptedWork(interruptedWork);
391 - interruptedWork = interruptedWork.return;
392 - }
393 - }
394 -
395 - if (__DEV__) {
396 - ReactStrictModeWarnings.discardPendingWarnings();
397 - checkThatStackIsEmpty();
398 - }
399 -
400 - nextRoot = null;
401 - nextRenderExpirationTime = NoWork;
402 - mostRecentEventTime = Sync;
403 - nextRenderDidSuspend = false;
404 - nextRenderDidError = false;
405 - nextUnitOfWork = null;
406 -}
407 -
408 -function commitAllHostEffects() {
409 - while (nextEffect !== null) {
410 - if (__DEV__) {
411 - setCurrentFiber(nextEffect);
412 - }
413 - recordEffect();
414 -
415 - const effectTag = nextEffect.effectTag;
416 -
417 - if (effectTag & ContentReset) {
418 - commitResetTextContent(nextEffect);
419 - }
420 -
421 - if (effectTag & Ref) {
422 - const current = nextEffect.alternate;
423 - if (current !== null) {
424 - commitDetachRef(current);
425 - }
426 - }
427 -
428 - // The following switch statement is only concerned about placement,
429 - // updates, and deletions. To avoid needing to add a case for every
430 - // possible bitmap value, we remove the secondary effects from the
431 - // effect tag and switch on that value.
432 - let primaryEffectTag = effectTag & (Placement | Update | Deletion);
433 - switch (primaryEffectTag) {
434 - case Placement: {
435 - commitPlacement(nextEffect);
436 - // Clear the "placement" from effect tag so that we know that this is inserted, before
437 - // any life-cycles like componentDidMount gets called.
438 - // TODO: findDOMNode doesn't rely on this any more but isMounted
439 - // does and isMounted is deprecated anyway so we should be able
440 - // to kill this.
441 - nextEffect.effectTag &= ~Placement;
442 - break;
443 - }
444 - case PlacementAndUpdate: {
445 - // Placement
446 - commitPlacement(nextEffect);
447 - // Clear the "placement" from effect tag so that we know that this is inserted, before
448 - // any life-cycles like componentDidMount gets called.
449 - nextEffect.effectTag &= ~Placement;
450 -
451 - // Update
452 - const current = nextEffect.alternate;
453 - commitWork(current, nextEffect);
454 - break;
455 - }
456 - case Update: {
457 - const current = nextEffect.alternate;
458 - commitWork(current, nextEffect);
459 - break;
460 - }
461 - case Deletion: {
462 - commitDeletion(nextEffect);
463 - break;
464 - }
465 - }
466 - nextEffect = nextEffect.nextEffect;
467 - }
468 -
469 - if (__DEV__) {
470 - resetCurrentFiber();
471 - }
472 -}
473 -
474 -function commitBeforeMutationLifecycles() {
475 - while (nextEffect !== null) {
476 - if (__DEV__) {
477 - setCurrentFiber(nextEffect);
478 - }
479 -
480 - const effectTag = nextEffect.effectTag;
481 - if (effectTag & Snapshot) {
482 - recordEffect();
483 - const current = nextEffect.alternate;
484 - commitBeforeMutationLifeCycles(current, nextEffect);
485 - }
486 -
487 - nextEffect = nextEffect.nextEffect;
488 - }
489 -
490 - if (__DEV__) {
491 - resetCurrentFiber();
492 - }
493 -}
494 -
495 -function commitAllLifeCycles(
496 - finishedRoot: FiberRoot,
497 - committedExpirationTime: ExpirationTime,
498 -) {
499 - if (__DEV__) {
500 - ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
501 - ReactStrictModeWarnings.flushLegacyContextWarning();
502 -
503 - if (warnAboutDeprecatedLifecycles) {
504 - ReactStrictModeWarnings.flushPendingDeprecationWarnings();
505 - }
506 - }
507 - while (nextEffect !== null) {
508 - if (__DEV__) {
509 - setCurrentFiber(nextEffect);
510 - }
511 - const effectTag = nextEffect.effectTag;
512 -
513 - if (effectTag & (Update | Callback)) {
514 - recordEffect();
515 - const current = nextEffect.alternate;
516 - commitLifeCycles(
517 - finishedRoot,
518 - current,
519 - nextEffect,
520 - committedExpirationTime,
521 - );
522 - }
523 -
524 - if (effectTag & Ref) {
525 - recordEffect();
526 - commitAttachRef(nextEffect);
527 - }
528 -
529 - if (effectTag & Passive) {
530 - rootWithPendingPassiveEffects = finishedRoot;
531 - }
532 -
533 - nextEffect = nextEffect.nextEffect;
534 - }
535 - if (__DEV__) {
536 - resetCurrentFiber();
537 - }
538 -}
539 -
540 -function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
541 - rootWithPendingPassiveEffects = null;
542 - passiveEffectCallbackHandle = null;
543 - passiveEffectCallback = null;
544 -
545 - // Set this to true to prevent re-entrancy
546 - const previousIsRendering = isRendering;
547 - isRendering = true;
548 -
549 - let effect = firstEffect;
550 - do {
551 - if (__DEV__) {
552 - setCurrentFiber(effect);
553 - }
554 -
555 - if (effect.effectTag & Passive) {
556 - let didError = false;
557 - let error;
558 - if (__DEV__) {
559 - isInPassiveEffectDEV = true;
560 - invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
561 - isInPassiveEffectDEV = false;
562 - if (hasCaughtError()) {
563 - didError = true;
564 - error = clearCaughtError();
565 - }
566 - } else {
567 - try {
568 - commitPassiveHookEffects(effect);
569 - } catch (e) {
570 - didError = true;
571 - error = e;
572 - }
573 - }
574 - if (didError) {
575 - captureCommitPhaseError(effect, error);
576 - }
577 - }
578 - effect = effect.nextEffect;
579 - } while (effect !== null);
580 - if (__DEV__) {
581 - resetCurrentFiber();
582 - }
583 -
584 - isRendering = previousIsRendering;
585 -
586 - // Check if work was scheduled by one of the effects
587 - const rootExpirationTime = root.expirationTime;
588 - if (rootExpirationTime !== NoWork) {
589 - requestWork(root, rootExpirationTime);
590 - }
591 - // Flush any sync work that was scheduled by effects
592 - if (!isBatchingUpdates && !isRendering) {
593 - performSyncWork();
594 - }
595 -
596 - if (__DEV__) {
597 - if (rootWithPendingPassiveEffects === root) {
598 - nestedPassiveEffectCountDEV++;
599 - } else {
600 - nestedPassiveEffectCountDEV = 0;
601 - }
602 - }
603 -}
604 -
605 -function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
606 - return (
607 - legacyErrorBoundariesThatAlreadyFailed !== null &&
608 - legacyErrorBoundariesThatAlreadyFailed.has(instance)
609 - );
610 -}
611 -
612 -function markLegacyErrorBoundaryAsFailed(instance: mixed) {
613 - if (legacyErrorBoundariesThatAlreadyFailed === null) {
614 - legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
615 - } else {
616 - legacyErrorBoundariesThatAlreadyFailed.add(instance);
617 - }
618 -}
619 -
620 -function flushPassiveEffects() {
621 - const didFlushEffects = passiveEffectCallback !== null;
622 - if (passiveEffectCallbackHandle !== null) {
623 - cancelCallback(passiveEffectCallbackHandle);
624 - }
625 - if (passiveEffectCallback !== null) {
626 - // We call the scheduled callback instead of commitPassiveEffects directly
627 - // to ensure tracing works correctly.
628 - passiveEffectCallback();
629 - }
630 - return didFlushEffects;
631 -}
632 -
633 -function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
634 - isWorking = true;
635 - isCommitting = true;
636 - startCommitTimer();
637 -
638 - invariant(
639 - root.current !== finishedWork,
640 - 'Cannot commit the same tree as before. This is probably a bug ' +
641 - 'related to the return field. This error is likely caused by a bug ' +
642 - 'in React. Please file an issue.',
643 - );
644 - const committedExpirationTime = root.pendingCommitExpirationTime;
645 - invariant(
646 - committedExpirationTime !== NoWork,
647 - 'Cannot commit an incomplete root. This error is likely caused by a ' +
648 - 'bug in React. Please file an issue.',
649 - );
650 - root.pendingCommitExpirationTime = NoWork;
651 -
652 - // Update the pending priority levels to account for the work that we are
653 - // about to commit. This needs to happen before calling the lifecycles, since
654 - // they may schedule additional updates.
655 - const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
656 - const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
657 - const earliestRemainingTimeBeforeCommit =
658 - childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
659 - ? childExpirationTimeBeforeCommit
660 - : updateExpirationTimeBeforeCommit;
661 - markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
662 -
663 - let prevInteractions: Set<Interaction> = (null: any);
664 - if (enableSchedulerTracing) {
665 - // Restore any pending interactions at this point,
666 - // So that cascading work triggered during the render phase will be accounted for.
667 - prevInteractions = __interactionsRef.current;
668 - __interactionsRef.current = root.memoizedInteractions;
669 - }
670 -
671 - // Reset this to null before calling lifecycles
672 - ReactCurrentOwner.current = null;
673 -
674 - let firstEffect;
675 - if (finishedWork.effectTag > PerformedWork) {
676 - // A fiber's effect list consists only of its children, not itself. So if
677 - // the root has an effect, we need to add it to the end of the list. The
678 - // resulting list is the set that would belong to the root's parent, if
679 - // it had one; that is, all the effects in the tree including the root.
680 - if (finishedWork.lastEffect !== null) {
681 - finishedWork.lastEffect.nextEffect = finishedWork;
682 - firstEffect = finishedWork.firstEffect;
683 - } else {
684 - firstEffect = finishedWork;
685 - }
686 - } else {
687 - // There is no effect on the root.
688 - firstEffect = finishedWork.firstEffect;
689 - }
690 -
691 - prepareForCommit(root.containerInfo);
692 -
693 - // Invoke instances of getSnapshotBeforeUpdate before mutation.
694 - nextEffect = firstEffect;
695 - startCommitSnapshotEffectsTimer();
696 - while (nextEffect !== null) {
697 - let didError = false;
698 - let error;
699 - if (__DEV__) {
700 - invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
701 - if (hasCaughtError()) {
702 - didError = true;
703 - error = clearCaughtError();
704 - }
705 - } else {
706 - try {
707 - commitBeforeMutationLifecycles();
708 - } catch (e) {
709 - didError = true;
710 - error = e;
711 - }
712 - }
713 - if (didError) {
714 - invariant(
715 - nextEffect !== null,
716 - 'Should have next effect. This error is likely caused by a bug ' +
717 - 'in React. Please file an issue.',
718 - );
719 - captureCommitPhaseError(nextEffect, error);
720 - // Clean-up
721 - if (nextEffect !== null) {
722 - nextEffect = nextEffect.nextEffect;
723 - }
724 - }
725 - }
726 - stopCommitSnapshotEffectsTimer();
727 -
728 - if (enableProfilerTimer) {
729 - // Mark the current commit time to be shared by all Profilers in this batch.
730 - // This enables them to be grouped later.
731 - recordCommitTime();
732 - }
733 -
734 - // Commit all the side-effects within a tree. We'll do this in two passes.
735 - // The first pass performs all the host insertions, updates, deletions and
736 - // ref unmounts.
737 - nextEffect = firstEffect;
738 - startCommitHostEffectsTimer();
739 - while (nextEffect !== null) {
740 - let didError = false;
741 - let error;
742 - if (__DEV__) {
743 - invokeGuardedCallback(null, commitAllHostEffects, null);
744 - if (hasCaughtError()) {
745 - didError = true;
746 - error = clearCaughtError();
747 - }
748 - } else {
749 - try {
750 - commitAllHostEffects();
751 - } catch (e) {
752 - didError = true;
753 - error = e;
754 - }
755 - }
756 - if (didError) {
757 - invariant(
758 - nextEffect !== null,
759 - 'Should have next effect. This error is likely caused by a bug ' +
760 - 'in React. Please file an issue.',
761 - );
762 - captureCommitPhaseError(nextEffect, error);
763 - // Clean-up
764 - if (nextEffect !== null) {
765 - nextEffect = nextEffect.nextEffect;
766 - }
767 - }
768 - }
769 - stopCommitHostEffectsTimer();
770 -
771 - resetAfterCommit(root.containerInfo);
772 -
773 - // The work-in-progress tree is now the current tree. This must come after
774 - // the first pass of the commit phase, so that the previous tree is still
775 - // current during componentWillUnmount, but before the second pass, so that
776 - // the finished work is current during componentDidMount/Update.
777 - root.current = finishedWork;
778 -
779 - // In the second pass we'll perform all life-cycles and ref callbacks.
780 - // Life-cycles happen as a separate pass so that all placements, updates,
781 - // and deletions in the entire tree have already been invoked.
782 - // This pass also triggers any renderer-specific initial effects.
783 - nextEffect = firstEffect;
784 - startCommitLifeCyclesTimer();
785 - while (nextEffect !== null) {
786 - let didError = false;
787 - let error;
788 - if (__DEV__) {
789 - invokeGuardedCallback(
790 - null,
791 - commitAllLifeCycles,
792 - null,
793 - root,
794 - committedExpirationTime,
795 - );
796 - if (hasCaughtError()) {
797 - didError = true;
798 - error = clearCaughtError();
799 - }
800 - } else {
801 - try {
802 - commitAllLifeCycles(root, committedExpirationTime);
803 - } catch (e) {
804 - didError = true;
805 - error = e;
806 - }
807 - }
808 - if (didError) {
809 - invariant(
810 - nextEffect !== null,
811 - 'Should have next effect. This error is likely caused by a bug ' +
812 - 'in React. Please file an issue.',
813 - );
814 - captureCommitPhaseError(nextEffect, error);
815 - if (nextEffect !== null) {
816 - nextEffect = nextEffect.nextEffect;
817 - }
818 - }
819 - }
820 -
821 - if (firstEffect !== null && rootWithPendingPassiveEffects !== null) {
822 - // This commit included a passive effect. These do not need to fire until
823 - // after the next paint. Schedule an callback to fire them in an async
824 - // event. To ensure serial execution, the callback will be flushed early if
825 - // we enter rootWithPendingPassiveEffects commit phase before then.
826 - let callback = commitPassiveEffects.bind(null, root, firstEffect);
827 - if (enableSchedulerTracing) {
828 - // TODO: Avoid this extra callback by mutating the tracing ref directly,
829 - // like we do at the beginning of commitRoot. I've opted not to do that
830 - // here because that code is still in flux.
831 - callback = Scheduler_tracing_wrap(callback);
832 - }
833 - passiveEffectCallbackHandle = scheduleCallback(NormalPriority, callback);
834 - passiveEffectCallback = callback;
835 - }
836 -
837 - isCommitting = false;
838 - isWorking = false;
839 - stopCommitLifeCyclesTimer();
840 - stopCommitTimer();
841 - onCommitRoot(finishedWork.stateNode);
842 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
843 - ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
844 - }
845 -
846 - const updateExpirationTimeAfterCommit = finishedWork.expirationTime;
847 - const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
848 - const earliestRemainingTimeAfterCommit =
849 - childExpirationTimeAfterCommit > updateExpirationTimeAfterCommit
850 - ? childExpirationTimeAfterCommit
851 - : updateExpirationTimeAfterCommit;
852 - if (earliestRemainingTimeAfterCommit === NoWork) {
853 - // If there's no remaining work, we can clear the set of already failed
854 - // error boundaries.
855 - legacyErrorBoundariesThatAlreadyFailed = null;
856 - }
857 - onCommit(root, earliestRemainingTimeAfterCommit);
858 -
859 - if (enableSchedulerTracing) {
860 - __interactionsRef.current = prevInteractions;
861 -
862 - let subscriber;
863 -
864 - try {
865 - subscriber = __subscriberRef.current;
866 - if (subscriber !== null && root.memoizedInteractions.size > 0) {
867 - const threadID = computeThreadID(
868 - committedExpirationTime,
869 - root.interactionThreadID,
870 - );
871 - subscriber.onWorkStopped(root.memoizedInteractions, threadID);
872 - }
873 - } catch (error) {
874 - // It's not safe for commitRoot() to throw.
875 - // Store the error for now and we'll re-throw in finishRendering().
876 - if (!hasUnhandledError) {
877 - hasUnhandledError = true;
878 - unhandledError = error;
879 - }
880 - } finally {
881 - // Clear completed interactions from the pending Map.
882 - // Unless the render was suspended or cascading work was scheduled,
883 - // In which case– leave pending interactions until the subsequent render.
884 - const pendingInteractionMap = root.pendingInteractionMap;
885 - pendingInteractionMap.forEach(
886 - (scheduledInteractions, scheduledExpirationTime) => {
887 - // Only decrement the pending interaction count if we're done.
888 - // If there's still work at the current priority,
889 - // That indicates that we are waiting for suspense data.
890 - if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
891 - pendingInteractionMap.delete(scheduledExpirationTime);
892 -
893 - scheduledInteractions.forEach(interaction => {
894 - interaction.__count--;
895 -
896 - if (subscriber !== null && interaction.__count === 0) {
897 - try {
898 - subscriber.onInteractionScheduledWorkCompleted(interaction);
899 - } catch (error) {
900 - // It's not safe for commitRoot() to throw.
901 - // Store the error for now and we'll re-throw in finishRendering().
902 - if (!hasUnhandledError) {
903 - hasUnhandledError = true;
904 - unhandledError = error;
905 - }
906 - }
907 - }
908 - });
909 - }
910 - },
911 - );
912 - }
913 - }
914 -}
915 -
916 -function resetChildExpirationTime(
917 - workInProgress: Fiber,
918 - renderTime: ExpirationTime,
919 -) {
920 - if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
921 - // The children of this component are hidden. Don't bubble their
922 - // expiration times.
923 - return;
924 - }
925 -
926 - let newChildExpirationTime = NoWork;
927 -
928 - // Bubble up the earliest expiration time.
929 - if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
930 - // We're in profiling mode.
931 - // Let's use this same traversal to update the render durations.
932 - let actualDuration = workInProgress.actualDuration;
933 - let treeBaseDuration = workInProgress.selfBaseDuration;
934 -
935 - // When a fiber is cloned, its actualDuration is reset to 0.
936 - // This value will only be updated if work is done on the fiber (i.e. it doesn't bailout).
937 - // When work is done, it should bubble to the parent's actualDuration.
938 - // If the fiber has not been cloned though, (meaning no work was done),
939 - // Then this value will reflect the amount of time spent working on a previous render.
940 - // In that case it should not bubble.
941 - // We determine whether it was cloned by comparing the child pointer.
942 - const shouldBubbleActualDurations =
943 - workInProgress.alternate === null ||
944 - workInProgress.child !== workInProgress.alternate.child;
945 -
946 - let child = workInProgress.child;
947 - while (child !== null) {
948 - const childUpdateExpirationTime = child.expirationTime;
949 - const childChildExpirationTime = child.childExpirationTime;
950 - if (childUpdateExpirationTime > newChildExpirationTime) {
951 - newChildExpirationTime = childUpdateExpirationTime;
952 - }
953 - if (childChildExpirationTime > newChildExpirationTime) {
954 - newChildExpirationTime = childChildExpirationTime;
955 - }
956 - if (shouldBubbleActualDurations) {
957 - actualDuration += child.actualDuration;
958 - }
959 - treeBaseDuration += child.treeBaseDuration;
960 - child = child.sibling;
961 - }
962 - workInProgress.actualDuration = actualDuration;
963 - workInProgress.treeBaseDuration = treeBaseDuration;
964 - } else {
965 - let child = workInProgress.child;
966 - while (child !== null) {
967 - const childUpdateExpirationTime = child.expirationTime;
968 - const childChildExpirationTime = child.childExpirationTime;
969 - if (childUpdateExpirationTime > newChildExpirationTime) {
970 - newChildExpirationTime = childUpdateExpirationTime;
971 - }
972 - if (childChildExpirationTime > newChildExpirationTime) {
973 - newChildExpirationTime = childChildExpirationTime;
974 - }
975 - child = child.sibling;
976 - }
977 - }
978 -
979 - workInProgress.childExpirationTime = newChildExpirationTime;
980 -}
981 -
982 -function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
983 - // Attempt to complete the current unit of work, then move to the
984 - // next sibling. If there are no more siblings, return to the
985 - // parent fiber.
986 - while (true) {
987 - // The current, flushed, state of this fiber is the alternate.
988 - // Ideally nothing should rely on this, but relying on it here
989 - // means that we don't need an additional field on the work in
990 - // progress.
991 - const current = workInProgress.alternate;
992 - if (__DEV__) {
993 - setCurrentFiber(workInProgress);
994 - }
995 -
996 - const returnFiber = workInProgress.return;
997 - const siblingFiber = workInProgress.sibling;
998 -
999 - if ((workInProgress.effectTag & Incomplete) === NoEffect) {
1000 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1001 - // Don't replay if it fails during completion phase.
1002 - mayReplayFailedUnitOfWork = false;
1003 - }
1004 - // This fiber completed.
1005 - // Remember we're completing this unit so we can find a boundary if it fails.
1006 - nextUnitOfWork = workInProgress;
1007 - if (enableProfilerTimer) {
1008 - if (workInProgress.mode & ProfileMode) {
1009 - startProfilerTimer(workInProgress);
1010 - }
1011 - nextUnitOfWork = completeWork(
1012 - current,
1013 - workInProgress,
1014 - nextRenderExpirationTime,
1015 - );
1016 - if (workInProgress.mode & ProfileMode) {
1017 - // Update render duration assuming we didn't error.
1018 - stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1019 - }
1020 - } else {
1021 - nextUnitOfWork = completeWork(
1022 - current,
1023 - workInProgress,
1024 - nextRenderExpirationTime,
1025 - );
1026 - }
1027 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1028 - // We're out of completion phase so replaying is fine now.
1029 - mayReplayFailedUnitOfWork = true;
1030 - }
1031 - stopWorkTimer(workInProgress);
1032 - resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
1033 - if (__DEV__) {
1034 - resetCurrentFiber();
1035 - }
1036 -
1037 - if (nextUnitOfWork !== null) {
1038 - // Completing this fiber spawned new work. Work on that next.
1039 - return nextUnitOfWork;
1040 - }
1041 -
1042 - if (
1043 - returnFiber !== null &&
1044 - // Do not append effects to parents if a sibling failed to complete
1045 - (returnFiber.effectTag & Incomplete) === NoEffect
1046 - ) {
1047 - // Append all the effects of the subtree and this fiber onto the effect
1048 - // list of the parent. The completion order of the children affects the
1049 - // side-effect order.
1050 - if (returnFiber.firstEffect === null) {
1051 - returnFiber.firstEffect = workInProgress.firstEffect;
1052 - }
1053 - if (workInProgress.lastEffect !== null) {
1054 - if (returnFiber.lastEffect !== null) {
1055 - returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1056 - }
1057 - returnFiber.lastEffect = workInProgress.lastEffect;
1058 - }
1059 -
1060 - // If this fiber had side-effects, we append it AFTER the children's
1061 - // side-effects. We can perform certain side-effects earlier if
1062 - // needed, by doing multiple passes over the effect list. We don't want
1063 - // to schedule our own side-effect on our own list because if end up
1064 - // reusing children we'll schedule this effect onto itself since we're
1065 - // at the end.
1066 - const effectTag = workInProgress.effectTag;
1067 - // Skip both NoWork and PerformedWork tags when creating the effect list.
1068 - // PerformedWork effect is read by React DevTools but shouldn't be committed.
1069 - if (effectTag > PerformedWork) {
1070 - if (returnFiber.lastEffect !== null) {
1071 - returnFiber.lastEffect.nextEffect = workInProgress;
1072 - } else {
1073 - returnFiber.firstEffect = workInProgress;
1074 - }
1075 - returnFiber.lastEffect = workInProgress;
1076 - }
1077 - }
1078 -
1079 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
1080 - ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
1081 - }
1082 -
1083 - if (siblingFiber !== null) {
1084 - // If there is more work to do in this returnFiber, do that next.
1085 - return siblingFiber;
1086 - } else if (returnFiber !== null) {
1087 - // If there's no more work in this returnFiber. Complete the returnFiber.
1088 - workInProgress = returnFiber;
1089 - continue;
1090 - } else {
1091 - // We've reached the root.
1092 - return null;
1093 - }
1094 - } else {
1095 - if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
1096 - // Record the render duration for the fiber that errored.
1097 - stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1098 -
1099 - // Include the time spent working on failed children before continuing.
1100 - let actualDuration = workInProgress.actualDuration;
1101 - let child = workInProgress.child;
1102 - while (child !== null) {
1103 - actualDuration += child.actualDuration;
1104 - child = child.sibling;
1105 - }
1106 - workInProgress.actualDuration = actualDuration;
1107 - }
1108 -
1109 - // This fiber did not complete because something threw. Pop values off
1110 - // the stack without entering the complete phase. If this is a boundary,
1111 - // capture values if possible.
1112 - const next = unwindWork(workInProgress, nextRenderExpirationTime);
1113 - // Because this fiber did not complete, don't reset its expiration time.
1114 - if (workInProgress.effectTag & DidCapture) {
1115 - // Restarting an error boundary
1116 - stopFailedWorkTimer(workInProgress);
1117 - } else {
1118 - stopWorkTimer(workInProgress);
1119 - }
1120 -
1121 - if (__DEV__) {
1122 - resetCurrentFiber();
1123 - }
1124 -
1125 - if (next !== null) {
1126 - stopWorkTimer(workInProgress);
1127 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
1128 - ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
1129 - }
1130 -
1131 - // If completing this work spawned new work, do that next. We'll come
1132 - // back here again.
1133 - // Since we're restarting, remove anything that is not a host effect
1134 - // from the effect tag.
1135 - next.effectTag &= HostEffectMask;
1136 - return next;
1137 - }
1138 -
1139 - if (returnFiber !== null) {
1140 - // Mark the parent fiber as incomplete and clear its effect list.
1141 - returnFiber.firstEffect = returnFiber.lastEffect = null;
1142 - returnFiber.effectTag |= Incomplete;
1143 - }
1144 -
1145 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
1146 - ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
1147 - }
1148 -
1149 - if (siblingFiber !== null) {
1150 - // If there is more work to do in this returnFiber, do that next.
1151 - return siblingFiber;
1152 - } else if (returnFiber !== null) {
1153 - // If there's no more work in this returnFiber. Complete the returnFiber.
1154 - workInProgress = returnFiber;
1155 - continue;
1156 - } else {
1157 - return null;
1158 - }
1159 - }
1160 - }
1161 -
1162 - // Without this explicit null return Flow complains of invalid return type
1163 - // TODO Remove the above while(true) loop
1164 - // eslint-disable-next-line no-unreachable
1165 - return null;
1166 -}
1167 -
1168 -function performUnitOfWork(workInProgress: Fiber): Fiber | null {
1169 - // The current, flushed, state of this fiber is the alternate.
1170 - // Ideally nothing should rely on this, but relying on it here
1171 - // means that we don't need an additional field on the work in
1172 - // progress.
1173 - const current = workInProgress.alternate;
1174 -
1175 - // See if beginning this work spawns more work.
1176 - startWorkTimer(workInProgress);
1177 - if (__DEV__) {
1178 - setCurrentFiber(workInProgress);
1179 - }
1180 -
1181 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1182 - stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
1183 - stashedWorkInProgressProperties,
1184 - workInProgress,
1185 - );
1186 - }
1187 -
1188 - let next;
1189 - if (enableProfilerTimer) {
1190 - if (workInProgress.mode & ProfileMode) {
1191 - startProfilerTimer(workInProgress);
1192 - }
1193 -
1194 - next = beginWork(current, workInProgress, nextRenderExpirationTime);
1195 - workInProgress.memoizedProps = workInProgress.pendingProps;
1196 -
1197 - if (workInProgress.mode & ProfileMode) {
1198 - // Record the render duration assuming we didn't bailout (or error).
1199 - stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
1200 - }
1201 - } else {
1202 - next = beginWork(current, workInProgress, nextRenderExpirationTime);
1203 - workInProgress.memoizedProps = workInProgress.pendingProps;
1204 - }
1205 -
1206 - if (__DEV__) {
1207 - resetCurrentFiber();
1208 - if (isReplayingFailedUnitOfWork) {
1209 - // Currently replaying a failed unit of work. This should be unreachable,
1210 - // because the render phase is meant to be idempotent, and it should
1211 - // have thrown again. Since it didn't, rethrow the original error, so
1212 - // React's internal stack is not misaligned.
1213 - rethrowOriginalError();
1214 - }
1215 - }
1216 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
1217 - ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
1218 - }
1219 -
1220 - if (next === null) {
1221 - // If this doesn't spawn new work, complete the current work.
1222 - next = completeUnitOfWork(workInProgress);
1223 - }
1224 -
1225 - ReactCurrentOwner.current = null;
1226 -
1227 - return next;
1228 -}
1229 -
1230 -function workLoop(isYieldy) {
1231 - if (!isYieldy) {
1232 - // Flush work without yielding
1233 - while (nextUnitOfWork !== null) {
1234 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1235 - }
1236 - } else {
1237 - // Flush asynchronous work until there's a higher priority event
1238 - while (nextUnitOfWork !== null && !shouldYield()) {
1239 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1240 - }
1241 - }
1242 -}
1243 -
1244 -function jnd(timeElapsed: number) {
1245 - return timeElapsed < 120
1246 - ? 120
1247 - : timeElapsed < 480
1248 - ? 480
1249 - : timeElapsed < 1080
1250 - ? 1080
1251 - : timeElapsed < 1920
1252 - ? 1920
1253 - : timeElapsed < 3000
1254 - ? 3000
1255 - : timeElapsed < 4320
1256 - ? 4320
1257 - : Math.ceil(timeElapsed / 1960) * 1960;
1258 -}
1259 -
1260 -function renderRoot(root: FiberRoot, isYieldy: boolean): void {
1261 - invariant(
1262 - !isWorking,
1263 - 'renderRoot was called recursively. This error is likely caused ' +
1264 - 'by a bug in React. Please file an issue.',
1265 - );
1266 -
1267 - flushPassiveEffects();
1268 -
1269 - isWorking = true;
1270 - const previousDispatcher = ReactCurrentDispatcher.current;
1271 - ReactCurrentDispatcher.current = ContextOnlyDispatcher;
1272 -
1273 - const expirationTime = root.nextExpirationTimeToWorkOn;
1274 -
1275 - // Check if we're starting from a fresh stack, or if we're resuming from
1276 - // previously yielded work.
1277 - if (
1278 - expirationTime !== nextRenderExpirationTime ||
1279 - root !== nextRoot ||
1280 - nextUnitOfWork === null
1281 - ) {
1282 - // Reset the stack and start working from the root.
1283 - resetStack();
1284 - nextRoot = root;
1285 - nextRenderExpirationTime = expirationTime;
1286 - nextUnitOfWork = createWorkInProgress(
1287 - nextRoot.current,
1288 - null,
1289 - nextRenderExpirationTime,
1290 - );
1291 - root.pendingCommitExpirationTime = NoWork;
1292 -
1293 - if (enableSchedulerTracing) {
1294 - // Determine which interactions this batch of work currently includes,
1295 - // So that we can accurately attribute time spent working on it,
1296 - // And so that cascading work triggered during the render phase will be associated with it.
1297 - const interactions: Set<Interaction> = new Set();
1298 - root.pendingInteractionMap.forEach(
1299 - (scheduledInteractions, scheduledExpirationTime) => {
1300 - if (scheduledExpirationTime >= expirationTime) {
1301 - scheduledInteractions.forEach(interaction =>
1302 - interactions.add(interaction),
1303 - );
1304 - }
1305 - },
1306 - );
1307 -
1308 - // Store the current set of interactions on the FiberRoot for a few reasons:
1309 - // We can re-use it in hot functions like renderRoot() without having to recalculate it.
1310 - // We will also use it in commitWork() to pass to any Profiler onRender() hooks.
1311 - // This also provides DevTools with a way to access it when the onCommitRoot() hook is called.
1312 - root.memoizedInteractions = interactions;
1313 -
1314 - if (interactions.size > 0) {
1315 - const subscriber = __subscriberRef.current;
1316 - if (subscriber !== null) {
1317 - const threadID = computeThreadID(
1318 - expirationTime,
1319 - root.interactionThreadID,
1320 - );
1321 - try {
1322 - subscriber.onWorkStarted(interactions, threadID);
1323 - } catch (error) {
1324 - // Work thrown by an interaction tracing subscriber should be rethrown,
1325 - // But only once it's safe (to avoid leaving the scheduler in an invalid state).
1326 - // Store the error for now and we'll re-throw in finishRendering().
1327 - if (!hasUnhandledError) {
1328 - hasUnhandledError = true;
1329 - unhandledError = error;
1330 - }
1331 - }
1332 - }
1333 - }
1334 - }
1335 - }
1336 -
1337 - let prevInteractions: Set<Interaction> = (null: any);
1338 - if (enableSchedulerTracing) {
1339 - // We're about to start new traced work.
1340 - // Restore pending interactions so cascading work triggered during the render phase will be accounted for.
1341 - prevInteractions = __interactionsRef.current;
1342 - __interactionsRef.current = root.memoizedInteractions;
1343 - }
1344 -
1345 - let didFatal = false;
1346 -
1347 - startWorkLoopTimer(nextUnitOfWork);
1348 -
1349 - do {
1350 - try {
1351 - workLoop(isYieldy);
1352 - } catch (thrownValue) {
1353 - resetContextDependences();
1354 - resetHooks();
1355 -
1356 - // Reset in case completion throws.
1357 - // This is only used in DEV and when replaying is on.
1358 - let mayReplay;
1359 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1360 - mayReplay = mayReplayFailedUnitOfWork;
1361 - mayReplayFailedUnitOfWork = true;
1362 - }
1363 -
1364 - if (nextUnitOfWork === null) {
1365 - // This is a fatal error.
1366 - didFatal = true;
1367 - onUncaughtError(thrownValue);
1368 - } else {
1369 - if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) {
1370 - // Record the time spent rendering before an error was thrown.
1371 - // This avoids inaccurate Profiler durations in the case of a suspended render.
1372 - stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true);
1373 - }
1374 -
1375 - if (__DEV__) {
1376 - // Reset global debug state
1377 - // We assume this is defined in DEV
1378 - (resetCurrentlyProcessingQueue: any)();
1379 - }
1380 -
1381 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1382 - if (mayReplay) {
1383 - const failedUnitOfWork: Fiber = nextUnitOfWork;
1384 - replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
1385 - }
1386 - }
1387 -
1388 - // TODO: we already know this isn't true in some cases.
1389 - // At least this shows a nicer error message until we figure out the cause.
1390 - // https://github.com/facebook/react/issues/12449#issuecomment-386727431
1391 - invariant(
1392 - nextUnitOfWork !== null,
1393 - 'Failed to replay rendering after an error. This ' +
1394 - 'is likely caused by a bug in React. Please file an issue ' +
1395 - 'with a reproducing case to help us find it.',
1396 - );
1397 -
1398 - const sourceFiber: Fiber = nextUnitOfWork;
1399 - let returnFiber = sourceFiber.return;
1400 - if (returnFiber === null) {
1401 - // This is the root. The root could capture its own errors. However,
1402 - // we don't know if it errors before or after we pushed the host
1403 - // context. This information is needed to avoid a stack mismatch.
1404 - // Because we're not sure, treat this as a fatal error. We could track
1405 - // which phase it fails in, but doesn't seem worth it. At least
1406 - // for now.
1407 - didFatal = true;
1408 - onUncaughtError(thrownValue);
1409 - } else {
1410 - throwException(
1411 - root,
1412 - returnFiber,
1413 - sourceFiber,
1414 - thrownValue,
1415 - nextRenderExpirationTime,
1416 - );
1417 - nextUnitOfWork = completeUnitOfWork(sourceFiber);
1418 - continue;
1419 - }
1420 - }
1421 - }
1422 - break;
1423 - } while (true);
1424 -
1425 - if (enableSchedulerTracing) {
1426 - // Traced work is done for now; restore the previous interactions.
1427 - __interactionsRef.current = prevInteractions;
1428 - }
1429 -
1430 - // We're done performing work. Time to clean up.
1431 - isWorking = false;
1432 - ReactCurrentDispatcher.current = previousDispatcher;
1433 - resetContextDependences();
1434 - resetHooks();
1435 -
1436 - // Yield back to main thread.
1437 - if (didFatal) {
1438 - const didCompleteRoot = false;
1439 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1440 - interruptedBy = null;
1441 - // There was a fatal error.
1442 - if (__DEV__) {
1443 - resetStackAfterFatalErrorInDev();
1444 - }
1445 - // `nextRoot` points to the in-progress root. A non-null value indicates
1446 - // that we're in the middle of an async render. Set it to null to indicate
1447 - // there's no more work to be done in the current batch.
1448 - nextRoot = null;
1449 - onFatal(root);
1450 - return;
1451 - }
1452 -
1453 - if (nextUnitOfWork !== null) {
1454 - // There's still remaining async work in this tree, but we ran out of time
1455 - // in the current frame. Yield back to the renderer. Unless we're
1456 - // interrupted by a higher priority update, we'll continue later from where
1457 - // we left off.
1458 - const didCompleteRoot = false;
1459 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1460 - interruptedBy = null;
1461 - onYield(root);
1462 - return;
1463 - }
1464 -
1465 - // We completed the whole tree.
1466 - const didCompleteRoot = true;
1467 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1468 - const rootWorkInProgress = root.current.alternate;
1469 - invariant(
1470 - rootWorkInProgress !== null,
1471 - 'Finished root should have a work-in-progress. This error is likely ' +
1472 - 'caused by a bug in React. Please file an issue.',
1473 - );
1474 -
1475 - // `nextRoot` points to the in-progress root. A non-null value indicates
1476 - // that we're in the middle of an async render. Set it to null to indicate
1477 - // there's no more work to be done in the current batch.
1478 - nextRoot = null;
1479 - interruptedBy = null;
1480 -
1481 - if (nextRenderDidError) {
1482 - // There was an error
1483 - if (hasLowerPriorityWork(root, expirationTime)) {
1484 - // There's lower priority work. If so, it may have the effect of fixing
1485 - // the exception that was just thrown. Exit without committing. This is
1486 - // similar to a suspend, but without a timeout because we're not waiting
1487 - // for a promise to resolve. React will restart at the lower
1488 - // priority level.
1489 - markSuspendedPriorityLevel(root, expirationTime);
1490 - const suspendedExpirationTime = expirationTime;
1491 - const rootExpirationTime = root.expirationTime;
1492 - onSuspend(
1493 - root,
1494 - rootWorkInProgress,
1495 - suspendedExpirationTime,
1496 - rootExpirationTime,
1497 - -1, // Indicates no timeout
1498 - );
1499 - return;
1500 - } else if (
1501 - // There's no lower priority work, but we're rendering asynchronously.
1502 - // Synchronously attempt to render the same level one more time. This is
1503 - // similar to a suspend, but without a timeout because we're not waiting
1504 - // for a promise to resolve.
1505 - !root.didError &&
1506 - isYieldy
1507 - ) {
1508 - root.didError = true;
1509 - const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
1510 - const rootExpirationTime = (root.expirationTime = Sync);
1511 - onSuspend(
1512 - root,
1513 - rootWorkInProgress,
1514 - suspendedExpirationTime,
1515 - rootExpirationTime,
1516 - -1, // Indicates no timeout
1517 - );
1518 - return;
1519 - }
1520 - }
1521 -
1522 - // Check if we should suspend this commit.
1523 - // If mostRecentEventTime is Sync, that means we didn't track any event
1524 - // times. That can happen if we retried but nothing switched from fallback
1525 - // to content. There's no reason to delay doing no work.
1526 - if (isYieldy && nextRenderDidSuspend && mostRecentEventTime !== Sync) {
1527 - // The tree was suspended.
1528 - const suspendedExpirationTime = expirationTime;
1529 - markSuspendedPriorityLevel(root, suspendedExpirationTime);
1530 -
1531 - const eventTimeMs: number = inferTimeFromExpirationTime(
1532 - mostRecentEventTime,
1533 - );
1534 - const currentTimeMs: number = now();
1535 - const timeElapsed = currentTimeMs - eventTimeMs;
1536 -
1537 - let msUntilTimeout = jnd(timeElapsed) - timeElapsed;
1538 -
1539 - if (msUntilTimeout < 10) {
1540 - // Don't bother with a very short suspense time.
1541 - msUntilTimeout = 0;
1542 - } else {
1543 - // Compute the time until this render pass would expire.
1544 - const timeUntilExpirationMs =
1545 - expirationTimeToMs(suspendedExpirationTime) +
1546 - originalStartTimeMs -
1547 - currentTimeMs;
1548 - // Clamp the timeout to the expiration time.
1549 - if (timeUntilExpirationMs < msUntilTimeout) {
1550 - msUntilTimeout = timeUntilExpirationMs;
1551 - }
1552 - }
1553 -
1554 - const rootExpirationTime = root.expirationTime;
1555 - onSuspend(
1556 - root,
1557 - rootWorkInProgress,
1558 - suspendedExpirationTime,
1559 - rootExpirationTime,
1560 - msUntilTimeout,
1561 - );
1562 - return;
1563 - }
1564 -
1565 - // Ready to commit.
1566 - onComplete(root, rootWorkInProgress, expirationTime);
1567 -}
1568 -
1569 -function captureCommitPhaseError(sourceFiber: Fiber, value: mixed) {
1570 - const expirationTime = Sync;
1571 - let fiber = sourceFiber.return;
1572 - while (fiber !== null) {
1573 - switch (fiber.tag) {
1574 - case ClassComponent:
1575 - const ctor = fiber.type;
1576 - const instance = fiber.stateNode;
1577 - if (
1578 - typeof ctor.getDerivedStateFromError === 'function' ||
1579 - (typeof instance.componentDidCatch === 'function' &&
1580 - !isAlreadyFailedLegacyErrorBoundary(instance))
1581 - ) {
1582 - const errorInfo = createCapturedValue(value, sourceFiber);
1583 - const update = createClassErrorUpdate(
1584 - fiber,
1585 - errorInfo,
1586 - expirationTime,
1587 - );
1588 - enqueueUpdate(fiber, update);
1589 - scheduleWork(fiber, expirationTime);
1590 - return;
1591 - }
1592 - break;
1593 - case HostRoot: {
1594 - const errorInfo = createCapturedValue(value, sourceFiber);
1595 - const update = createRootErrorUpdate(fiber, errorInfo, expirationTime);
1596 - enqueueUpdate(fiber, update);
1597 - scheduleWork(fiber, expirationTime);
1598 - return;
1599 - }
1600 - }
1601 - fiber = fiber.return;
1602 - }
1603 -
1604 - if (sourceFiber.tag === HostRoot) {
1605 - // Error was thrown at the root. There is no parent, so the root
1606 - // itself should capture it.
1607 - const rootFiber = sourceFiber;
1608 - const errorInfo = createCapturedValue(value, rootFiber);
1609 - const update = createRootErrorUpdate(rootFiber, errorInfo, expirationTime);
1610 - enqueueUpdate(rootFiber, update);
1611 - scheduleWork(rootFiber, expirationTime);
1612 - }
1613 -}
1614 -
1615 -function computeThreadID(
1616 - expirationTime: ExpirationTime,
1617 - interactionThreadID: number,
1618 -): number {
1619 - // Interaction threads are unique per root and expiration time.
1620 - return expirationTime * 1000 + interactionThreadID;
1621 -}
1622 -
1623 -// Creates a unique async expiration time.
1624 -function computeUniqueAsyncExpiration(): ExpirationTime {
1625 - const currentTime = requestCurrentTime();
1626 - let result = computeAsyncExpiration(currentTime);
1627 - if (result >= lastUniqueAsyncExpiration) {
1628 - // Since we assume the current time monotonically increases, we only hit
1629 - // this branch when computeUniqueAsyncExpiration is fired multiple times
1630 - // within a 200ms window (or whatever the async bucket size is).
1631 - result = lastUniqueAsyncExpiration - 1;
1632 - }
1633 - lastUniqueAsyncExpiration = result;
1634 - return lastUniqueAsyncExpiration;
1635 -}
1636 -
1637 -function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
1638 - let expirationTime;
1639 - if (expirationContext !== NoWork) {
1640 - // An explicit expiration context was set;
1641 - expirationTime = expirationContext;
1642 - } else if (isWorking) {
1643 - if (isCommitting) {
1644 - // Updates that occur during the commit phase should have sync priority
1645 - // by default.
1646 - expirationTime = Sync;
1647 - } else {
1648 - // Updates during the render phase should expire at the same time as
1649 - // the work that is being rendered.
1650 - expirationTime = nextRenderExpirationTime;
1651 - }
1652 - } else {
1653 - // No explicit expiration context was set, and we're not currently
1654 - // performing work. Calculate a new expiration time.
1655 - if (fiber.mode & ConcurrentMode) {
1656 - if (isBatchingInteractiveUpdates) {
1657 - // This is an interactive update
1658 - expirationTime = computeInteractiveExpiration(currentTime);
1659 - } else {
1660 - // This is an async update
1661 - expirationTime = computeAsyncExpiration(currentTime);
1662 - }
1663 - // If we're in the middle of rendering a tree, do not update at the same
1664 - // expiration time that is already rendering.
1665 - if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
1666 - expirationTime -= 1;
1667 - }
1668 - } else {
1669 - // This is a sync update
1670 - expirationTime = Sync;
1671 - }
1672 - }
1673 - if (isBatchingInteractiveUpdates) {
1674 - // This is an interactive update. Keep track of the lowest pending
1675 - // interactive expiration time. This allows us to synchronously flush
1676 - // all interactive updates when needed.
1677 - if (
1678 - lowestPriorityPendingInteractiveExpirationTime === NoWork ||
1679 - expirationTime < lowestPriorityPendingInteractiveExpirationTime
1680 - ) {
1681 - lowestPriorityPendingInteractiveExpirationTime = expirationTime;
1682 - }
1683 - }
1684 - return expirationTime;
1685 -}
1686 -
1687 -function markRenderEventTime(expirationTime: ExpirationTime): void {
1688 - if (expirationTime < mostRecentEventTime) {
1689 - mostRecentEventTime = expirationTime;
1690 - }
1691 -}
1692 -
1693 -function renderDidSuspend() {
1694 - nextRenderDidSuspend = true;
1695 -}
1696 -
1697 -function renderDidError() {
1698 - nextRenderDidError = true;
1699 -}
1700 -
1701 -function inferTimeFromExpirationTime(expirationTime: ExpirationTime) {
1702 - // We don't know exactly when the update was scheduled, but we can infer an
1703 - // approximate start time from the expiration time.
1704 - const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
1705 - return (
1706 - earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION + originalStartTimeMs
1707 - );
1708 -}
1709 -
1710 -function pingSuspendedRoot(
1711 - root: FiberRoot,
1712 - thenable: Thenable,
1713 - pingTime: ExpirationTime,
1714 -) {
1715 - // A promise that previously suspended React from committing has resolved.
1716 - // If React is still suspended, try again at the previous level (pingTime).
1717 -
1718 - const pingCache = root.pingCache;
1719 - if (pingCache !== null) {
1720 - // The thenable resolved, so we no longer need to memoize, because it will
1721 - // never be thrown again.
1722 - pingCache.delete(thenable);
1723 - }
1724 -
1725 - if (nextRoot !== null && nextRenderExpirationTime === pingTime) {
1726 - // Received a ping at the same priority level at which we're currently
1727 - // rendering. Restart from the root.
1728 - nextRoot = null;
1729 - } else {
1730 - // Confirm that the root is still suspended at this level. Otherwise exit.
1731 - if (isPriorityLevelSuspended(root, pingTime)) {
1732 - // Ping at the original level
1733 - markPingedPriorityLevel(root, pingTime);
1734 - const rootExpirationTime = root.expirationTime;
1735 - if (rootExpirationTime !== NoWork) {
1736 - requestWork(root, rootExpirationTime);
1737 - }
1738 - }
1739 - }
1740 -}
1741 -
1742 -function retryTimedOutBoundary(boundaryFiber: Fiber) {
1743 - const currentTime = requestCurrentTime();
1744 - const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
1745 - const root = scheduleWorkToRoot(boundaryFiber, retryTime);
1746 - if (root !== null) {
1747 - markPendingPriorityLevel(root, retryTime);
1748 - const rootExpirationTime = root.expirationTime;
1749 - if (rootExpirationTime !== NoWork) {
1750 - requestWork(root, rootExpirationTime);
1751 - }
1752 - }
1753 -}
1754 -
1755 -function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
1756 - // The boundary fiber (a Suspense component) previously timed out and was
1757 - // rendered in its fallback state. One of the promises that suspended it has
1758 - // resolved, which means at least part of the tree was likely unblocked. Try
1759 - // rendering again, at a new expiration time.
1760 -
1761 - let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
1762 - if (enableSuspenseServerRenderer) {
1763 - switch (boundaryFiber.tag) {
1764 - case SuspenseComponent:
1765 - retryCache = boundaryFiber.stateNode;
1766 - break;
1767 - case DehydratedSuspenseComponent:
1768 - retryCache = boundaryFiber.memoizedState;
1769 - break;
1770 - default:
1771 - invariant(
1772 - false,
1773 - 'Pinged unknown suspense boundary type. ' +
1774 - 'This is probably a bug in React.',
1775 - );
1776 - }
1777 - } else {
1778 - retryCache = boundaryFiber.stateNode;
1779 - }
1780 - if (retryCache !== null) {
1781 - // The thenable resolved, so we no longer need to memoize, because it will
1782 - // never be thrown again.
1783 - retryCache.delete(thenable);
1784 - }
1785 -
1786 - retryTimedOutBoundary(boundaryFiber);
1787 -}
1788 -
1789 -function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
1790 - recordScheduleUpdate();
1791 -
1792 - if (__DEV__) {
1793 - if (fiber.tag === ClassComponent) {
1794 - const instance = fiber.stateNode;
1795 - warnAboutInvalidUpdates(instance);
1796 - }
1797 - }
1798 -
1799 - // Update the source fiber's expiration time
1800 - if (fiber.expirationTime < expirationTime) {
1801 - fiber.expirationTime = expirationTime;
1802 - }
1803 - let alternate = fiber.alternate;
1804 - if (alternate !== null && alternate.expirationTime < expirationTime) {
1805 - alternate.expirationTime = expirationTime;
1806 - }
1807 - // Walk the parent path to the root and update the child expiration time.
1808 - let node = fiber.return;
1809 - let root = null;
1810 - if (node === null && fiber.tag === HostRoot) {
1811 - root = fiber.stateNode;
1812 - } else {
1813 - while (node !== null) {
1814 - alternate = node.alternate;
1815 - if (node.childExpirationTime < expirationTime) {
1816 - node.childExpirationTime = expirationTime;
1817 - if (
1818 - alternate !== null &&
1819 - alternate.childExpirationTime < expirationTime
1820 - ) {
1821 - alternate.childExpirationTime = expirationTime;
1822 - }
1823 - } else if (
1824 - alternate !== null &&
1825 - alternate.childExpirationTime < expirationTime
1826 - ) {
1827 - alternate.childExpirationTime = expirationTime;
1828 - }
1829 - if (node.return === null && node.tag === HostRoot) {
1830 - root = node.stateNode;
1831 - break;
1832 - }
1833 - node = node.return;
1834 - }
1835 - }
1836 -
1837 - if (enableSchedulerTracing) {
1838 - if (root !== null) {
1839 - const interactions = __interactionsRef.current;
1840 - if (interactions.size > 0) {
1841 - const pendingInteractionMap = root.pendingInteractionMap;
1842 - const pendingInteractions = pendingInteractionMap.get(expirationTime);
1843 - if (pendingInteractions != null) {
1844 - interactions.forEach(interaction => {
1845 - if (!pendingInteractions.has(interaction)) {
1846 - // Update the pending async work count for previously unscheduled interaction.
1847 - interaction.__count++;
1848 - }
1849 -
1850 - pendingInteractions.add(interaction);
1851 - });
1852 - } else {
1853 - pendingInteractionMap.set(expirationTime, new Set(interactions));
1854 -
1855 - // Update the pending async work count for the current interactions.
1856 - interactions.forEach(interaction => {
1857 - interaction.__count++;
1858 - });
1859 - }
1860 -
1861 - const subscriber = __subscriberRef.current;
1862 - if (subscriber !== null) {
1863 - const threadID = computeThreadID(
1864 - expirationTime,
1865 - root.interactionThreadID,
1866 - );
1867 - subscriber.onWorkScheduled(interactions, threadID);
1868 - }
1869 - }
1870 - }
1871 - }
1872 - return root;
1873 -}
1874 -
1875 -// in a test-like environment, we want to warn if dispatchAction() is
1876 -// called outside of a TestUtils.act(...)/batchedUpdates/render call.
1877 -// so we have a a step counter for when we descend/ascend from
1878 -// act() calls, and test on it for when to warn
1879 -// It's a tuple with a single value. Look for shared/createAct to
1880 -// see how we change the value inside act() calls
1881 -
1882 -export function warnIfNotCurrentlyActingUpdatesInDev(fiber: Fiber): void {
1883 - if (__DEV__) {
1884 - if (
1885 - isBatchingUpdates === false &&
1886 - isRendering === false &&
1887 - ReactShouldWarnActingUpdates.current === false
1888 - ) {
1889 - warningWithoutStack(
1890 - false,
1891 - 'An update to %s inside a test was not wrapped in act(...).\n\n' +
1892 - 'When testing, code that causes React state updates should be wrapped into act(...):\n\n' +
1893 - 'act(() => {\n' +
1894 - ' /* fire events that update state */\n' +
1895 - '});\n' +
1896 - '/* assert on the output */\n\n' +
1897 - "This ensures that you're testing the behavior the user would see in the browser." +
1898 - ' Learn more at https://fb.me/react-wrap-tests-with-act' +
1899 - '%s',
1900 - getComponentName(fiber.type),
1901 - getStackByFiberInDevAndProd(fiber),
1902 - );
1903 - }
1904 - }
1905 -}
1906 -
1907 -function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1908 - const root = scheduleWorkToRoot(fiber, expirationTime);
1909 - if (root === null) {
1910 - if (__DEV__) {
1911 - switch (fiber.tag) {
1912 - case ClassComponent:
1913 - warnAboutUpdateOnUnmounted(fiber, true);
1914 - break;
1915 - case FunctionComponent:
1916 - case ForwardRef:
1917 - case MemoComponent:
1918 - case SimpleMemoComponent:
1919 - warnAboutUpdateOnUnmounted(fiber, false);
1920 - break;
1921 - }
1922 - }
1923 - return;
1924 - }
1925 -
1926 - if (
1927 - !isWorking &&
1928 - nextRenderExpirationTime !== NoWork &&
1929 - expirationTime > nextRenderExpirationTime
1930 - ) {
1931 - // This is an interruption. (Used for performance tracking.)
1932 - interruptedBy = fiber;
1933 - resetStack();
1934 - }
1935 - markPendingPriorityLevel(root, expirationTime);
1936 - if (
1937 - // If we're in the render phase, we don't need to schedule this root
1938 - // for an update, because we'll do it before we exit...
1939 - !isWorking ||
1940 - isCommitting ||
1941 - // ...unless this is a different root than the one we're rendering.
1942 - nextRoot !== root
1943 - ) {
1944 - const rootExpirationTime = root.expirationTime;
1945 - requestWork(root, rootExpirationTime);
1946 - }
1947 - if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1948 - // Reset this back to zero so subsequent updates don't throw.
1949 - nestedUpdateCount = 0;
1950 - invariant(
1951 - false,
1952 - 'Maximum update depth exceeded. This can happen when a ' +
1953 - 'component repeatedly calls setState inside ' +
1954 - 'componentWillUpdate or componentDidUpdate. React limits ' +
1955 - 'the number of nested updates to prevent infinite loops.',
1956 - );
1957 - }
1958 - if (__DEV__) {
1959 - if (
1960 - isInPassiveEffectDEV &&
1961 - nestedPassiveEffectCountDEV > NESTED_PASSIVE_UPDATE_LIMIT
1962 - ) {
1963 - nestedPassiveEffectCountDEV = 0;
1964 - warning(
1965 - false,
1966 - 'Maximum update depth exceeded. This can happen when a ' +
1967 - 'component calls setState inside useEffect, but ' +
1968 - "useEffect either doesn't have a dependency array, or " +
1969 - 'one of the dependencies changes on every render.',
1970 - );
1971 - }
1972 - }
1973 -}
1974 -
1975 -function deferredUpdates<A>(fn: () => A): A {
1976 - const currentTime = requestCurrentTime();
1977 - const previousExpirationContext = expirationContext;
1978 - const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1979 - expirationContext = computeAsyncExpiration(currentTime);
1980 - isBatchingInteractiveUpdates = false;
1981 - try {
1982 - return fn();
1983 - } finally {
1984 - expirationContext = previousExpirationContext;
1985 - isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1986 - }
1987 -}
1988 -
1989 -function syncUpdates<A, B, C0, D, R>(
1990 - fn: (A, B, C0, D) => R,
1991 - a: A,
1992 - b: B,
1993 - c: C0,
1994 - d: D,
1995 -): R {
1996 - const previousExpirationContext = expirationContext;
1997 - expirationContext = Sync;
1998 - try {
1999 - return fn(a, b, c, d);
2000 - } finally {
2001 - expirationContext = previousExpirationContext;
2002 - }
2003 -}
2004 -
2005 -// TODO: Everything below this is written as if it has been lifted to the
2006 -// renderers. I'll do this in a follow-up.
2007 -
2008 -// Linked-list of roots
2009 -let firstScheduledRoot: FiberRoot | null = null;
2010 -let lastScheduledRoot: FiberRoot | null = null;
2011 -
2012 -let callbackExpirationTime: ExpirationTime = NoWork;
2013 -let callbackID: *;
2014 -let isRendering: boolean = false;
2015 -let nextFlushedRoot: FiberRoot | null = null;
2016 -let nextFlushedExpirationTime: ExpirationTime = NoWork;
2017 -let lowestPriorityPendingInteractiveExpirationTime: ExpirationTime = NoWork;
2018 -let hasUnhandledError: boolean = false;
2019 -let unhandledError: mixed | null = null;
2020 -
2021 -let isBatchingUpdates: boolean = false;
2022 -let isUnbatchingUpdates: boolean = false;
2023 -let isBatchingInteractiveUpdates: boolean = false;
2024 -
2025 -let completedBatches: Array<Batch> | null = null;
2026 -
2027 -let originalStartTimeMs: number = now();
2028 -let currentRendererTime: ExpirationTime = msToExpirationTime(
2029 - originalStartTimeMs,
2030 -);
2031 -let currentSchedulerTime: ExpirationTime = currentRendererTime;
2032 -
2033 -// Use these to prevent an infinite loop of nested updates
2034 -const NESTED_UPDATE_LIMIT = 50;
2035 -let nestedUpdateCount: number = 0;
2036 -let lastCommittedRootDuringThisBatch: FiberRoot | null = null;
2037 -
2038 -// Similar, but for useEffect infinite loops. These are DEV-only.
2039 -const NESTED_PASSIVE_UPDATE_LIMIT = 50;
2040 -let nestedPassiveEffectCountDEV;
2041 -let isInPassiveEffectDEV;
2042 -if (__DEV__) {
2043 - nestedPassiveEffectCountDEV = 0;
2044 - isInPassiveEffectDEV = false;
2045 -}
2046 -
2047 -function recomputeCurrentRendererTime() {
2048 - const currentTimeMs = now() - originalStartTimeMs;
2049 - currentRendererTime = msToExpirationTime(currentTimeMs);
2050 -}
2051 -
2052 -function scheduleCallbackWithExpirationTime(
2053 - root: FiberRoot,
2054 - expirationTime: ExpirationTime,
2055 -) {
2056 - if (callbackExpirationTime !== NoWork) {
2057 - // A callback is already scheduled. Check its expiration time (timeout).
2058 - if (expirationTime < callbackExpirationTime) {
2059 - // Existing callback has sufficient timeout. Exit.
2060 - return;
2061 - } else {
2062 - if (callbackID !== null) {
2063 - // Existing callback has insufficient timeout. Cancel and schedule a
2064 - // new one.
2065 - cancelCallback(callbackID);
2066 - }
2067 - }
2068 - // The request callback timer is already running. Don't start a new one.
2069 - } else {
2070 - startRequestCallbackTimer();
2071 - }
2072 -
2073 - callbackExpirationTime = expirationTime;
2074 - const currentMs = now() - originalStartTimeMs;
2075 - const expirationTimeMs = expirationTimeToMs(expirationTime);
2076 - const timeout = expirationTimeMs - currentMs;
2077 - const priorityLevel = getCurrentPriorityLevel();
2078 - callbackID = scheduleCallback(priorityLevel, performAsyncWork, {timeout});
2079 -}
2080 -
2081 -// For every call to renderRoot, one of onFatal, onComplete, onSuspend, and
2082 -// onYield is called upon exiting. We use these in lieu of returning a tuple.
2083 -// I've also chosen not to inline them into renderRoot because these will
2084 -// eventually be lifted into the renderer.
2085 -function onFatal(root) {
2086 - root.finishedWork = null;
2087 -}
2088 -
2089 -function onComplete(
2090 - root: FiberRoot,
2091 - finishedWork: Fiber,
2092 - expirationTime: ExpirationTime,
2093 -) {
2094 - root.pendingCommitExpirationTime = expirationTime;
2095 - root.finishedWork = finishedWork;
2096 -}
2097 -
2098 -function onSuspend(
2099 - root: FiberRoot,
2100 - finishedWork: Fiber,
2101 - suspendedExpirationTime: ExpirationTime,
2102 - rootExpirationTime: ExpirationTime,
2103 - msUntilTimeout: number,
2104 -): void {
2105 - root.expirationTime = rootExpirationTime;
2106 - if (msUntilTimeout === 0 && (disableYielding || !shouldYield())) {
2107 - // Don't wait an additional tick. Commit the tree immediately.
2108 - root.pendingCommitExpirationTime = suspendedExpirationTime;
2109 - root.finishedWork = finishedWork;
2110 - } else if (msUntilTimeout > 0) {
2111 - // Wait `msUntilTimeout` milliseconds before committing.
2112 - root.timeoutHandle = scheduleTimeout(
2113 - onTimeout.bind(null, root, finishedWork, suspendedExpirationTime),
2114 - msUntilTimeout,
2115 - );
2116 - }
2117 -}
2118 -
2119 -function onYield(root) {
2120 - root.finishedWork = null;
2121 -}
2122 -
2123 -function onTimeout(root, finishedWork, suspendedExpirationTime) {
2124 - // The root timed out. Commit it.
2125 - root.pendingCommitExpirationTime = suspendedExpirationTime;
2126 - root.finishedWork = finishedWork;
2127 - // Read the current time before entering the commit phase. We can be
2128 - // certain this won't cause tearing related to batching of event updates
2129 - // because we're at the top of a timer event.
2130 - recomputeCurrentRendererTime();
2131 - currentSchedulerTime = currentRendererTime;
2132 - flushRoot(root, suspendedExpirationTime);
2133 -}
2134 -
2135 -function onCommit(root, expirationTime) {
2136 - root.expirationTime = expirationTime;
2137 - root.finishedWork = null;
2138 -}
2139 -
2140 -function requestCurrentTime() {
2141 - // requestCurrentTime is called by the scheduler to compute an expiration
2142 - // time.
2143 - //
2144 - // Expiration times are computed by adding to the current time (the start
2145 - // time). However, if two updates are scheduled within the same event, we
2146 - // should treat their start times as simultaneous, even if the actual clock
2147 - // time has advanced between the first and second call.
2148 -
2149 - // In other words, because expiration times determine how updates are batched,
2150 - // we want all updates of like priority that occur within the same event to
2151 - // receive the same expiration time. Otherwise we get tearing.
2152 - //
2153 - // We keep track of two separate times: the current "renderer" time and the
2154 - // current "scheduler" time. The renderer time can be updated whenever; it
2155 - // only exists to minimize the calls performance.now.
2156 - //
2157 - // But the scheduler time can only be updated if there's no pending work, or
2158 - // if we know for certain that we're not in the middle of an event.
2159 -
2160 - if (isRendering) {
2161 - // We're already rendering. Return the most recently read time.
2162 - return currentSchedulerTime;
2163 - }
2164 - // Check if there's pending work.
2165 - findHighestPriorityRoot();
2166 - if (
2167 - nextFlushedExpirationTime === NoWork ||
2168 - nextFlushedExpirationTime === Never
2169 - ) {
2170 - // If there's no pending work, or if the pending work is offscreen, we can
2171 - // read the current time without risk of tearing.
2172 - recomputeCurrentRendererTime();
2173 - currentSchedulerTime = currentRendererTime;
2174 - return currentSchedulerTime;
2175 - }
2176 - // There's already pending work. We might be in the middle of a browser
2177 - // event. If we were to read the current time, it could cause multiple updates
2178 - // within the same event to receive different expiration times, leading to
2179 - // tearing. Return the last read time. During the next idle callback, the
2180 - // time will be updated.
2181 - return currentSchedulerTime;
2182 -}
2183 -
2184 -// requestWork is called by the scheduler whenever a root receives an update.
2185 -// It's up to the renderer to call renderRoot at some point in the future.
2186 -function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
2187 - addRootToSchedule(root, expirationTime);
2188 - if (isRendering) {
2189 - // Prevent reentrancy. Remaining work will be scheduled at the end of
2190 - // the currently rendering batch.
2191 - return;
2192 - }
2193 -
2194 - if (isBatchingUpdates) {
2195 - // Flush work at the end of the batch.
2196 - if (isUnbatchingUpdates) {
2197 - // ...unless we're inside unbatchedUpdates, in which case we should
2198 - // flush it now.
2199 - nextFlushedRoot = root;
2200 - nextFlushedExpirationTime = Sync;
2201 - performWorkOnRoot(root, Sync, false);
2202 - }
2203 - return;
2204 - }
2205 -
2206 - // TODO: Get rid of Sync and use current time?
2207 - if (expirationTime === Sync) {
2208 - performSyncWork();
2209 - } else {
2210 - scheduleCallbackWithExpirationTime(root, expirationTime);
2211 - }
2212 -}
2213 -
2214 -function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
2215 - // Add the root to the schedule.
2216 - // Check if this root is already part of the schedule.
2217 - if (root.nextScheduledRoot === null) {
2218 - // This root is not already scheduled. Add it.
2219 - root.expirationTime = expirationTime;
2220 - if (lastScheduledRoot === null) {
2221 - firstScheduledRoot = lastScheduledRoot = root;
2222 - root.nextScheduledRoot = root;
2223 - } else {
2224 - lastScheduledRoot.nextScheduledRoot = root;
2225 - lastScheduledRoot = root;
2226 - lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
2227 - }
2228 - } else {
2229 - // This root is already scheduled, but its priority may have increased.
2230 - const remainingExpirationTime = root.expirationTime;
2231 - if (expirationTime > remainingExpirationTime) {
2232 - // Update the priority.
2233 - root.expirationTime = expirationTime;
2234 - }
2235 - }
2236 -}
2237 -
2238 -function findHighestPriorityRoot() {
2239 - let highestPriorityWork = NoWork;
2240 - let highestPriorityRoot = null;
2241 - if (lastScheduledRoot !== null) {
2242 - let previousScheduledRoot = lastScheduledRoot;
2243 - let root = firstScheduledRoot;
2244 - while (root !== null) {
2245 - const remainingExpirationTime = root.expirationTime;
2246 - if (remainingExpirationTime === NoWork) {
2247 - // This root no longer has work. Remove it from the scheduler.
2248 -
2249 - // TODO: This check is redudant, but Flow is confused by the branch
2250 - // below where we set lastScheduledRoot to null, even though we break
2251 - // from the loop right after.
2252 - invariant(
2253 - previousScheduledRoot !== null && lastScheduledRoot !== null,
2254 - 'Should have a previous and last root. This error is likely ' +
2255 - 'caused by a bug in React. Please file an issue.',
2256 - );
2257 - if (root === root.nextScheduledRoot) {
2258 - // This is the only root in the list.
2259 - root.nextScheduledRoot = null;
2260 - firstScheduledRoot = lastScheduledRoot = null;
2261 - break;
2262 - } else if (root === firstScheduledRoot) {
2263 - // This is the first root in the list.
2264 - const next = root.nextScheduledRoot;
2265 - firstScheduledRoot = next;
2266 - lastScheduledRoot.nextScheduledRoot = next;
2267 - root.nextScheduledRoot = null;
2268 - } else if (root === lastScheduledRoot) {
2269 - // This is the last root in the list.
2270 - lastScheduledRoot = previousScheduledRoot;
2271 - lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
2272 - root.nextScheduledRoot = null;
2273 - break;
2274 - } else {
2275 - previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
2276 - root.nextScheduledRoot = null;
2277 - }
2278 - root = previousScheduledRoot.nextScheduledRoot;
2279 - } else {
2280 - if (remainingExpirationTime > highestPriorityWork) {
2281 - // Update the priority, if it's higher
2282 - highestPriorityWork = remainingExpirationTime;
2283 - highestPriorityRoot = root;
2284 - }
2285 - if (root === lastScheduledRoot) {
2286 - break;
2287 - }
2288 - if (highestPriorityWork === Sync) {
2289 - // Sync is highest priority by definition so
2290 - // we can stop searching.
2291 - break;
2292 - }
2293 - previousScheduledRoot = root;
2294 - root = root.nextScheduledRoot;
2295 - }
2296 - }
2297 - }
2298 -
2299 - nextFlushedRoot = highestPriorityRoot;
2300 - nextFlushedExpirationTime = highestPriorityWork;
2301 -}
2302 -
2303 -function performAsyncWork(didTimeout) {
2304 - if (didTimeout) {
2305 - // The callback timed out. That means at least one update has expired.
2306 - // Iterate through the root schedule. If they contain expired work, set
2307 - // the next render expiration time to the current time. This has the effect
2308 - // of flushing all expired work in a single batch, instead of flushing each
2309 - // level one at a time.
2310 - if (firstScheduledRoot !== null) {
2311 - recomputeCurrentRendererTime();
2312 - let root: FiberRoot = firstScheduledRoot;
2313 - do {
2314 - didExpireAtExpirationTime(root, currentRendererTime);
2315 - // The root schedule is circular, so this is never null.
2316 - root = (root.nextScheduledRoot: any);
2317 - } while (root !== firstScheduledRoot);
2318 - }
2319 - }
2320 -
2321 - // Keep working on roots until there's no more work, or until there's a higher
2322 - // priority event.
2323 - findHighestPriorityRoot();
2324 -
2325 - if (disableYielding) {
2326 - // Just do it all
2327 - while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork) {
2328 - performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
2329 - findHighestPriorityRoot();
2330 - }
2331 - } else {
2332 - recomputeCurrentRendererTime();
2333 - currentSchedulerTime = currentRendererTime;
2334 -
2335 - if (enableUserTimingAPI) {
2336 - const didExpire = nextFlushedExpirationTime > currentRendererTime;
2337 - const timeout = expirationTimeToMs(nextFlushedExpirationTime);
2338 - stopRequestCallbackTimer(didExpire, timeout);
2339 - }
2340 -
2341 - while (
2342 - nextFlushedRoot !== null &&
2343 - nextFlushedExpirationTime !== NoWork &&
2344 - !(shouldYield() && currentRendererTime > nextFlushedExpirationTime)
2345 - ) {
2346 - performWorkOnRoot(
2347 - nextFlushedRoot,
2348 - nextFlushedExpirationTime,
2349 - currentRendererTime > nextFlushedExpirationTime,
2350 - );
2351 - findHighestPriorityRoot();
2352 - recomputeCurrentRendererTime();
2353 - currentSchedulerTime = currentRendererTime;
2354 - }
2355 - }
2356 -
2357 - // We're done flushing work. Either we ran out of time in this callback,
2358 - // or there's no more work left with sufficient priority.
2359 -
2360 - // If we're inside a callback, set this to false since we just completed it.
2361 - callbackExpirationTime = NoWork;
2362 - callbackID = null;
2363 -
2364 - // If there's work left over, schedule a new callback.
2365 - if (nextFlushedExpirationTime !== NoWork) {
2366 - scheduleCallbackWithExpirationTime(
2367 - ((nextFlushedRoot: any): FiberRoot),
2368 - nextFlushedExpirationTime,
2369 - );
2370 - }
2371 -
2372 - // Clean-up.
2373 - finishRendering();
2374 -}
2375 -
2376 -function performSyncWork() {
2377 - performWork(Sync);
2378 -}
2379 -
2380 -function performWork(minExpirationTime: ExpirationTime) {
2381 - // Keep working on roots until there's no more work, or until there's a higher
2382 - // priority event.
2383 - findHighestPriorityRoot();
2384 -
2385 - while (
2386 - nextFlushedRoot !== null &&
2387 - nextFlushedExpirationTime !== NoWork &&
2388 - minExpirationTime <= nextFlushedExpirationTime
2389 - ) {
2390 - performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
2391 - findHighestPriorityRoot();
2392 - }
2393 -
2394 - // We're done flushing work. Either we ran out of time in this callback,
2395 - // or there's no more work left with sufficient priority.
2396 -
2397 - // If there's work left over, schedule a new callback.
2398 - if (nextFlushedExpirationTime !== NoWork) {
2399 - scheduleCallbackWithExpirationTime(
2400 - ((nextFlushedRoot: any): FiberRoot),
2401 - nextFlushedExpirationTime,
2402 - );
2403 - }
2404 -
2405 - // Clean-up.
2406 - finishRendering();
2407 -}
2408 -
2409 -function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
2410 - invariant(
2411 - !isRendering,
2412 - 'work.commit(): Cannot commit while already rendering. This likely ' +
2413 - 'means you attempted to commit from inside a lifecycle method.',
2414 - );
2415 - // Perform work on root as if the given expiration time is the current time.
2416 - // This has the effect of synchronously flushing all work up to and
2417 - // including the given time.
2418 - nextFlushedRoot = root;
2419 - nextFlushedExpirationTime = expirationTime;
2420 - performWorkOnRoot(root, expirationTime, false);
2421 - // Flush any sync work that was scheduled by lifecycles
2422 - performSyncWork();
2423 -}
2424 -
2425 -function finishRendering() {
2426 - nestedUpdateCount = 0;
2427 - lastCommittedRootDuringThisBatch = null;
2428 -
2429 - if (__DEV__) {
2430 - if (rootWithPendingPassiveEffects === null) {
2431 - nestedPassiveEffectCountDEV = 0;
2432 - }
2433 - }
2434 -
2435 - if (completedBatches !== null) {
2436 - const batches = completedBatches;
2437 - completedBatches = null;
2438 - for (let i = 0; i < batches.length; i++) {
2439 - const batch = batches[i];
2440 - try {
2441 - batch._onComplete();
2442 - } catch (error) {
2443 - if (!hasUnhandledError) {
2444 - hasUnhandledError = true;
2445 - unhandledError = error;
2446 - }
2447 - }
2448 - }
2449 - }
2450 -
2451 - if (hasUnhandledError) {
2452 - const error = unhandledError;
2453 - unhandledError = null;
2454 - hasUnhandledError = false;
2455 - throw error;
2456 - }
2457 -}
2458 -
2459 -function performWorkOnRoot(
2460 - root: FiberRoot,
2461 - expirationTime: ExpirationTime,
2462 - isYieldy: boolean,
2463 -) {
2464 - invariant(
2465 - !isRendering,
2466 - 'performWorkOnRoot was called recursively. This error is likely caused ' +
2467 - 'by a bug in React. Please file an issue.',
2468 - );
2469 -
2470 - isRendering = true;
2471 -
2472 - // Check if this is async work or sync/expired work.
2473 - if (!isYieldy) {
2474 - // Flush work without yielding.
2475 - // TODO: Non-yieldy work does not necessarily imply expired work. A renderer
2476 - // may want to perform some work without yielding, but also without
2477 - // requiring the root to complete (by triggering placeholders).
2478 -
2479 - let finishedWork = root.finishedWork;
2480 - if (finishedWork !== null) {
2481 - // This root is already complete. We can commit it.
2482 - completeRoot(root, finishedWork, expirationTime);
2483 - } else {
2484 - root.finishedWork = null;
2485 - // If this root previously suspended, clear its existing timeout, since
2486 - // we're about to try rendering again.
2487 - const timeoutHandle = root.timeoutHandle;
2488 - if (timeoutHandle !== noTimeout) {
2489 - root.timeoutHandle = noTimeout;
2490 - // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
2491 - cancelTimeout(timeoutHandle);
2492 - }
2493 - renderRoot(root, isYieldy);
2494 - finishedWork = root.finishedWork;
2495 - if (finishedWork !== null) {
2496 - // We've completed the root. Commit it.
2497 - completeRoot(root, finishedWork, expirationTime);
2498 - }
2499 - }
2500 - } else {
2501 - // Flush async work.
2502 - let finishedWork = root.finishedWork;
2503 - if (finishedWork !== null) {
2504 - // This root is already complete. We can commit it.
2505 - completeRoot(root, finishedWork, expirationTime);
2506 - } else {
2507 - root.finishedWork = null;
2508 - // If this root previously suspended, clear its existing timeout, since
2509 - // we're about to try rendering again.
2510 - const timeoutHandle = root.timeoutHandle;
2511 - if (timeoutHandle !== noTimeout) {
2512 - root.timeoutHandle = noTimeout;
2513 - // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
2514 - cancelTimeout(timeoutHandle);
2515 - }
2516 - renderRoot(root, isYieldy);
2517 - finishedWork = root.finishedWork;
2518 - if (finishedWork !== null) {
2519 - // We've completed the root. Check the if we should yield one more time
2520 - // before committing.
2521 - if (!shouldYield()) {
2522 - // Still time left. Commit the root.
2523 - completeRoot(root, finishedWork, expirationTime);
2524 - } else {
2525 - // There's no time left. Mark this root as complete. We'll come
2526 - // back and commit it later.
2527 - root.finishedWork = finishedWork;
2528 - }
2529 - }
2530 - }
2531 - }
2532 -
2533 - isRendering = false;
2534 -}
2535 -
2536 -function completeRoot(
2537 - root: FiberRoot,
2538 - finishedWork: Fiber,
2539 - expirationTime: ExpirationTime,
2540 -): void {
2541 - // Check if there's a batch that matches this expiration time.
2542 - const firstBatch = root.firstBatch;
2543 - if (firstBatch !== null && firstBatch._expirationTime >= expirationTime) {
2544 - if (completedBatches === null) {
2545 - completedBatches = [firstBatch];
2546 - } else {
2547 - completedBatches.push(firstBatch);
2548 - }
2549 - if (firstBatch._defer) {
2550 - // This root is blocked from committing by a batch. Unschedule it until
2551 - // we receive another update.
2552 - root.finishedWork = finishedWork;
2553 - root.expirationTime = NoWork;
2554 - return;
2555 - }
2556 - }
2557 -
2558 - // Commit the root.
2559 - root.finishedWork = null;
2560 -
2561 - // Check if this is a nested update (a sync update scheduled during the
2562 - // commit phase).
2563 - if (root === lastCommittedRootDuringThisBatch) {
2564 - // If the next root is the same as the previous root, this is a nested
2565 - // update. To prevent an infinite loop, increment the nested update count.
2566 - nestedUpdateCount++;
2567 - } else {
2568 - // Reset whenever we switch roots.
2569 - lastCommittedRootDuringThisBatch = root;
2570 - nestedUpdateCount = 0;
2571 - }
2572 - commitRoot(root, finishedWork);
2573 -}
2574 -
2575 -function onUncaughtError(error: mixed) {
2576 - invariant(
2577 - nextFlushedRoot !== null,
2578 - 'Should be working on a root. This error is likely caused by a bug in ' +
2579 - 'React. Please file an issue.',
2580 - );
2581 - // Unschedule this root so we don't work on it again until there's
2582 - // another update.
2583 - nextFlushedRoot.expirationTime = NoWork;
2584 - if (!hasUnhandledError) {
2585 - hasUnhandledError = true;
2586 - unhandledError = error;
2587 - }
2588 -}
2589 -
2590 -// TODO: Batching should be implemented at the renderer level, not inside
2591 -// the reconciler.
2592 -function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
2593 - const previousIsBatchingUpdates = isBatchingUpdates;
2594 - isBatchingUpdates = true;
2595 - try {
2596 - return fn(a);
2597 - } finally {
2598 - isBatchingUpdates = previousIsBatchingUpdates;
2599 - if (!isBatchingUpdates && !isRendering) {
2600 - performSyncWork();
2601 - }
2602 - }
2603 -}
2604 -
2605 -// TODO: Batching should be implemented at the renderer level, not inside
2606 -// the reconciler.
2607 -function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
2608 - if (isBatchingUpdates && !isUnbatchingUpdates) {
2609 - isUnbatchingUpdates = true;
2610 - try {
2611 - return fn(a);
2612 - } finally {
2613 - isUnbatchingUpdates = false;
2614 - }
2615 - }
2616 - return fn(a);
2617 -}
2618 -
2619 -// TODO: Batching should be implemented at the renderer level, not within
2620 -// the reconciler.
2621 -function flushSync<A, R>(fn: (a: A) => R, a: A): R {
2622 - invariant(
2623 - !isRendering,
2624 - 'flushSync was called from inside a lifecycle method. It cannot be ' +
2625 - 'called when React is already rendering.',
2626 - );
2627 - const previousIsBatchingUpdates = isBatchingUpdates;
2628 - isBatchingUpdates = true;
2629 - try {
2630 - return syncUpdates(fn, a);
2631 - } finally {
2632 - isBatchingUpdates = previousIsBatchingUpdates;
2633 - performSyncWork();
2634 - }
2635 -}
2636 -
2637 -function interactiveUpdates<A, B, C, R>(
2638 - fn: (A, B, C) => R,
2639 - a: A,
2640 - b: B,
2641 - c: C,
2642 -): R {
2643 - if (isBatchingInteractiveUpdates) {
2644 - return fn(a, b, c);
2645 - }
2646 - // If there are any pending interactive updates, synchronously flush them.
2647 - // This needs to happen before we read any handlers, because the effect of
2648 - // the previous event may influence which handlers are called during
2649 - // this event.
2650 - if (
2651 - !isBatchingUpdates &&
2652 - !isRendering &&
2653 - lowestPriorityPendingInteractiveExpirationTime !== NoWork
2654 - ) {
2655 - // Synchronously flush pending interactive updates.
2656 - performWork(lowestPriorityPendingInteractiveExpirationTime);
2657 - lowestPriorityPendingInteractiveExpirationTime = NoWork;
2658 - }
2659 - const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
2660 - const previousIsBatchingUpdates = isBatchingUpdates;
2661 - isBatchingInteractiveUpdates = true;
2662 - isBatchingUpdates = true;
2663 - try {
2664 - return fn(a, b, c);
2665 - } finally {
2666 - isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
2667 - isBatchingUpdates = previousIsBatchingUpdates;
2668 - if (!isBatchingUpdates && !isRendering) {
2669 - performSyncWork();
2670 - }
2671 - }
2672 -}
2673 -
2674 -function flushInteractiveUpdates() {
2675 - if (
2676 - !isRendering &&
2677 - lowestPriorityPendingInteractiveExpirationTime !== NoWork
2678 - ) {
2679 - // Synchronously flush pending interactive updates.
2680 - performWork(lowestPriorityPendingInteractiveExpirationTime);
2681 - lowestPriorityPendingInteractiveExpirationTime = NoWork;
2682 - }
2683 -}
2684 -
2685 -function flushControlled(fn: () => mixed): void {
2686 - const previousIsBatchingUpdates = isBatchingUpdates;
2687 - isBatchingUpdates = true;
2688 - try {
2689 - syncUpdates(fn);
2690 - } finally {
2691 - isBatchingUpdates = previousIsBatchingUpdates;
2692 - if (!isBatchingUpdates && !isRendering) {
2693 - performSyncWork();
2694 - }
2695 - }
2696 -}
2697 -
2698 -export {
2699 - requestCurrentTime,
2700 - computeExpirationForFiber,
2701 - captureCommitPhaseError,
2702 - onUncaughtError,
2703 - markRenderEventTime,
2704 - renderDidSuspend,
2705 - renderDidError,
2706 - pingSuspendedRoot,
2707 - retryTimedOutBoundary,
2708 - resolveRetryThenable,
2709 - markLegacyErrorBoundaryAsFailed,
2710 - isAlreadyFailedLegacyErrorBoundary,
2711 - scheduleWork,
2712 - flushRoot,
2713 - batchedUpdates,
2714 - unbatchedUpdates,
2715 - flushSync,
2716 - flushControlled,
2717 - deferredUpdates,
2718 - syncUpdates,
2719 - interactiveUpdates,
2720 - flushInteractiveUpdates,
2721 - computeUniqueAsyncExpiration,
2722 - flushPassiveEffects,
2723 -};
packages/react-reconciler/src/SchedulerWithReactIntegration.js
+19 -2
@@ -10,8 +10,11 @@
10 // Intentionally not named imports because Rollup would use dynamic dispatch for
11 // CommonJS interop named imports.
12 import * as Scheduler from 'scheduler';
13 -
14 -import {disableYielding} from 'shared/ReactFeatureFlags';
13 +import {__interactionsRef} from 'scheduler/tracing';
14 +import {
15 + disableYielding,
16 + enableSchedulerTracing,
17 +} from 'shared/ReactFeatureFlags';
18 import invariant from 'shared/invariant';
19
20 const {
@@ -28,6 +31,20 @@ const {
31 unstable_IdlePriority: Scheduler_IdlePriority,
32 } = Scheduler;
33
34 +if (enableSchedulerTracing) {
35 + // Provide explicit error message when production+profiling bundle of e.g.
36 + // react-dom is used with production (non-profiling) bundle of
37 + // scheduler/tracing
38 + invariant(
39 + __interactionsRef != null && __interactionsRef.current != null,
40 + 'It is not supported to run the profiling version of a renderer (for ' +
41 + 'example, `react-dom/profiling`) without also replacing the ' +
42 + '`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' +
43 + 'bundler might have a setting for aliasing both modules. Learn more at ' +
44 + 'http://fb.me/react-profiling',
45 + );
46 +}
47 +
48 export opaque type ReactPriorityLevel = 99 | 98 | 97 | 96 | 95 | 90;
49 export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;
50
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+9 -19
@@ -15,7 +15,6 @@ let ReactFeatureFlags;
15 let React;
16 let ReactNoop;
17 let Scheduler;
18 -let enableNewScheduler;
18
19 describe('ReactIncrementalErrorHandling', () => {
20 beforeEach(() => {
@@ -23,7 +22,6 @@ describe('ReactIncrementalErrorHandling', () => {
22 ReactFeatureFlags = require('shared/ReactFeatureFlags');
23 ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
24 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
26 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
25 PropTypes = require('prop-types');
26 React = require('react');
27 ReactNoop = require('react-noop-renderer');
@@ -1034,23 +1032,15 @@ describe('ReactIncrementalErrorHandling', () => {
1032 ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
1033 ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
1034
1037 - if (enableNewScheduler) {
1038 - // The new scheduler will throw all three errors.
1039 - expect(() => {
1040 - expect(Scheduler).toFlushWithoutYielding();
1041 - }).toThrow('a');
1042 - expect(() => {
1043 - expect(Scheduler).toFlushWithoutYielding();
1044 - }).toThrow('c');
1045 - expect(() => {
1046 - expect(Scheduler).toFlushWithoutYielding();
1047 - }).toThrow('e');
1048 - } else {
1049 - // The old scheduler only throws the first one.
1050 - expect(() => {
1051 - expect(Scheduler).toFlushWithoutYielding();
1052 - }).toThrow('a');
1053 - }
1035 + expect(() => {
1036 + expect(Scheduler).toFlushWithoutYielding();
1037 + }).toThrow('a');
1038 + expect(() => {
1039 + expect(Scheduler).toFlushWithoutYielding();
1040 + }).toThrow('c');
1041 + expect(() => {
1042 + expect(Scheduler).toFlushWithoutYielding();
1043 + }).toThrow('e');
1044
1045 expect(Scheduler).toFlushWithoutYielding();
1046 expect(ReactNoop.getChildren('a')).toEqual([]);
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+479 -493
@@ -118,568 +118,554 @@ describe('ReactDebugFiberPerf', () => {
118 return <div>{props.children}</div>;
119 }
120
121 - describe('old scheduler', () => {
122 - runTests(false);
121 + beforeEach(() => {
122 + jest.resetModules();
123 + resetFlamechart();
124 + global.performance = createUserTimingPolyfill();
125 +
126 + require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
127 + require('shared/ReactFeatureFlags').enableProfilerTimer = false;
128 + require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
129 + require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
130 +
131 + // Import after the polyfill is set up:
132 + React = require('react');
133 + ReactNoop = require('react-noop-renderer');
134 + Scheduler = require('scheduler');
135 + PropTypes = require('prop-types');
136 });
137
125 - describe('new scheduler', () => {
126 - runTests(true);
138 + afterEach(() => {
139 + delete global.performance;
140 });
141
129 - function runTests(enableNewScheduler) {
130 - beforeEach(() => {
131 - jest.resetModules();
132 - resetFlamechart();
133 - global.performance = createUserTimingPolyfill();
134 -
135 - require('shared/ReactFeatureFlags').enableNewScheduler = enableNewScheduler;
136 - require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
137 - require('shared/ReactFeatureFlags').enableProfilerTimer = false;
138 - require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
139 - require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
140 -
141 - // Import after the polyfill is set up:
142 - React = require('react');
143 - ReactNoop = require('react-noop-renderer');
144 - Scheduler = require('scheduler');
145 - PropTypes = require('prop-types');
146 - });
142 + it('measures a simple reconciliation', () => {
143 + ReactNoop.render(
144 + <Parent>
145 + <Child />
146 + </Parent>,
147 + );
148 + addComment('Mount');
149 + expect(Scheduler).toFlushWithoutYielding();
150 +
151 + ReactNoop.render(
152 + <Parent>
153 + <Child />
154 + </Parent>,
155 + );
156 + addComment('Update');
157 + expect(Scheduler).toFlushWithoutYielding();
158 +
159 + ReactNoop.render(null);
160 + addComment('Unmount');
161 + expect(Scheduler).toFlushWithoutYielding();
162 +
163 + expect(getFlameChart()).toMatchSnapshot();
164 + });
165
148 - afterEach(() => {
149 - delete global.performance;
166 + it('properly displays the forwardRef component in measurements', () => {
167 + const AnonymousForwardRef = React.forwardRef((props, ref) => (
168 + <Child {...props} ref={ref} />
169 + ));
170 + const NamedForwardRef = React.forwardRef(function refForwarder(props, ref) {
171 + return <Child {...props} ref={ref} />;
172 });
173 + function notImportant(props, ref) {
174 + return <Child {...props} ref={ref} />;
175 + }
176 + notImportant.displayName = 'OverriddenName';
177 + const DisplayNamedForwardRef = React.forwardRef(notImportant);
178 +
179 + ReactNoop.render(
180 + <Parent>
181 + <AnonymousForwardRef />
182 + <NamedForwardRef />
183 + <DisplayNamedForwardRef />
184 + </Parent>,
185 + );
186 + addComment('Mount');
187 + expect(Scheduler).toFlushWithoutYielding();
188 +
189 + expect(getFlameChart()).toMatchSnapshot();
190 + });
191
152 - it('measures a simple reconciliation', () => {
153 - ReactNoop.render(
154 - <Parent>
155 - <Child />
156 - </Parent>,
157 - );
158 - addComment('Mount');
159 - expect(Scheduler).toFlushWithoutYielding();
160 -
161 - ReactNoop.render(
162 - <Parent>
163 - <Child />
164 - </Parent>,
165 - );
166 - addComment('Update');
167 - expect(Scheduler).toFlushWithoutYielding();
168 -
169 - ReactNoop.render(null);
170 - addComment('Unmount');
171 - expect(Scheduler).toFlushWithoutYielding();
192 + it('does not include ConcurrentMode, StrictMode, or Profiler components in measurements', () => {
193 + ReactNoop.render(
194 + <React.Profiler id="test" onRender={jest.fn()}>
195 + <React.StrictMode>
196 + <Parent>
197 + <React.unstable_ConcurrentMode>
198 + <Child />
199 + </React.unstable_ConcurrentMode>
200 + </Parent>
201 + </React.StrictMode>
202 + </React.Profiler>,
203 + );
204 + addComment('Mount');
205 + expect(Scheduler).toFlushWithoutYielding();
206
173 - expect(getFlameChart()).toMatchSnapshot();
174 - });
207 + expect(getFlameChart()).toMatchSnapshot();
208 + });
209
176 - it('properly displays the forwardRef component in measurements', () => {
177 - const AnonymousForwardRef = React.forwardRef((props, ref) => (
178 - <Child {...props} ref={ref} />
179 - ));
180 - const NamedForwardRef = React.forwardRef(function refForwarder(
181 - props,
182 - ref,
183 - ) {
184 - return <Child {...props} ref={ref} />;
185 - });
186 - function notImportant(props, ref) {
187 - return <Child {...props} ref={ref} />;
188 - }
189 - notImportant.displayName = 'OverriddenName';
190 - const DisplayNamedForwardRef = React.forwardRef(notImportant);
210 + it('does not include context provider or consumer in measurements', () => {
211 + const {Consumer, Provider} = React.createContext(true);
212
192 - ReactNoop.render(
213 + ReactNoop.render(
214 + <Provider value={false}>
215 <Parent>
194 - <AnonymousForwardRef />
195 - <NamedForwardRef />
196 - <DisplayNamedForwardRef />
197 - </Parent>,
198 - );
199 - addComment('Mount');
200 - expect(Scheduler).toFlushWithoutYielding();
201 -
202 - expect(getFlameChart()).toMatchSnapshot();
203 - });
204 -
205 - it('does not include ConcurrentMode, StrictMode, or Profiler components in measurements', () => {
206 - ReactNoop.render(
207 - <React.Profiler id="test" onRender={jest.fn()}>
208 - <React.StrictMode>
209 - <Parent>
210 - <React.unstable_ConcurrentMode>
211 - <Child />
212 - </React.unstable_ConcurrentMode>
213 - </Parent>
214 - </React.StrictMode>
215 - </React.Profiler>,
216 - );
217 - addComment('Mount');
218 - expect(Scheduler).toFlushWithoutYielding();
219 -
220 - expect(getFlameChart()).toMatchSnapshot();
221 - });
222 -
223 - it('does not include context provider or consumer in measurements', () => {
224 - const {Consumer, Provider} = React.createContext(true);
225 -
226 - ReactNoop.render(
227 - <Provider value={false}>
228 - <Parent>
229 - <Consumer>{value => <Child value={value} />}</Consumer>
230 - </Parent>
231 - </Provider>,
232 - );
233 - addComment('Mount');
234 - expect(Scheduler).toFlushWithoutYielding();
235 -
236 - expect(getFlameChart()).toMatchSnapshot();
237 - });
216 + <Consumer>{value => <Child value={value} />}</Consumer>
217 + </Parent>
218 + </Provider>,
219 + );
220 + addComment('Mount');
221 + expect(Scheduler).toFlushWithoutYielding();
222 +
223 + expect(getFlameChart()).toMatchSnapshot();
224 + });
225
239 - it('skips parents during setState', () => {
240 - class A extends React.Component {
241 - render() {
242 - return <div>{this.props.children}</div>;
243 - }
226 + it('skips parents during setState', () => {
227 + class A extends React.Component {
228 + render() {
229 + return <div>{this.props.children}</div>;
230 }
231 + }
232
246 - class B extends React.Component {
247 - render() {
248 - return <div>{this.props.children}</div>;
249 - }
233 + class B extends React.Component {
234 + render() {
235 + return <div>{this.props.children}</div>;
236 }
237 + }
238
252 - let a;
253 - let b;
254 - ReactNoop.render(
239 + let a;
240 + let b;
241 + ReactNoop.render(
242 + <Parent>
243 <Parent>
244 <Parent>
257 - <Parent>
258 - <A ref={inst => (a = inst)} />
259 - </Parent>
260 - </Parent>
261 - <Parent>
262 - <B ref={inst => (b = inst)} />
245 + <A ref={inst => (a = inst)} />
246 </Parent>
264 - </Parent>,
265 - );
266 - expect(Scheduler).toFlushWithoutYielding();
267 - resetFlamechart();
268 -
269 - a.setState({});
270 - b.setState({});
271 - addComment('Should include just A and B, no Parents');
272 - expect(Scheduler).toFlushWithoutYielding();
273 - expect(getFlameChart()).toMatchSnapshot();
274 - });
275 -
276 - it('warns on cascading renders from setState', () => {
277 - class Cascading extends React.Component {
278 - componentDidMount() {
279 - this.setState({});
280 - }
281 - render() {
282 - return <div>{this.props.children}</div>;
283 - }
284 - }
285 -
286 - ReactNoop.render(
247 + </Parent>
248 <Parent>
288 - <Cascading />
289 - </Parent>,
290 - );
291 - addComment('Should print a warning');
292 - expect(Scheduler).toFlushWithoutYielding();
293 - expect(getFlameChart()).toMatchSnapshot();
294 - });
249 + <B ref={inst => (b = inst)} />
250 + </Parent>
251 + </Parent>,
252 + );
253 + expect(Scheduler).toFlushWithoutYielding();
254 + resetFlamechart();
255 +
256 + a.setState({});
257 + b.setState({});
258 + addComment('Should include just A and B, no Parents');
259 + expect(Scheduler).toFlushWithoutYielding();
260 + expect(getFlameChart()).toMatchSnapshot();
261 + });
262
296 - it('warns on cascading renders from top-level render', () => {
297 - class Cascading extends React.Component {
298 - componentDidMount() {
299 - ReactNoop.renderToRootWithID(<Child />, 'b');
300 - addComment('Scheduling another root from componentDidMount');
301 - }
302 - render() {
303 - return <div>{this.props.children}</div>;
304 - }
263 + it('warns on cascading renders from setState', () => {
264 + class Cascading extends React.Component {
265 + componentDidMount() {
266 + this.setState({});
267 }
306 -
307 - ReactNoop.renderToRootWithID(<Cascading />, 'a');
308 - addComment('Rendering the first root');
309 - expect(Scheduler).toFlushWithoutYielding();
310 - expect(getFlameChart()).toMatchSnapshot();
311 - });
312 -
313 - it('does not treat setState from cWM or cWRP as cascading', () => {
314 - class NotCascading extends React.Component {
315 - UNSAFE_componentWillMount() {
316 - this.setState({});
317 - }
318 - UNSAFE_componentWillReceiveProps() {
319 - this.setState({});
320 - }
321 - render() {
322 - return <div>{this.props.children}</div>;
323 - }
268 + render() {
269 + return <div>{this.props.children}</div>;
270 }
271 + }
272 +
273 + ReactNoop.render(
274 + <Parent>
275 + <Cascading />
276 + </Parent>,
277 + );
278 + addComment('Should print a warning');
279 + expect(Scheduler).toFlushWithoutYielding();
280 + expect(getFlameChart()).toMatchSnapshot();
281 + });
282
326 - ReactNoop.render(
327 - <Parent>
328 - <NotCascading />
329 - </Parent>,
330 - );
331 - addComment('Should not print a warning');
332 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
333 - [
334 - 'componentWillMount: Please update the following components ' +
335 - 'to use componentDidMount instead: NotCascading' +
336 - '\n\ncomponentWillReceiveProps: Please update the following components ' +
337 - 'to use static getDerivedStateFromProps instead: NotCascading',
338 - ],
339 - {withoutStack: true},
340 - );
341 - ReactNoop.render(
342 - <Parent>
343 - <NotCascading />
344 - </Parent>,
345 - );
346 - addComment('Should not print a warning');
347 - expect(Scheduler).toFlushWithoutYielding();
348 - expect(getFlameChart()).toMatchSnapshot();
349 - });
350 -
351 - it('captures all lifecycles', () => {
352 - class AllLifecycles extends React.Component {
353 - static childContextTypes = {
354 - foo: PropTypes.any,
355 - };
356 - shouldComponentUpdate() {
357 - return true;
358 - }
359 - getChildContext() {
360 - return {foo: 42};
361 - }
362 - UNSAFE_componentWillMount() {}
363 - componentDidMount() {}
364 - UNSAFE_componentWillReceiveProps() {}
365 - UNSAFE_componentWillUpdate() {}
366 - componentDidUpdate() {}
367 - componentWillUnmount() {}
368 - render() {
369 - return <div />;
370 - }
283 + it('warns on cascading renders from top-level render', () => {
284 + class Cascading extends React.Component {
285 + componentDidMount() {
286 + ReactNoop.renderToRootWithID(<Child />, 'b');
287 + addComment('Scheduling another root from componentDidMount');
288 }
372 - ReactNoop.render(<AllLifecycles />);
373 - addComment('Mount');
374 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
375 - [
376 - 'componentWillMount: Please update the following components ' +
377 - 'to use componentDidMount instead: AllLifecycles' +
378 - '\n\ncomponentWillReceiveProps: Please update the following components ' +
379 - 'to use static getDerivedStateFromProps instead: AllLifecycles' +
380 - '\n\ncomponentWillUpdate: Please update the following components ' +
381 - 'to use componentDidUpdate instead: AllLifecycles',
382 - 'Legacy context API has been detected within a strict-mode tree: \n\n' +
383 - 'Please update the following components: AllLifecycles',
384 - ],
385 - {withoutStack: true},
386 - );
387 - ReactNoop.render(<AllLifecycles />);
388 - addComment('Update');
389 - expect(Scheduler).toFlushWithoutYielding();
390 - ReactNoop.render(null);
391 - addComment('Unmount');
392 - expect(Scheduler).toFlushWithoutYielding();
393 - expect(getFlameChart()).toMatchSnapshot();
394 - });
289 + render() {
290 + return <div>{this.props.children}</div>;
291 + }
292 + }
293
396 - it('measures deprioritized work', () => {
397 - addComment('Flush the parent');
398 - ReactNoop.flushSync(() => {
399 - ReactNoop.render(
400 - <Parent>
401 - <div hidden={true}>
402 - <Child />
403 - </div>
404 - </Parent>,
405 - );
406 - });
407 - addComment('Flush the child');
408 - expect(Scheduler).toFlushWithoutYielding();
409 - expect(getFlameChart()).toMatchSnapshot();
410 - });
294 + ReactNoop.renderToRootWithID(<Cascading />, 'a');
295 + addComment('Rendering the first root');
296 + expect(Scheduler).toFlushWithoutYielding();
297 + expect(getFlameChart()).toMatchSnapshot();
298 + });
299
412 - it('measures deferred work in chunks', () => {
413 - class A extends React.Component {
414 - render() {
415 - Scheduler.yieldValue('A');
416 - return <div>{this.props.children}</div>;
417 - }
300 + it('does not treat setState from cWM or cWRP as cascading', () => {
301 + class NotCascading extends React.Component {
302 + UNSAFE_componentWillMount() {
303 + this.setState({});
304 }
419 -
420 - class B extends React.Component {
421 - render() {
422 - Scheduler.yieldValue('B');
423 - return <div>{this.props.children}</div>;
424 - }
305 + UNSAFE_componentWillReceiveProps() {
306 + this.setState({});
307 }
308 + render() {
309 + return <div>{this.props.children}</div>;
310 + }
311 + }
312 +
313 + ReactNoop.render(
314 + <Parent>
315 + <NotCascading />
316 + </Parent>,
317 + );
318 + addComment('Should not print a warning');
319 + expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
320 + [
321 + 'componentWillMount: Please update the following components ' +
322 + 'to use componentDidMount instead: NotCascading' +
323 + '\n\ncomponentWillReceiveProps: Please update the following components ' +
324 + 'to use static getDerivedStateFromProps instead: NotCascading',
325 + ],
326 + {withoutStack: true},
327 + );
328 + ReactNoop.render(
329 + <Parent>
330 + <NotCascading />
331 + </Parent>,
332 + );
333 + addComment('Should not print a warning');
334 + expect(Scheduler).toFlushWithoutYielding();
335 + expect(getFlameChart()).toMatchSnapshot();
336 + });
337
427 - class C extends React.Component {
428 - render() {
429 - Scheduler.yieldValue('C');
430 - return <div>{this.props.children}</div>;
431 - }
338 + it('captures all lifecycles', () => {
339 + class AllLifecycles extends React.Component {
340 + static childContextTypes = {
341 + foo: PropTypes.any,
342 + };
343 + shouldComponentUpdate() {
344 + return true;
345 }
346 + getChildContext() {
347 + return {foo: 42};
348 + }
349 + UNSAFE_componentWillMount() {}
350 + componentDidMount() {}
351 + UNSAFE_componentWillReceiveProps() {}
352 + UNSAFE_componentWillUpdate() {}
353 + componentDidUpdate() {}
354 + componentWillUnmount() {}
355 + render() {
356 + return <div />;
357 + }
358 + }
359 + ReactNoop.render(<AllLifecycles />);
360 + addComment('Mount');
361 + expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
362 + [
363 + 'componentWillMount: Please update the following components ' +
364 + 'to use componentDidMount instead: AllLifecycles' +
365 + '\n\ncomponentWillReceiveProps: Please update the following components ' +
366 + 'to use static getDerivedStateFromProps instead: AllLifecycles' +
367 + '\n\ncomponentWillUpdate: Please update the following components ' +
368 + 'to use componentDidUpdate instead: AllLifecycles',
369 + 'Legacy context API has been detected within a strict-mode tree: \n\n' +
370 + 'Please update the following components: AllLifecycles',
371 + ],
372 + {withoutStack: true},
373 + );
374 + ReactNoop.render(<AllLifecycles />);
375 + addComment('Update');
376 + expect(Scheduler).toFlushWithoutYielding();
377 + ReactNoop.render(null);
378 + addComment('Unmount');
379 + expect(Scheduler).toFlushWithoutYielding();
380 + expect(getFlameChart()).toMatchSnapshot();
381 + });
382
383 + it('measures deprioritized work', () => {
384 + addComment('Flush the parent');
385 + ReactNoop.flushSync(() => {
386 ReactNoop.render(
387 <Parent>
436 - <A>
388 + <div hidden={true}>
389 <Child />
438 - </A>
439 - <B>
440 - <Child />
441 - </B>
442 - <C>
443 - <Child />
444 - </C>
390 + </div>
391 </Parent>,
392 );
447 - addComment('Start rendering through B');
448 - expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
449 - addComment('Complete the rest');
450 - expect(Scheduler).toFlushAndYield(['C']);
451 - expect(getFlameChart()).toMatchSnapshot();
393 });
394 + addComment('Flush the child');
395 + expect(Scheduler).toFlushWithoutYielding();
396 + expect(getFlameChart()).toMatchSnapshot();
397 + });
398
454 - it('recovers from fatal errors', () => {
455 - function Baddie() {
456 - throw new Error('Game over');
457 - }
458 -
459 - ReactNoop.render(
460 - <Parent>
461 - <Baddie />
462 - </Parent>,
463 - );
464 - try {
465 - addComment('Will fatal');
466 - expect(Scheduler).toFlushWithoutYielding();
467 - } catch (err) {
468 - expect(err.message).toBe('Game over');
399 + it('measures deferred work in chunks', () => {
400 + class A extends React.Component {
401 + render() {
402 + Scheduler.yieldValue('A');
403 + return <div>{this.props.children}</div>;
404 }
470 - ReactNoop.render(
471 - <Parent>
472 - <Child />
473 - </Parent>,
474 - );
475 - addComment('Will reconcile from a clean state');
476 - expect(Scheduler).toFlushWithoutYielding();
477 - expect(getFlameChart()).toMatchSnapshot();
478 - });
405 + }
406
480 - it('recovers from caught errors', () => {
481 - function Baddie() {
482 - throw new Error('Game over');
407 + class B extends React.Component {
408 + render() {
409 + Scheduler.yieldValue('B');
410 + return <div>{this.props.children}</div>;
411 }
412 + }
413
485 - function ErrorReport() {
486 - return <div />;
414 + class C extends React.Component {
415 + render() {
416 + Scheduler.yieldValue('C');
417 + return <div>{this.props.children}</div>;
418 }
419 + }
420
489 - class Boundary extends React.Component {
490 - state = {error: null};
491 - componentDidCatch(error) {
492 - this.setState({error});
493 - }
494 - render() {
495 - if (this.state.error) {
496 - return <ErrorReport />;
497 - }
498 - return this.props.children;
499 - }
500 - }
421 + ReactNoop.render(
422 + <Parent>
423 + <A>
424 + <Child />
425 + </A>
426 + <B>
427 + <Child />
428 + </B>
429 + <C>
430 + <Child />
431 + </C>
432 + </Parent>,
433 + );
434 + addComment('Start rendering through B');
435 + expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
436 + addComment('Complete the rest');
437 + expect(Scheduler).toFlushAndYield(['C']);
438 + expect(getFlameChart()).toMatchSnapshot();
439 + });
440
502 - ReactNoop.render(
503 - <Parent>
504 - <Boundary>
505 - <Parent>
506 - <Baddie />
507 - </Parent>
508 - </Boundary>
509 - </Parent>,
510 - );
511 - addComment('Stop on Baddie and restart from Boundary');
441 + it('recovers from fatal errors', () => {
442 + function Baddie() {
443 + throw new Error('Game over');
444 + }
445 +
446 + ReactNoop.render(
447 + <Parent>
448 + <Baddie />
449 + </Parent>,
450 + );
451 + try {
452 + addComment('Will fatal');
453 expect(Scheduler).toFlushWithoutYielding();
513 - expect(getFlameChart()).toMatchSnapshot();
514 - });
454 + } catch (err) {
455 + expect(err.message).toBe('Game over');
456 + }
457 + ReactNoop.render(
458 + <Parent>
459 + <Child />
460 + </Parent>,
461 + );
462 + addComment('Will reconcile from a clean state');
463 + expect(Scheduler).toFlushWithoutYielding();
464 + expect(getFlameChart()).toMatchSnapshot();
465 + });
466
516 - it('deduplicates lifecycle names during commit to reduce overhead', () => {
517 - class A extends React.Component {
518 - componentDidUpdate() {}
519 - render() {
520 - return <div />;
521 - }
522 - }
467 + it('recovers from caught errors', () => {
468 + function Baddie() {
469 + throw new Error('Game over');
470 + }
471
524 - class B extends React.Component {
525 - componentDidUpdate(prevProps) {
526 - if (this.props.cascade && !prevProps.cascade) {
527 - this.setState({});
528 - }
529 - }
530 - render() {
531 - return <div />;
472 + function ErrorReport() {
473 + return <div />;
474 + }
475 +
476 + class Boundary extends React.Component {
477 + state = {error: null};
478 + componentDidCatch(error) {
479 + this.setState({error});
480 + }
481 + render() {
482 + if (this.state.error) {
483 + return <ErrorReport />;
484 }
485 + return this.props.children;
486 }
487 + }
488
535 - ReactNoop.render(
536 - <Parent>
537 - <A />
538 - <B />
539 - <A />
540 - <B />
541 - </Parent>,
542 - );
543 - expect(Scheduler).toFlushWithoutYielding();
544 - resetFlamechart();
545 -
546 - ReactNoop.render(
547 - <Parent>
548 - <A />
549 - <B />
550 - <A />
551 - <B />
552 - </Parent>,
553 - );
554 - addComment('The commit phase should mention A and B just once');
555 - expect(Scheduler).toFlushWithoutYielding();
556 - ReactNoop.render(
557 - <Parent>
558 - <A />
559 - <B />
560 - <A />
561 - <B cascade={true} />
562 - </Parent>,
563 - );
564 - addComment("Because of deduplication, we don't know B was cascading,");
565 - addComment('but we should still see the warning for the commit phase.');
566 - expect(Scheduler).toFlushWithoutYielding();
567 - expect(getFlameChart()).toMatchSnapshot();
568 - });
569 -
570 - it('supports portals', () => {
571 - const portalContainer = ReactNoop.getOrCreateRootContainer(
572 - 'portalContainer',
573 - );
574 - ReactNoop.render(
575 - <Parent>
576 - {ReactNoop.createPortal(<Child />, portalContainer, null)}
577 - </Parent>,
578 - );
579 - expect(Scheduler).toFlushWithoutYielding();
580 - expect(getFlameChart()).toMatchSnapshot();
581 - });
489 + ReactNoop.render(
490 + <Parent>
491 + <Boundary>
492 + <Parent>
493 + <Baddie />
494 + </Parent>
495 + </Boundary>
496 + </Parent>,
497 + );
498 + addComment('Stop on Baddie and restart from Boundary');
499 + expect(Scheduler).toFlushWithoutYielding();
500 + expect(getFlameChart()).toMatchSnapshot();
501 + });
502
583 - it('supports memo', () => {
584 - const MemoFoo = React.memo(function Foo() {
503 + it('deduplicates lifecycle names during commit to reduce overhead', () => {
504 + class A extends React.Component {
505 + componentDidUpdate() {}
506 + render() {
507 return <div />;
586 - });
587 - ReactNoop.render(
588 - <Parent>
589 - <MemoFoo />
590 - </Parent>,
591 - );
592 - expect(Scheduler).toFlushWithoutYielding();
593 - expect(getFlameChart()).toMatchSnapshot();
594 - });
595 -
596 - it('supports Suspense and lazy', async () => {
597 - function Spinner() {
598 - return <span />;
508 }
509 + }
510
601 - function fakeImport(result) {
602 - return {default: result};
511 + class B extends React.Component {
512 + componentDidUpdate(prevProps) {
513 + if (this.props.cascade && !prevProps.cascade) {
514 + this.setState({});
515 + }
516 + }
517 + render() {
518 + return <div />;
519 }
520 + }
521 +
522 + ReactNoop.render(
523 + <Parent>
524 + <A />
525 + <B />
526 + <A />
527 + <B />
528 + </Parent>,
529 + );
530 + expect(Scheduler).toFlushWithoutYielding();
531 + resetFlamechart();
532 +
533 + ReactNoop.render(
534 + <Parent>
535 + <A />
536 + <B />
537 + <A />
538 + <B />
539 + </Parent>,
540 + );
541 + addComment('The commit phase should mention A and B just once');
542 + expect(Scheduler).toFlushWithoutYielding();
543 + ReactNoop.render(
544 + <Parent>
545 + <A />
546 + <B />
547 + <A />
548 + <B cascade={true} />
549 + </Parent>,
550 + );
551 + addComment("Because of deduplication, we don't know B was cascading,");
552 + addComment('but we should still see the warning for the commit phase.');
553 + expect(Scheduler).toFlushWithoutYielding();
554 + expect(getFlameChart()).toMatchSnapshot();
555 + });
556
605 - let resolve;
606 - const LazyFoo = React.lazy(
607 - () =>
608 - new Promise(r => {
609 - resolve = r;
610 - }),
611 - );
557 + it('supports portals', () => {
558 + const portalContainer = ReactNoop.getOrCreateRootContainer(
559 + 'portalContainer',
560 + );
561 + ReactNoop.render(
562 + <Parent>
563 + {ReactNoop.createPortal(<Child />, portalContainer, null)}
564 + </Parent>,
565 + );
566 + expect(Scheduler).toFlushWithoutYielding();
567 + expect(getFlameChart()).toMatchSnapshot();
568 + });
569
613 - ReactNoop.render(
614 - <Parent>
615 - <React.Suspense fallback={<Spinner />}>
616 - <LazyFoo />
617 - </React.Suspense>
618 - </Parent>,
619 - );
620 - expect(Scheduler).toFlushWithoutYielding();
621 - expect(getFlameChart()).toMatchSnapshot();
570 + it('supports memo', () => {
571 + const MemoFoo = React.memo(function Foo() {
572 + return <div />;
573 + });
574 + ReactNoop.render(
575 + <Parent>
576 + <MemoFoo />
577 + </Parent>,
578 + );
579 + expect(Scheduler).toFlushWithoutYielding();
580 + expect(getFlameChart()).toMatchSnapshot();
581 + });
582
623 - resolve(
624 - fakeImport(function Foo() {
625 - return <div />;
626 - }),
627 - );
583 + it('supports Suspense and lazy', async () => {
584 + function Spinner() {
585 + return <span />;
586 + }
587
629 - await Promise.resolve();
588 + function fakeImport(result) {
589 + return {default: result};
590 + }
591
631 - ReactNoop.render(
632 - <Parent>
633 - <React.Suspense>
634 - <LazyFoo />
635 - </React.Suspense>
636 - </Parent>,
637 - );
638 - expect(Scheduler).toFlushWithoutYielding();
639 - expect(getFlameChart()).toMatchSnapshot();
640 - });
592 + let resolve;
593 + const LazyFoo = React.lazy(
594 + () =>
595 + new Promise(r => {
596 + resolve = r;
597 + }),
598 + );
599 +
600 + ReactNoop.render(
601 + <Parent>
602 + <React.Suspense fallback={<Spinner />}>
603 + <LazyFoo />
604 + </React.Suspense>
605 + </Parent>,
606 + );
607 + expect(Scheduler).toFlushWithoutYielding();
608 + expect(getFlameChart()).toMatchSnapshot();
609 +
610 + resolve(
611 + fakeImport(function Foo() {
612 + return <div />;
613 + }),
614 + );
615 +
616 + await Promise.resolve();
617 +
618 + ReactNoop.render(
619 + <Parent>
620 + <React.Suspense>
621 + <LazyFoo />
622 + </React.Suspense>
623 + </Parent>,
624 + );
625 + expect(Scheduler).toFlushWithoutYielding();
626 + expect(getFlameChart()).toMatchSnapshot();
627 + });
628
642 - it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
643 - class Component extends React.Component {
644 - state = {step: 1};
645 - componentDidMount() {
646 - this.setState({step: 2});
647 - }
648 - render() {
649 - return <span prop={this.state.step} />;
650 - }
629 + it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
630 + class Component extends React.Component {
631 + state = {step: 1};
632 + componentDidMount() {
633 + this.setState({step: 2});
634 + }
635 + render() {
636 + return <span prop={this.state.step} />;
637 }
652 - ReactNoop.flushSync(() => {
653 - ReactNoop.render(<Component />);
654 - });
655 - expect(getFlameChart()).toMatchSnapshot();
638 + }
639 + ReactNoop.flushSync(() => {
640 + ReactNoop.render(<Component />);
641 });
642 + expect(getFlameChart()).toMatchSnapshot();
643 + });
644
658 - it('warns if an in-progress update is interrupted', () => {
659 - function Foo() {
660 - Scheduler.yieldValue('Foo');
661 - return <span />;
662 - }
645 + it('warns if an in-progress update is interrupted', () => {
646 + function Foo() {
647 + Scheduler.yieldValue('Foo');
648 + return <span />;
649 + }
650
651 + ReactNoop.render(<Foo />);
652 + ReactNoop.flushNextYield();
653 + ReactNoop.flushSync(() => {
654 ReactNoop.render(<Foo />);
665 - ReactNoop.flushNextYield();
666 - ReactNoop.flushSync(() => {
667 - ReactNoop.render(<Foo />);
668 - });
669 - expect(Scheduler).toHaveYielded(['Foo']);
670 - expect(Scheduler).toFlushWithoutYielding();
671 - expect(getFlameChart()).toMatchSnapshot();
655 });
656 + expect(Scheduler).toHaveYielded(['Foo']);
657 + expect(Scheduler).toFlushWithoutYielding();
658 + expect(getFlameChart()).toMatchSnapshot();
659 + });
660
674 - it('warns if async work expires (starvation)', () => {
675 - function Foo() {
676 - return <span />;
677 - }
661 + it('warns if async work expires (starvation)', () => {
662 + function Foo() {
663 + return <span />;
664 + }
665
679 - ReactNoop.render(<Foo />);
680 - ReactNoop.expire(6000);
681 - expect(Scheduler).toFlushWithoutYielding();
682 - expect(getFlameChart()).toMatchSnapshot();
683 - });
684 - }
666 + ReactNoop.render(<Foo />);
667 + ReactNoop.expire(6000);
668 + expect(Scheduler).toFlushWithoutYielding();
669 + expect(getFlameChart()).toMatchSnapshot();
670 + });
671 });
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+2 -22
@@ -5,7 +5,6 @@ let Scheduler;
5 let ReactFeatureFlags;
6 let Suspense;
7 let lazy;
8 -let enableNewScheduler;
8
9 describe('ReactLazy', () => {
10 beforeEach(() => {
@@ -19,7 +18,6 @@ describe('ReactLazy', () => {
18 lazy = React.lazy;
19 ReactTestRenderer = require('react-test-renderer');
20 Scheduler = require('scheduler');
22 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
21 });
22
23 function Text(props) {
@@ -487,13 +485,7 @@ describe('ReactLazy', () => {
485
486 await Promise.resolve();
487
490 - if (enableNewScheduler) {
491 - // The new scheduler pings in a separate task
492 - expect(Scheduler).toHaveYielded([]);
493 - } else {
494 - // The old scheduler pings synchronously
495 - expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A1']);
496 - }
488 + expect(Scheduler).toHaveYielded([]);
489
490 root.update(
491 <Suspense fallback={<Text text="Loading..." />}>
@@ -501,19 +493,7 @@ describe('ReactLazy', () => {
493 </Suspense>,
494 );
495
504 - if (enableNewScheduler) {
505 - // Because this ping happens in a new task, the ping and the update
506 - // are batched together
507 - expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A2']);
508 - } else {
509 - // The old scheduler must do two separate renders, no batching.
510 - expect(Scheduler).toHaveYielded([
511 - 'UNSAFE_componentWillReceiveProps: A -> A',
512 - 'UNSAFE_componentWillUpdate: A -> A',
513 - 'A2',
514 - ]);
515 - }
516 -
496 + expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A2']);
497 expect(root).toMatchRenderedOutput('A2');
498
499 root.update(
packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js
-1
@@ -26,7 +26,6 @@ describe('ReactSchedulerIntegration', () => {
26 jest.resetModules();
27 ReactFeatureFlags = require('shared/ReactFeatureFlags');
28 ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
29 - ReactFeatureFlags.enableNewScheduler = true;
29 React = require('react');
30 ReactNoop = require('react-noop-renderer');
31 Scheduler = require('scheduler');
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+69 -198
@@ -5,7 +5,6 @@ let Scheduler;
5 let ReactCache;
6 let Suspense;
7 let act;
8 -let enableNewScheduler;
8
9 let TextResource;
10 let textResourceShouldFail;
@@ -23,7 +22,6 @@ describe('ReactSuspense', () => {
22 act = ReactTestRenderer.act;
23 Scheduler = require('scheduler');
24 ReactCache = require('react-cache');
26 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
25
26 Suspense = React.Suspense;
27
@@ -267,11 +265,7 @@ describe('ReactSuspense', () => {
265
266 await LazyClass;
267
270 - if (enableNewScheduler) {
271 - expect(Scheduler).toFlushExpired(['Hi', 'Did mount: Hi']);
272 - } else {
273 - expect(Scheduler).toHaveYielded(['Hi', 'Did mount: Hi']);
274 - }
268 + expect(Scheduler).toFlushExpired(['Hi', 'Did mount: Hi']);
269 expect(root).toMatchRenderedOutput('Hi');
270 });
271
@@ -400,24 +394,13 @@ describe('ReactSuspense', () => {
394
395 jest.advanceTimersByTime(100);
396
403 - if (enableNewScheduler) {
404 - expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
405 - expect(Scheduler).toFlushExpired([
406 - 'B:1',
407 - 'Unmount [Loading...]',
408 - // Should be a mount, not an update
409 - 'Mount [B:1]',
410 - ]);
411 - } else {
412 - expect(Scheduler).toHaveYielded([
413 - 'Promise resolved [B:1]',
414 - 'B:1',
415 - 'Unmount [Loading...]',
416 - // Should be a mount, not an update
417 - 'Mount [B:1]',
418 - ]);
419 - }
420 -
397 + expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
398 + expect(Scheduler).toFlushExpired([
399 + 'B:1',
400 + 'Unmount [Loading...]',
401 + // Should be a mount, not an update
402 + 'Mount [B:1]',
403 + ]);
404 expect(root).toMatchRenderedOutput('AB:1C');
405
406 instance.setState({step: 2});
@@ -430,21 +413,12 @@ describe('ReactSuspense', () => {
413
414 jest.advanceTimersByTime(100);
415
433 - if (enableNewScheduler) {
434 - expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
435 - expect(Scheduler).toFlushExpired([
436 - 'B:2',
437 - 'Unmount [Loading...]',
438 - 'Update [B:2]',
439 - ]);
440 - } else {
441 - expect(Scheduler).toHaveYielded([
442 - 'Promise resolved [B:2]',
443 - 'B:2',
444 - 'Unmount [Loading...]',
445 - 'Update [B:2]',
446 - ]);
447 - }
416 + expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
417 + expect(Scheduler).toFlushExpired([
418 + 'B:2',
419 + 'Unmount [Loading...]',
420 + 'Update [B:2]',
421 + ]);
422 expect(root).toMatchRenderedOutput('AB:2C');
423 });
424
@@ -477,13 +451,8 @@ describe('ReactSuspense', () => {
451
452 jest.advanceTimersByTime(1000);
453
480 - if (enableNewScheduler) {
481 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
482 - expect(Scheduler).toFlushExpired(['A']);
483 - } else {
484 - expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
485 - }
486 -
454 + expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
455 + expect(Scheduler).toFlushExpired(['A']);
456 expect(root).toMatchRenderedOutput('Stateful: 1A');
457
458 root.update(<App text="B" />);
@@ -500,13 +469,8 @@ describe('ReactSuspense', () => {
469
470 jest.advanceTimersByTime(1000);
471
503 - if (enableNewScheduler) {
504 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
505 - expect(Scheduler).toFlushExpired(['B']);
506 - } else {
507 - expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
508 - }
509 -
472 + expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
473 + expect(Scheduler).toFlushExpired(['B']);
474 expect(root).toMatchRenderedOutput('Stateful: 2B');
475 });
476
@@ -547,12 +511,8 @@ describe('ReactSuspense', () => {
511
512 jest.advanceTimersByTime(1000);
513
550 - if (enableNewScheduler) {
551 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
552 - expect(Scheduler).toFlushExpired(['A']);
553 - } else {
554 - expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
555 - }
514 + expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
515 + expect(Scheduler).toFlushExpired(['A']);
516 expect(root).toMatchRenderedOutput('Stateful: 1A');
517
518 root.update(<App text="B" />);
@@ -576,13 +536,8 @@ describe('ReactSuspense', () => {
536
537 jest.advanceTimersByTime(1000);
538
579 - if (enableNewScheduler) {
580 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
581 - expect(Scheduler).toFlushExpired(['B']);
582 - } else {
583 - expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
584 - }
585 -
539 + expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
540 + expect(Scheduler).toFlushExpired(['B']);
541 expect(root).toMatchRenderedOutput('Stateful: 2B');
542 });
543
@@ -664,16 +619,8 @@ describe('ReactSuspense', () => {
619 expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
620 jest.advanceTimersByTime(500);
621
667 - if (enableNewScheduler) {
668 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
669 - expect(Scheduler).toFlushExpired(['A', 'Did commit: A']);
670 - } else {
671 - expect(Scheduler).toHaveYielded([
672 - 'Promise resolved [A]',
673 - 'A',
674 - 'Did commit: A',
675 - ]);
676 - }
622 + expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
623 + expect(Scheduler).toFlushExpired(['A', 'Did commit: A']);
624 });
625
626 it('retries when an update is scheduled on a timed out tree', () => {
@@ -756,43 +703,25 @@ describe('ReactSuspense', () => {
703 'Loading...',
704 ]);
705 expect(Scheduler).toFlushAndYield([]);
706 +
707 jest.advanceTimersByTime(1000);
760 - if (enableNewScheduler) {
761 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
762 - expect(Scheduler).toFlushExpired([
763 - 'Child 1',
764 - 'Suspend! [Child 2]',
765 - 'Suspend! [Child 3]',
766 - ]);
767 - } else {
768 - expect(Scheduler).toHaveYielded([
769 - 'Promise resolved [Child 1]',
770 - 'Child 1',
771 - 'Suspend! [Child 2]',
772 - 'Suspend! [Child 3]',
773 - ]);
774 - }
708 +
709 + expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
710 + expect(Scheduler).toFlushExpired([
711 + 'Child 1',
712 + 'Suspend! [Child 2]',
713 + 'Suspend! [Child 3]',
714 + ]);
715 +
716 jest.advanceTimersByTime(1000);
776 - if (enableNewScheduler) {
777 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
778 - expect(Scheduler).toFlushExpired(['Child 2', 'Suspend! [Child 3]']);
779 - } else {
780 - expect(Scheduler).toHaveYielded([
781 - 'Promise resolved [Child 2]',
782 - 'Child 2',
783 - 'Suspend! [Child 3]',
784 - ]);
785 - }
717 +
718 + expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
719 + expect(Scheduler).toFlushExpired(['Child 2', 'Suspend! [Child 3]']);
720 +
721 jest.advanceTimersByTime(1000);
787 - if (enableNewScheduler) {
788 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
789 - expect(Scheduler).toFlushExpired(['Child 3']);
790 - } else {
791 - expect(Scheduler).toHaveYielded([
792 - 'Promise resolved [Child 3]',
793 - 'Child 3',
794 - ]);
795 - }
722 +
723 + expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
724 + expect(Scheduler).toFlushExpired(['Child 3']);
725 expect(root).toMatchRenderedOutput(
726 ['Child 1', 'Child 2', 'Child 3'].join(''),
727 );
@@ -852,15 +781,8 @@ describe('ReactSuspense', () => {
781 expect(root).toMatchRenderedOutput('Loading...');
782 jest.advanceTimersByTime(1000);
783
855 - if (enableNewScheduler) {
856 - expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
857 - expect(Scheduler).toFlushExpired(['Tab: 0']);
858 - } else {
859 - expect(Scheduler).toHaveYielded([
860 - 'Promise resolved [Tab: 0]',
861 - 'Tab: 0',
862 - ]);
863 - }
784 + expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
785 + expect(Scheduler).toFlushExpired(['Tab: 0']);
786 expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
787
788 act(() => setTab(1));
@@ -872,16 +794,8 @@ describe('ReactSuspense', () => {
794 expect(root).toMatchRenderedOutput('Loading...');
795 jest.advanceTimersByTime(1000);
796
875 - if (enableNewScheduler) {
876 - expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
877 - expect(Scheduler).toFlushExpired(['Tab: 1']);
878 - } else {
879 - expect(Scheduler).toHaveYielded([
880 - 'Promise resolved [Tab: 1]',
881 - 'Tab: 1',
882 - ]);
883 - }
884 -
797 + expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
798 + expect(Scheduler).toFlushExpired(['Tab: 1']);
799 expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
800
801 act(() => setTab(2));
@@ -893,16 +807,8 @@ describe('ReactSuspense', () => {
807 expect(root).toMatchRenderedOutput('Loading...');
808 jest.advanceTimersByTime(1000);
809
896 - if (enableNewScheduler) {
897 - expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
898 - expect(Scheduler).toFlushExpired(['Tab: 2']);
899 - } else {
900 - expect(Scheduler).toHaveYielded([
901 - 'Promise resolved [Tab: 2]',
902 - 'Tab: 2',
903 - ]);
904 - }
905 -
810 + expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
811 + expect(Scheduler).toFlushExpired(['Tab: 2']);
812 expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
813 });
814
@@ -939,13 +845,8 @@ describe('ReactSuspense', () => {
845 expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
846 jest.advanceTimersByTime(1000);
847
942 - if (enableNewScheduler) {
943 - expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
944 - expect(Scheduler).toFlushExpired(['A:0']);
945 - } else {
946 - expect(Scheduler).toHaveYielded(['Promise resolved [A:0]', 'A:0']);
947 - }
948 -
848 + expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
849 + expect(Scheduler).toFlushExpired(['A:0']);
850 expect(root).toMatchRenderedOutput('A:0');
851
852 act(() => setStep(1));
@@ -982,65 +883,35 @@ describe('ReactSuspense', () => {
883 // Resolve A
884 jest.advanceTimersByTime(1000);
885
985 - if (enableNewScheduler) {
986 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
987 - expect(Scheduler).toFlushExpired([
988 - 'A',
989 - // The promises for B and C have now been thrown twice
990 - 'Suspend! [B]',
991 - 'Suspend! [C]',
992 - ]);
993 - } else {
994 - expect(Scheduler).toHaveYielded([
995 - 'Promise resolved [A]',
996 - 'A',
997 - // The promises for B and C have now been thrown twice
998 - 'Suspend! [B]',
999 - 'Suspend! [C]',
1000 - ]);
1001 - }
886 + expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
887 + expect(Scheduler).toFlushExpired([
888 + 'A',
889 + // The promises for B and C have now been thrown twice
890 + 'Suspend! [B]',
891 + 'Suspend! [C]',
892 + ]);
893
894 // Resolve B
895 jest.advanceTimersByTime(1000);
896
1006 - if (enableNewScheduler) {
1007 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1008 - expect(Scheduler).toFlushExpired([
1009 - // Even though the promise for B was thrown twice, we should only
1010 - // re-render once.
1011 - 'B',
1012 - // The promise for C has now been thrown three times
1013 - 'Suspend! [C]',
1014 - ]);
1015 - } else {
1016 - expect(Scheduler).toHaveYielded([
1017 - 'Promise resolved [B]',
1018 - // Even though the promise for B was thrown twice, we should only
1019 - // re-render once.
1020 - 'B',
1021 - // The promise for C has now been thrown three times
1022 - 'Suspend! [C]',
1023 - ]);
1024 - }
897 + expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
898 + expect(Scheduler).toFlushExpired([
899 + // Even though the promise for B was thrown twice, we should only
900 + // re-render once.
901 + 'B',
902 + // The promise for C has now been thrown three times
903 + 'Suspend! [C]',
904 + ]);
905
906 // Resolve C
907 jest.advanceTimersByTime(1000);
908
1029 - if (enableNewScheduler) {
1030 - expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
1031 - expect(Scheduler).toFlushExpired([
1032 - // Even though the promise for C was thrown three times, we should only
1033 - // re-render once.
1034 - 'C',
1035 - ]);
1036 - } else {
1037 - expect(Scheduler).toHaveYielded([
1038 - 'Promise resolved [C]',
1039 - // Even though the promise for C was thrown three times, we should only
1040 - // re-render once.
1041 - 'C',
1042 - ]);
1043 - }
909 + expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
910 + expect(Scheduler).toFlushExpired([
911 + // Even though the promise for C was thrown three times, we should only
912 + // re-render once.
913 + 'C',
914 + ]);
915 });
916
917 it('#14162', () => {
packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+4 -22
@@ -17,7 +17,6 @@ let ReactCache;
17 let Suspense;
18 let TextResource;
19 let textResourceShouldFail;
20 -let enableNewScheduler;
20
21 describe('ReactSuspensePlaceholder', () => {
22 beforeEach(() => {
@@ -31,7 +30,6 @@ describe('ReactSuspensePlaceholder', () => {
30 ReactNoop = require('react-noop-renderer');
31 Scheduler = require('scheduler');
32 ReactCache = require('react-cache');
34 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
33
34 Profiler = React.Profiler;
35 Suspense = React.Suspense;
@@ -325,16 +323,8 @@ describe('ReactSuspensePlaceholder', () => {
323
324 jest.advanceTimersByTime(1000);
325
328 - if (enableNewScheduler) {
329 - expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
330 - expect(Scheduler).toFlushExpired(['Loaded']);
331 - } else {
332 - expect(Scheduler).toHaveYielded([
333 - 'Promise resolved [Loaded]',
334 - 'Loaded',
335 - ]);
336 - }
337 -
326 + expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
327 + expect(Scheduler).toFlushExpired(['Loaded']);
328 expect(ReactNoop).toMatchRenderedOutput('LoadedText');
329 expect(onRender).toHaveBeenCalledTimes(2);
330
@@ -434,16 +424,8 @@ describe('ReactSuspensePlaceholder', () => {
424
425 jest.advanceTimersByTime(1000);
426
437 - if (enableNewScheduler) {
438 - expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
439 - expect(Scheduler).toFlushExpired(['Loaded']);
440 - } else {
441 - expect(Scheduler).toHaveYielded([
442 - 'Promise resolved [Loaded]',
443 - 'Loaded',
444 - ]);
445 - }
446 -
427 + expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
428 + expect(Scheduler).toFlushExpired(['Loaded']);
429 expect(ReactNoop).toMatchRenderedOutput('LoadedNew');
430 expect(onRender).toHaveBeenCalledTimes(4);
431
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+55 -156
@@ -7,7 +7,6 @@ let ReactCache;
7 let Suspense;
8 let StrictMode;
9 let ConcurrentMode;
10 -let enableNewScheduler;
10
11 let TextResource;
12 let textResourceShouldFail;
@@ -29,7 +28,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
28 Suspense = React.Suspense;
29 StrictMode = React.StrictMode;
30 ConcurrentMode = React.unstable_ConcurrentMode;
32 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
31
32 TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
33 return new Promise((resolve, reject) =>
@@ -889,16 +887,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
887 ReactNoop.expire(100);
888 await advanceTimers(100);
889
892 - if (enableNewScheduler) {
893 - expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
894 - expect(Scheduler).toFlushExpired(['Result']);
895 - } else {
896 - expect(Scheduler).toHaveYielded([
897 - 'Promise resolved [Result]',
898 - 'Result',
899 - ]);
900 - }
901 -
890 + expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
891 + expect(Scheduler).toFlushExpired(['Result']);
892 expect(ReactNoop.getChildren()).toEqual([span('Result')]);
893 });
894
@@ -935,27 +925,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
925 // Initial mount. This is synchronous, because the root is sync.
926 ReactNoop.renderLegacySyncRoot(<App />);
927 await advanceTimers(100);
938 - if (enableNewScheduler) {
939 - expect(Scheduler).toHaveYielded([
940 - 'Suspend! [Step: 1]',
941 - 'Sibling',
942 - 'Loading (1)',
943 - 'Loading (2)',
944 - 'Loading (3)',
945 - 'Promise resolved [Step: 1]',
946 - ]);
947 - expect(Scheduler).toFlushExpired(['Step: 1']);
948 - } else {
949 - expect(Scheduler).toHaveYielded([
950 - 'Suspend! [Step: 1]',
951 - 'Sibling',
952 - 'Loading (1)',
953 - 'Loading (2)',
954 - 'Loading (3)',
955 - 'Promise resolved [Step: 1]',
956 - 'Step: 1',
957 - ]);
958 - }
928 + expect(Scheduler).toHaveYielded([
929 + 'Suspend! [Step: 1]',
930 + 'Sibling',
931 + 'Loading (1)',
932 + 'Loading (2)',
933 + 'Loading (3)',
934 + 'Promise resolved [Step: 1]',
935 + ]);
936 + expect(Scheduler).toFlushExpired(['Step: 1']);
937 expect(ReactNoop).toMatchRenderedOutput(
938 <React.Fragment>
939 <span prop="Step: 1" />
@@ -987,15 +965,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
965 );
966
967 await advanceTimers(100);
990 - if (enableNewScheduler) {
991 - expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
992 - expect(Scheduler).toFlushExpired(['Step: 2']);
993 - } else {
994 - expect(Scheduler).toHaveYielded([
995 - 'Promise resolved [Step: 2]',
996 - 'Step: 2',
997 - ]);
998 - }
968 + expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
969 + expect(Scheduler).toFlushExpired(['Step: 2']);
970 expect(ReactNoop).toMatchRenderedOutput(
971 <React.Fragment>
972 <span prop="Step: 2" />
@@ -1054,33 +1025,18 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1025 );
1026 await advanceTimers(100);
1027
1057 - if (enableNewScheduler) {
1058 - expect(Scheduler).toHaveYielded([
1059 - 'Before',
1060 - 'Suspend! [Async: 1]',
1061 - 'After',
1062 - 'Loading...',
1063 - 'Before',
1064 - 'Sync: 1',
1065 - 'After',
1066 - 'Did mount',
1067 - 'Promise resolved [Async: 1]',
1068 - ]);
1069 - expect(Scheduler).toFlushExpired(['Async: 1']);
1070 - } else {
1071 - expect(Scheduler).toHaveYielded([
1072 - 'Before',
1073 - 'Suspend! [Async: 1]',
1074 - 'After',
1075 - 'Loading...',
1076 - 'Before',
1077 - 'Sync: 1',
1078 - 'After',
1079 - 'Did mount',
1080 - 'Promise resolved [Async: 1]',
1081 - 'Async: 1',
1082 - ]);
1083 - }
1028 + expect(Scheduler).toHaveYielded([
1029 + 'Before',
1030 + 'Suspend! [Async: 1]',
1031 + 'After',
1032 + 'Loading...',
1033 + 'Before',
1034 + 'Sync: 1',
1035 + 'After',
1036 + 'Did mount',
1037 + 'Promise resolved [Async: 1]',
1038 + ]);
1039 + expect(Scheduler).toFlushExpired(['Async: 1']);
1040 expect(ReactNoop).toMatchRenderedOutput(
1041 <React.Fragment>
1042 <span prop="Before" />
@@ -1135,16 +1091,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1091 // synchronously.
1092 await advanceTimers(100);
1093
1138 - if (enableNewScheduler) {
1139 - expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
1140 - expect(Scheduler).toFlushExpired(['Async: 2']);
1141 - } else {
1142 - expect(Scheduler).toHaveYielded([
1143 - 'Promise resolved [Async: 2]',
1144 - 'Async: 2',
1145 - ]);
1146 - }
1147 -
1094 + expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
1095 + expect(Scheduler).toFlushExpired(['Async: 2']);
1096 expect(ReactNoop).toMatchRenderedOutput(
1097 <React.Fragment>
1098 <span prop="Before" />
@@ -1208,33 +1156,18 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1156 Scheduler.yieldValue('Did mount'),
1157 );
1158 await advanceTimers(100);
1211 - if (enableNewScheduler) {
1212 - expect(Scheduler).toHaveYielded([
1213 - 'Before',
1214 - 'Suspend! [Async: 1]',
1215 - 'After',
1216 - 'Loading...',
1217 - 'Before',
1218 - 'Sync: 1',
1219 - 'After',
1220 - 'Did mount',
1221 - 'Promise resolved [Async: 1]',
1222 - ]);
1223 - expect(Scheduler).toFlushExpired(['Async: 1']);
1224 - } else {
1225 - expect(Scheduler).toHaveYielded([
1226 - 'Before',
1227 - 'Suspend! [Async: 1]',
1228 - 'After',
1229 - 'Loading...',
1230 - 'Before',
1231 - 'Sync: 1',
1232 - 'After',
1233 - 'Did mount',
1234 - 'Promise resolved [Async: 1]',
1235 - 'Async: 1',
1236 - ]);
1237 - }
1159 + expect(Scheduler).toHaveYielded([
1160 + 'Before',
1161 + 'Suspend! [Async: 1]',
1162 + 'After',
1163 + 'Loading...',
1164 + 'Before',
1165 + 'Sync: 1',
1166 + 'After',
1167 + 'Did mount',
1168 + 'Promise resolved [Async: 1]',
1169 + ]);
1170 + expect(Scheduler).toFlushExpired(['Async: 1']);
1171 expect(ReactNoop).toMatchRenderedOutput(
1172 <React.Fragment>
1173 <span prop="Before" />
@@ -1289,16 +1222,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1222 // synchronously.
1223 await advanceTimers(100);
1224
1292 - if (enableNewScheduler) {
1293 - expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
1294 - expect(Scheduler).toFlushExpired(['Async: 2']);
1295 - } else {
1296 - expect(Scheduler).toHaveYielded([
1297 - 'Promise resolved [Async: 2]',
1298 - 'Async: 2',
1299 - ]);
1300 - }
1301 -
1225 + expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
1226 + expect(Scheduler).toFlushExpired(['Async: 2']);
1227 expect(ReactNoop).toMatchRenderedOutput(
1228 <React.Fragment>
1229 <span prop="Before" />
@@ -1376,13 +1301,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1301 ReactNoop.expire(1000);
1302 await advanceTimers(1000);
1303
1379 - if (enableNewScheduler) {
1380 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1381 - expect(Scheduler).toFlushExpired(['B']);
1382 - } else {
1383 - expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
1384 - }
1385 -
1304 + expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1305 + expect(Scheduler).toFlushExpired(['B']);
1306 expect(ReactNoop).toMatchRenderedOutput(
1307 <React.Fragment>
1308 <span prop="A" />
@@ -1434,21 +1354,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1354
1355 await advanceTimers(1000);
1356
1437 - if (enableNewScheduler) {
1438 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1439 - expect(Scheduler).toFlushExpired([
1440 - 'constructor',
1441 - 'Hi',
1442 - 'componentDidMount',
1443 - ]);
1444 - } else {
1445 - expect(Scheduler).toHaveYielded([
1446 - 'Promise resolved [Hi]',
1447 - 'constructor',
1448 - 'Hi',
1449 - 'componentDidMount',
1450 - ]);
1451 - }
1357 + expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1358 + expect(Scheduler).toFlushExpired([
1359 + 'constructor',
1360 + 'Hi',
1361 + 'componentDidMount',
1362 + ]);
1363 expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1364 });
1365
@@ -1487,12 +1398,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1398 ]);
1399 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1400 await advanceTimers(100);
1490 - if (enableNewScheduler) {
1491 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1492 - expect(Scheduler).toFlushExpired(['Hi']);
1493 - } else {
1494 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
1495 - }
1401 + expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1402 + expect(Scheduler).toFlushExpired(['Hi']);
1403 expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1404 });
1405
@@ -1536,12 +1443,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1443
1444 await advanceTimers(1000);
1445
1539 - if (enableNewScheduler) {
1540 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1541 - expect(Scheduler).toFlushExpired(['Hi']);
1542 - } else {
1543 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
1544 - }
1446 + expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1447 + expect(Scheduler).toFlushExpired(['Hi']);
1448 });
1449 } else {
1450 it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
@@ -1580,12 +1483,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1483
1484 await advanceTimers(1000);
1485
1583 - if (enableNewScheduler) {
1584 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1585 - expect(Scheduler).toFlushExpired(['Hi']);
1586 - } else {
1587 - expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
1588 - }
1486 + expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1487 + expect(Scheduler).toFlushExpired(['Hi']);
1488 });
1489 }
1490 });
packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
+21 -525
@@ -1,6 +1,6 @@
1 // Jest Snapshot v1, https://goo.gl/fbAQLP
2
3 -exports[`ReactDebugFiberPerf new scheduler captures all lifecycles 1`] = `
3 +exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
4 "⚛ (Waiting for async callback... will force flush in 5250 ms)
5
6 // Mount
@@ -44,7 +44,7 @@ exports[`ReactDebugFiberPerf new scheduler captures all lifecycles 1`] = `
44 "
45 `;
46
47 -exports[`ReactDebugFiberPerf new scheduler deduplicates lifecycle names during commit to reduce overhead 1`] = `
47 +exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = `
48 "⚛ (Waiting for async callback... will force flush in 5250 ms)
49
50 // The commit phase should mention A and B just once
@@ -91,7 +91,7 @@ exports[`ReactDebugFiberPerf new scheduler deduplicates lifecycle names during c
91 "
92 `;
93
94 -exports[`ReactDebugFiberPerf new scheduler does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
94 +exports[`ReactDebugFiberPerf does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
95 "⚛ (Waiting for async callback... will force flush in 5250 ms)
96
97 // Mount
@@ -107,7 +107,7 @@ exports[`ReactDebugFiberPerf new scheduler does not include ConcurrentMode, Stri
107 "
108 `;
109
110 -exports[`ReactDebugFiberPerf new scheduler does not include context provider or consumer in measurements 1`] = `
110 +exports[`ReactDebugFiberPerf does not include context provider or consumer in measurements 1`] = `
111 "⚛ (Waiting for async callback... will force flush in 5250 ms)
112
113 // Mount
@@ -122,7 +122,7 @@ exports[`ReactDebugFiberPerf new scheduler does not include context provider or
122 "
123 `;
124
125 -exports[`ReactDebugFiberPerf new scheduler does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
125 +exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
126 "⚛ (React Tree Reconciliation: Completed Root)
127 ⚛ Component [mount]
128
@@ -142,7 +142,7 @@ exports[`ReactDebugFiberPerf new scheduler does not schedule an extra callback i
142 "
143 `;
144
145 -exports[`ReactDebugFiberPerf new scheduler does not treat setState from cWM or cWRP as cascading 1`] = `
145 +exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = `
146 "⚛ (Waiting for async callback... will force flush in 5250 ms)
147
148 // Should not print a warning
@@ -171,7 +171,7 @@ exports[`ReactDebugFiberPerf new scheduler does not treat setState from cWM or c
171 "
172 `;
173
174 -exports[`ReactDebugFiberPerf new scheduler measures a simple reconciliation 1`] = `
174 +exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
175 "⚛ (Waiting for async callback... will force flush in 5250 ms)
176
177 // Mount
@@ -208,7 +208,7 @@ exports[`ReactDebugFiberPerf new scheduler measures a simple reconciliation 1`]
208 "
209 `;
210
211 -exports[`ReactDebugFiberPerf new scheduler measures deferred work in chunks 1`] = `
211 +exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
212 "⚛ (Waiting for async callback... will force flush in 5250 ms)
213
214 // Start rendering through B
@@ -235,7 +235,7 @@ exports[`ReactDebugFiberPerf new scheduler measures deferred work in chunks 1`]
235 "
236 `;
237
238 -exports[`ReactDebugFiberPerf new scheduler measures deprioritized work 1`] = `
238 +exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
239 "// Flush the parent
240 ⚛ (React Tree Reconciliation: Completed Root)
241 ⚛ Parent [mount]
@@ -258,7 +258,7 @@ exports[`ReactDebugFiberPerf new scheduler measures deprioritized work 1`] = `
258 "
259 `;
260
261 -exports[`ReactDebugFiberPerf new scheduler properly displays the forwardRef component in measurements 1`] = `
261 +exports[`ReactDebugFiberPerf properly displays the forwardRef component in measurements 1`] = `
262 "⚛ (Waiting for async callback... will force flush in 5250 ms)
263
264 // Mount
@@ -278,7 +278,7 @@ exports[`ReactDebugFiberPerf new scheduler properly displays the forwardRef comp
278 "
279 `;
280
281 -exports[`ReactDebugFiberPerf new scheduler recovers from caught errors 1`] = `
281 +exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
282 "⚛ (Waiting for async callback... will force flush in 5250 ms)
283
284 // Stop on Baddie and restart from Boundary
@@ -312,7 +312,7 @@ exports[`ReactDebugFiberPerf new scheduler recovers from caught errors 1`] = `
312 "
313 `;
314
315 -exports[`ReactDebugFiberPerf new scheduler recovers from fatal errors 1`] = `
315 +exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
316 "⚛ (Waiting for async callback... will force flush in 5250 ms)
317
318 // Will fatal
@@ -343,7 +343,7 @@ exports[`ReactDebugFiberPerf new scheduler recovers from fatal errors 1`] = `
343 "
344 `;
345
346 -exports[`ReactDebugFiberPerf new scheduler skips parents during setState 1`] = `
346 +exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
347 "⚛ (Waiting for async callback... will force flush in 5250 ms)
348
349 // Should include just A and B, no Parents
@@ -358,7 +358,7 @@ exports[`ReactDebugFiberPerf new scheduler skips parents during setState 1`] = `
358 "
359 `;
360
361 -exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 1`] = `
361 +exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
362 "⚛ (Waiting for async callback... will force flush in 5250 ms)
363
364 ⚛ (React Tree Reconciliation: Completed Root)
@@ -369,7 +369,7 @@ exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 1`] = `
369 "
370 `;
371
372 -exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 2`] = `
372 +exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
373 "⚛ (Waiting for async callback... will force flush in 5250 ms)
374
375 ⚛ (React Tree Reconciliation: Completed Root)
@@ -392,7 +392,7 @@ exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 2`] = `
392 "
393 `;
394
395 -exports[`ReactDebugFiberPerf new scheduler supports memo 1`] = `
395 +exports[`ReactDebugFiberPerf supports memo 1`] = `
396 "⚛ (Waiting for async callback... will force flush in 5250 ms)
397
398 ⚛ (React Tree Reconciliation: Completed Root)
@@ -406,7 +406,7 @@ exports[`ReactDebugFiberPerf new scheduler supports memo 1`] = `
406 "
407 `;
408
409 -exports[`ReactDebugFiberPerf new scheduler supports portals 1`] = `
409 +exports[`ReactDebugFiberPerf supports portals 1`] = `
410 "⚛ (Waiting for async callback... will force flush in 5250 ms)
411
412 ⚛ (React Tree Reconciliation: Completed Root)
@@ -420,7 +420,7 @@ exports[`ReactDebugFiberPerf new scheduler supports portals 1`] = `
420 "
421 `;
422
423 -exports[`ReactDebugFiberPerf new scheduler warns if an in-progress update is interrupted 1`] = `
423 +exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
424 "⚛ (Waiting for async callback... will force flush in 5250 ms)
425
426 ⚛ (React Tree Reconciliation: Yielded)
@@ -443,7 +443,7 @@ exports[`ReactDebugFiberPerf new scheduler warns if an in-progress update is int
443 "
444 `;
445
446 -exports[`ReactDebugFiberPerf new scheduler warns if async work expires (starvation) 1`] = `
446 +exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = `
447 "⛔ (Waiting for async callback... will force flush in 5250 ms) Warning: React was blocked by main thread
448
449 ⚛ (Committing Changes)
@@ -453,7 +453,7 @@ exports[`ReactDebugFiberPerf new scheduler warns if async work expires (starvati
453 "
454 `;
455
456 -exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from setState 1`] = `
456 +exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
457 "⚛ (Waiting for async callback... will force flush in 5250 ms)
458
459 // Should print a warning
@@ -477,511 +477,7 @@ exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from setSt
477 "
478 `;
479
480 -exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from top-level render 1`] = `
481 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
482 -
483 -// Rendering the first root
484 -⚛ (React Tree Reconciliation: Completed Root)
485 - ⚛ Cascading [mount]
486 -
487 -⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
488 - ⚛ (Committing Snapshot Effects: 0 Total)
489 - ⚛ (Committing Host Effects: 1 Total)
490 - ⚛ (Calling Lifecycle Methods: 1 Total)
491 - ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
492 -
493 -// Scheduling another root from componentDidMount
494 -⚛ (React Tree Reconciliation: Completed Root)
495 - ⚛ Child [mount]
496 -
497 -⚛ (Committing Changes)
498 - ⚛ (Committing Snapshot Effects: 0 Total)
499 - ⚛ (Committing Host Effects: 1 Total)
500 - ⚛ (Calling Lifecycle Methods: 0 Total)
501 -"
502 -`;
503 -
504 -exports[`ReactDebugFiberPerf old scheduler captures all lifecycles 1`] = `
505 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
506 -
507 -// Mount
508 -⚛ (React Tree Reconciliation: Completed Root)
509 - ⚛ AllLifecycles [mount]
510 - ⚛ AllLifecycles.componentWillMount
511 - ⚛ AllLifecycles.getChildContext
512 -
513 -⚛ (Committing Changes)
514 - ⚛ (Committing Snapshot Effects: 0 Total)
515 - ⚛ (Committing Host Effects: 1 Total)
516 - ⚛ (Calling Lifecycle Methods: 1 Total)
517 - ⚛ AllLifecycles.componentDidMount
518 -
519 -⚛ (Waiting for async callback... will force flush in 5250 ms)
520 -
521 -// Update
522 -⚛ (React Tree Reconciliation: Completed Root)
523 - ⚛ AllLifecycles [update]
524 - ⚛ AllLifecycles.componentWillReceiveProps
525 - ⚛ AllLifecycles.shouldComponentUpdate
526 - ⚛ AllLifecycles.componentWillUpdate
527 - ⚛ AllLifecycles.getChildContext
528 -
529 -⚛ (Committing Changes)
530 - ⚛ (Committing Snapshot Effects: 0 Total)
531 - ⚛ (Committing Host Effects: 2 Total)
532 - ⚛ (Calling Lifecycle Methods: 2 Total)
533 - ⚛ AllLifecycles.componentDidUpdate
534 -
535 -⚛ (Waiting for async callback... will force flush in 5250 ms)
536 -
537 -// Unmount
538 -⚛ (React Tree Reconciliation: Completed Root)
539 -
540 -⚛ (Committing Changes)
541 - ⚛ (Committing Snapshot Effects: 0 Total)
542 - ⚛ (Committing Host Effects: 1 Total)
543 - ⚛ AllLifecycles.componentWillUnmount
544 - ⚛ (Calling Lifecycle Methods: 0 Total)
545 -"
546 -`;
547 -
548 -exports[`ReactDebugFiberPerf old scheduler deduplicates lifecycle names during commit to reduce overhead 1`] = `
549 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
550 -
551 -// The commit phase should mention A and B just once
552 -⚛ (React Tree Reconciliation: Completed Root)
553 - ⚛ Parent [update]
554 - ⚛ A [update]
555 - ⚛ B [update]
556 - ⚛ A [update]
557 - ⚛ B [update]
558 -
559 -⚛ (Committing Changes)
560 - ⚛ (Committing Snapshot Effects: 0 Total)
561 - ⚛ (Committing Host Effects: 9 Total)
562 - ⚛ (Calling Lifecycle Methods: 9 Total)
563 - ⚛ A.componentDidUpdate
564 - ⚛ B.componentDidUpdate
565 -
566 -⚛ (Waiting for async callback... will force flush in 5250 ms)
567 -
568 -// Because of deduplication, we don't know B was cascading,
569 -// but we should still see the warning for the commit phase.
570 -⚛ (React Tree Reconciliation: Completed Root)
571 - ⚛ Parent [update]
572 - ⚛ A [update]
573 - ⚛ B [update]
574 - ⚛ A [update]
575 - ⚛ B [update]
576 -
577 -⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
578 - ⚛ (Committing Snapshot Effects: 0 Total)
579 - ⚛ (Committing Host Effects: 9 Total)
580 - ⚛ (Calling Lifecycle Methods: 9 Total)
581 - ⚛ A.componentDidUpdate
582 - ⚛ B.componentDidUpdate
583 -
584 -⚛ (React Tree Reconciliation: Completed Root)
585 - ⚛ B [update]
586 -
587 -⚛ (Committing Changes)
588 - ⚛ (Committing Snapshot Effects: 0 Total)
589 - ⚛ (Committing Host Effects: 2 Total)
590 - ⚛ (Calling Lifecycle Methods: 2 Total)
591 - ⚛ B.componentDidUpdate
592 -"
593 -`;
594 -
595 -exports[`ReactDebugFiberPerf old scheduler does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
596 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
597 -
598 -// Mount
599 -⚛ (React Tree Reconciliation: Completed Root)
600 - ⚛ Profiler [mount]
601 - ⚛ Parent [mount]
602 - ⚛ Child [mount]
603 -
604 -⚛ (Committing Changes)
605 - ⚛ (Committing Snapshot Effects: 0 Total)
606 - ⚛ (Committing Host Effects: 1 Total)
607 - ⚛ (Calling Lifecycle Methods: 0 Total)
608 -"
609 -`;
610 -
611 -exports[`ReactDebugFiberPerf old scheduler does not include context provider or consumer in measurements 1`] = `
612 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
613 -
614 -// Mount
615 -⚛ (React Tree Reconciliation: Completed Root)
616 - ⚛ Parent [mount]
617 - ⚛ Child [mount]
618 -
619 -⚛ (Committing Changes)
620 - ⚛ (Committing Snapshot Effects: 0 Total)
621 - ⚛ (Committing Host Effects: 1 Total)
622 - ⚛ (Calling Lifecycle Methods: 0 Total)
623 -"
624 -`;
625 -
626 -exports[`ReactDebugFiberPerf old scheduler does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
627 -"⚛ (React Tree Reconciliation: Completed Root)
628 - ⚛ Component [mount]
629 -
630 -⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
631 - ⚛ (Committing Snapshot Effects: 0 Total)
632 - ⚛ (Committing Host Effects: 1 Total)
633 - ⚛ (Calling Lifecycle Methods: 1 Total)
634 - ⛔ Component.componentDidMount Warning: Scheduled a cascading update
635 -
636 -⚛ (React Tree Reconciliation: Completed Root)
637 - ⚛ Component [update]
638 -
639 -⚛ (Committing Changes)
640 - ⚛ (Committing Snapshot Effects: 0 Total)
641 - ⚛ (Committing Host Effects: 1 Total)
642 - ⚛ (Calling Lifecycle Methods: 1 Total)
643 -"
644 -`;
645 -
646 -exports[`ReactDebugFiberPerf old scheduler does not treat setState from cWM or cWRP as cascading 1`] = `
647 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
648 -
649 -// Should not print a warning
650 -⚛ (React Tree Reconciliation: Completed Root)
651 - ⚛ Parent [mount]
652 - ⚛ NotCascading [mount]
653 - ⚛ NotCascading.componentWillMount
654 -
655 -⚛ (Committing Changes)
656 - ⚛ (Committing Snapshot Effects: 0 Total)
657 - ⚛ (Committing Host Effects: 1 Total)
658 - ⚛ (Calling Lifecycle Methods: 0 Total)
659 -
660 -⚛ (Waiting for async callback... will force flush in 5250 ms)
661 -
662 -// Should not print a warning
663 -⚛ (React Tree Reconciliation: Completed Root)
664 - ⚛ Parent [update]
665 - ⚛ NotCascading [update]
666 - ⚛ NotCascading.componentWillReceiveProps
667 -
668 -⚛ (Committing Changes)
669 - ⚛ (Committing Snapshot Effects: 0 Total)
670 - ⚛ (Committing Host Effects: 2 Total)
671 - ⚛ (Calling Lifecycle Methods: 2 Total)
672 -"
673 -`;
674 -
675 -exports[`ReactDebugFiberPerf old scheduler measures a simple reconciliation 1`] = `
676 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
677 -
678 -// Mount
679 -⚛ (React Tree Reconciliation: Completed Root)
680 - ⚛ Parent [mount]
681 - ⚛ Child [mount]
682 -
683 -⚛ (Committing Changes)
684 - ⚛ (Committing Snapshot Effects: 0 Total)
685 - ⚛ (Committing Host Effects: 1 Total)
686 - ⚛ (Calling Lifecycle Methods: 0 Total)
687 -
688 -⚛ (Waiting for async callback... will force flush in 5250 ms)
689 -
690 -// Update
691 -⚛ (React Tree Reconciliation: Completed Root)
692 - ⚛ Parent [update]
693 - ⚛ Child [update]
694 -
695 -⚛ (Committing Changes)
696 - ⚛ (Committing Snapshot Effects: 0 Total)
697 - ⚛ (Committing Host Effects: 2 Total)
698 - ⚛ (Calling Lifecycle Methods: 2 Total)
699 -
700 -⚛ (Waiting for async callback... will force flush in 5250 ms)
701 -
702 -// Unmount
703 -⚛ (React Tree Reconciliation: Completed Root)
704 -
705 -⚛ (Committing Changes)
706 - ⚛ (Committing Snapshot Effects: 0 Total)
707 - ⚛ (Committing Host Effects: 1 Total)
708 - ⚛ (Calling Lifecycle Methods: 0 Total)
709 -"
710 -`;
711 -
712 -exports[`ReactDebugFiberPerf old scheduler measures deferred work in chunks 1`] = `
713 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
714 -
715 -// Start rendering through B
716 -⚛ (React Tree Reconciliation: Yielded)
717 - ⚛ Parent [mount]
718 - ⚛ A [mount]
719 - ⚛ Child [mount]
720 - ⚛ B [mount]
721 -
722 -⚛ (Waiting for async callback... will force flush in 5250 ms)
723 -
724 -// Complete the rest
725 -⚛ (React Tree Reconciliation: Completed Root)
726 - ⚛ Parent [mount]
727 - ⚛ B [mount]
728 - ⚛ Child [mount]
729 - ⚛ C [mount]
730 - ⚛ Child [mount]
731 -
732 -⚛ (Committing Changes)
733 - ⚛ (Committing Snapshot Effects: 0 Total)
734 - ⚛ (Committing Host Effects: 1 Total)
735 - ⚛ (Calling Lifecycle Methods: 0 Total)
736 -"
737 -`;
738 -
739 -exports[`ReactDebugFiberPerf old scheduler measures deprioritized work 1`] = `
740 -"// Flush the parent
741 -⚛ (React Tree Reconciliation: Completed Root)
742 - ⚛ Parent [mount]
743 -
744 -⚛ (Committing Changes)
745 - ⚛ (Committing Snapshot Effects: 0 Total)
746 - ⚛ (Committing Host Effects: 1 Total)
747 - ⚛ (Calling Lifecycle Methods: 0 Total)
748 -
749 -⚛ (Waiting for async callback... will force flush in 10737418210 ms)
750 -
751 -// Flush the child
752 -⚛ (React Tree Reconciliation: Completed Root)
753 - ⚛ Child [mount]
754 -
755 -⚛ (Committing Changes)
756 - ⚛ (Committing Snapshot Effects: 0 Total)
757 - ⚛ (Committing Host Effects: 1 Total)
758 - ⚛ (Calling Lifecycle Methods: 0 Total)
759 -"
760 -`;
761 -
762 -exports[`ReactDebugFiberPerf old scheduler properly displays the forwardRef component in measurements 1`] = `
763 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
764 -
765 -// Mount
766 -⚛ (React Tree Reconciliation: Completed Root)
767 - ⚛ Parent [mount]
768 - ⚛ ForwardRef [mount]
769 - ⚛ Child [mount]
770 - ⚛ ForwardRef(refForwarder) [mount]
771 - ⚛ Child [mount]
772 - ⚛ ForwardRef(OverriddenName) [mount]
773 - ⚛ Child [mount]
774 -
775 -⚛ (Committing Changes)
776 - ⚛ (Committing Snapshot Effects: 0 Total)
777 - ⚛ (Committing Host Effects: 1 Total)
778 - ⚛ (Calling Lifecycle Methods: 0 Total)
779 -"
780 -`;
781 -
782 -exports[`ReactDebugFiberPerf old scheduler recovers from caught errors 1`] = `
783 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
784 -
785 -// Stop on Baddie and restart from Boundary
786 -⚛ (React Tree Reconciliation: Completed Root)
787 - ⚛ Parent [mount]
788 - ⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
789 - ⚛ Parent [mount]
790 - ⚛ Baddie [mount]
791 - ⚛ Boundary [mount]
792 -
793 -⚛ (React Tree Reconciliation: Completed Root)
794 - ⚛ Parent [mount]
795 - ⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
796 - ⚛ Parent [mount]
797 - ⚛ Baddie [mount]
798 - ⚛ Boundary [mount]
799 -
800 -⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
801 - ⚛ (Committing Snapshot Effects: 0 Total)
802 - ⚛ (Committing Host Effects: 2 Total)
803 - ⚛ (Calling Lifecycle Methods: 1 Total)
804 -
805 -⚛ (React Tree Reconciliation: Completed Root)
806 - ⚛ Boundary [update]
807 - ⚛ ErrorReport [mount]
808 -
809 -⚛ (Committing Changes)
810 - ⚛ (Committing Snapshot Effects: 0 Total)
811 - ⚛ (Committing Host Effects: 1 Total)
812 - ⚛ (Calling Lifecycle Methods: 0 Total)
813 -"
814 -`;
815 -
816 -exports[`ReactDebugFiberPerf old scheduler recovers from fatal errors 1`] = `
817 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
818 -
819 -// Will fatal
820 -⚛ (React Tree Reconciliation: Completed Root)
821 - ⚛ Parent [mount]
822 - ⚛ Baddie [mount]
823 -
824 -⚛ (React Tree Reconciliation: Completed Root)
825 - ⚛ Parent [mount]
826 - ⚛ Baddie [mount]
827 -
828 -⚛ (Committing Changes)
829 - ⚛ (Committing Snapshot Effects: 0 Total)
830 - ⚛ (Committing Host Effects: 1 Total)
831 - ⚛ (Calling Lifecycle Methods: 1 Total)
832 -
833 -⚛ (Waiting for async callback... will force flush in 5250 ms)
834 -
835 -// Will reconcile from a clean state
836 -⚛ (React Tree Reconciliation: Completed Root)
837 - ⚛ Parent [mount]
838 - ⚛ Child [mount]
839 -
840 -⚛ (Committing Changes)
841 - ⚛ (Committing Snapshot Effects: 0 Total)
842 - ⚛ (Committing Host Effects: 1 Total)
843 - ⚛ (Calling Lifecycle Methods: 0 Total)
844 -"
845 -`;
846 -
847 -exports[`ReactDebugFiberPerf old scheduler skips parents during setState 1`] = `
848 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
849 -
850 -// Should include just A and B, no Parents
851 -⚛ (React Tree Reconciliation: Completed Root)
852 - ⚛ A [update]
853 - ⚛ B [update]
854 -
855 -⚛ (Committing Changes)
856 - ⚛ (Committing Snapshot Effects: 0 Total)
857 - ⚛ (Committing Host Effects: 2 Total)
858 - ⚛ (Calling Lifecycle Methods: 2 Total)
859 -"
860 -`;
861 -
862 -exports[`ReactDebugFiberPerf old scheduler supports Suspense and lazy 1`] = `
863 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
864 -
865 -⚛ (React Tree Reconciliation: Completed Root)
866 - ⚛ Parent [mount]
867 - ⛔ Suspense [mount] Warning: Rendering was suspended
868 - ⚛ Suspense [mount]
869 - ⚛ Spinner [mount]
870 -"
871 -`;
872 -
873 -exports[`ReactDebugFiberPerf old scheduler supports Suspense and lazy 2`] = `
874 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
875 -
876 -⚛ (React Tree Reconciliation: Completed Root)
877 - ⚛ Parent [mount]
878 - ⛔ Suspense [mount] Warning: Rendering was suspended
879 - ⚛ Suspense [mount]
880 - ⚛ Spinner [mount]
881 -
882 -⚛ (Waiting for async callback... will force flush in 5250 ms)
883 -
884 -⚛ (React Tree Reconciliation: Completed Root)
885 - ⚛ Parent [mount]
886 - ⚛ Suspense [mount]
887 - ⚛ Foo [mount]
888 -
889 -⚛ (Committing Changes)
890 - ⚛ (Committing Snapshot Effects: 0 Total)
891 - ⚛ (Committing Host Effects: 1 Total)
892 - ⚛ (Calling Lifecycle Methods: 0 Total)
893 -"
894 -`;
895 -
896 -exports[`ReactDebugFiberPerf old scheduler supports memo 1`] = `
897 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
898 -
899 -⚛ (React Tree Reconciliation: Completed Root)
900 - ⚛ Parent [mount]
901 - ⚛ Foo [mount]
902 -
903 -⚛ (Committing Changes)
904 - ⚛ (Committing Snapshot Effects: 0 Total)
905 - ⚛ (Committing Host Effects: 1 Total)
906 - ⚛ (Calling Lifecycle Methods: 0 Total)
907 -"
908 -`;
909 -
910 -exports[`ReactDebugFiberPerf old scheduler supports portals 1`] = `
911 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
912 -
913 -⚛ (React Tree Reconciliation: Completed Root)
914 - ⚛ Parent [mount]
915 - ⚛ Child [mount]
916 -
917 -⚛ (Committing Changes)
918 - ⚛ (Committing Snapshot Effects: 0 Total)
919 - ⚛ (Committing Host Effects: 2 Total)
920 - ⚛ (Calling Lifecycle Methods: 0 Total)
921 -"
922 -`;
923 -
924 -exports[`ReactDebugFiberPerf old scheduler warns if an in-progress update is interrupted 1`] = `
925 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
926 -
927 -⚛ (React Tree Reconciliation: Yielded)
928 - ⚛ Foo [mount]
929 -
930 -⚛ (Waiting for async callback... will force flush in 5250 ms)
931 - ⛔ (React Tree Reconciliation: Completed Root) Warning: A top-level update interrupted the previous render
932 - ⚛ Foo [mount]
933 - ⚛ (Committing Changes)
934 - ⚛ (Committing Snapshot Effects: 0 Total)
935 - ⚛ (Committing Host Effects: 1 Total)
936 - ⚛ (Calling Lifecycle Methods: 0 Total)
937 -
938 -⚛ (React Tree Reconciliation: Completed Root)
939 -
940 -⚛ (Committing Changes)
941 - ⚛ (Committing Snapshot Effects: 0 Total)
942 - ⚛ (Committing Host Effects: 0 Total)
943 - ⚛ (Calling Lifecycle Methods: 0 Total)
944 -"
945 -`;
946 -
947 -exports[`ReactDebugFiberPerf old scheduler warns if async work expires (starvation) 1`] = `
948 -"⛔ (Waiting for async callback... will force flush in 5250 ms) Warning: React was blocked by main thread
949 -
950 -⚛ (React Tree Reconciliation: Completed Root)
951 - ⚛ Foo [mount]
952 -
953 -⚛ (Committing Changes)
954 - ⚛ (Committing Snapshot Effects: 0 Total)
955 - ⚛ (Committing Host Effects: 1 Total)
956 - ⚛ (Calling Lifecycle Methods: 0 Total)
957 -"
958 -`;
959 -
960 -exports[`ReactDebugFiberPerf old scheduler warns on cascading renders from setState 1`] = `
961 -"⚛ (Waiting for async callback... will force flush in 5250 ms)
962 -
963 -// Should print a warning
964 -⚛ (React Tree Reconciliation: Completed Root)
965 - ⚛ Parent [mount]
966 - ⚛ Cascading [mount]
967 -
968 -⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
969 - ⚛ (Committing Snapshot Effects: 0 Total)
970 - ⚛ (Committing Host Effects: 2 Total)
971 - ⚛ (Calling Lifecycle Methods: 1 Total)
972 - ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
973 -
974 -⚛ (React Tree Reconciliation: Completed Root)
975 - ⚛ Cascading [update]
976 -
977 -⚛ (Committing Changes)
978 - ⚛ (Committing Snapshot Effects: 0 Total)
979 - ⚛ (Committing Host Effects: 1 Total)
980 - ⚛ (Calling Lifecycle Methods: 1 Total)
981 -"
982 -`;
983 -
984 -exports[`ReactDebugFiberPerf old scheduler warns on cascading renders from top-level render 1`] = `
480 +exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
481 "⚛ (Waiting for async callback... will force flush in 5250 ms)
482
483 // Rendering the first root
packages/react/src/__tests__/ReactProfiler-test.internal.js
+10 -36
@@ -12,7 +12,6 @@
12
13 let React;
14 let ReactFeatureFlags;
15 -let enableNewScheduler;
15 let ReactNoop;
16 let Scheduler;
17 let ReactCache;
@@ -36,7 +35,6 @@ function loadModules({
35 ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
36 ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
37 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback;
39 - enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
38
39 React = require('react');
40 Scheduler = require('scheduler');
@@ -1354,9 +1352,7 @@ describe('Profiler', () => {
1352 },
1353 );
1354 }).toThrow('Expected error onWorkScheduled');
1357 - if (enableNewScheduler) {
1358 - expect(Scheduler).toFlushAndYield(['Component:fail']);
1359 - }
1355 + expect(Scheduler).toFlushAndYield(['Component:fail']);
1356 throwInOnWorkScheduled = false;
1357 expect(onWorkScheduled).toHaveBeenCalled();
1358
@@ -1391,14 +1387,10 @@ describe('Profiler', () => {
1387 // Errors that happen inside of a subscriber should throw,
1388 throwInOnWorkStarted = true;
1389 expect(Scheduler).toFlushAndThrow('Expected error onWorkStarted');
1394 - if (enableNewScheduler) {
1395 - // Rendering was interrupted by the error that was thrown
1396 - expect(Scheduler).toHaveYielded([]);
1397 - // Rendering continues in the next task
1398 - expect(Scheduler).toFlushAndYield(['Component:text']);
1399 - } else {
1400 - expect(Scheduler).toHaveYielded(['Component:text']);
1401 - }
1390 + // Rendering was interrupted by the error that was thrown
1391 + expect(Scheduler).toHaveYielded([]);
1392 + // Rendering continues in the next task
1393 + expect(Scheduler).toFlushAndYield(['Component:text']);
1394 throwInOnWorkStarted = false;
1395 expect(onWorkStarted).toHaveBeenCalled();
1396
@@ -2389,16 +2381,8 @@ describe('Profiler', () => {
2381 jest.runAllTimers();
2382 await resourcePromise;
2383
2392 - if (enableNewScheduler) {
2393 - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2394 - expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
2395 - } else {
2396 - expect(Scheduler).toHaveYielded([
2397 - 'Promise resolved [loaded]',
2398 - 'AsyncText [loaded]',
2399 - ]);
2400 - }
2401 -
2384 + expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2385 + expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
2386 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
2387 expect(
2388 onInteractionScheduledWorkCompleted,
@@ -2454,9 +2438,7 @@ describe('Profiler', () => {
2438 await resourcePromise;
2439
2440 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2457 - if (enableNewScheduler) {
2458 - expect(Scheduler).toFlushExpired([]);
2459 - }
2441 + expect(Scheduler).toFlushExpired([]);
2442
2443 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2444
@@ -2631,16 +2613,8 @@ describe('Profiler', () => {
2613 jest.advanceTimersByTime(100);
2614 await originalPromise;
2615
2634 - if (enableNewScheduler) {
2635 - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2636 - expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
2637 - } else {
2638 - expect(Scheduler).toHaveYielded([
2639 - 'Promise resolved [loaded]',
2640 - 'AsyncText [loaded]',
2641 - ]);
2642 - }
2643 -
2616 + expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2617 + expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
2618 expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
2619
2620 expect(onRender).toHaveBeenCalledTimes(1);
packages/shared/ReactFeatureFlags.js
-4
@@ -65,9 +65,5 @@ export const warnAboutDeprecatedSetNativeProps = false;
65 // Experimental React Events support. Only used in www builds for now.
66 export const enableEventAPI = false;
67
68 -// Enables rewritten version of ReactFiberScheduler. Added in case we need to
69 -// quickly revert it.
70 -export const enableNewScheduler = false;
71 -
68 // New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
69 export const enableJSXTransformAPI = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -31,7 +31,6 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
31 export const warnAboutDeprecatedLifecycles = true;
32 export const warnAboutDeprecatedSetNativeProps = true;
33 export const enableEventAPI = false;
34 -export const enableNewScheduler = false;
34 export const enableJSXTransformAPI = false;
35
36 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -28,7 +28,6 @@ export const warnAboutShorthandPropertyCollision = false;
28 export const enableSchedulerDebugging = false;
29 export const warnAboutDeprecatedSetNativeProps = false;
30 export const enableEventAPI = false;
31 -export const enableNewScheduler = false;
31 export const enableJSXTransformAPI = false;
32
33 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.new-scheduler.js deleted
-31
@@ -1,31 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow strict
8 - */
9 -
10 -export const enableUserTimingAPI = __DEV__;
11 -export const debugRenderPhaseSideEffects = false;
12 -export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
13 -export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
14 -export const warnAboutDeprecatedLifecycles = true;
15 -export const enableProfilerTimer = __PROFILE__;
16 -export const enableSchedulerTracing = __PROFILE__;
17 -export const enableSuspenseServerRenderer = false; // TODO: __DEV__? Here it might just be false.
18 -export const enableSchedulerDebugging = false;
19 -export function addUserTimingListener() {
20 - throw new Error('Not implemented.');
21 -}
22 -export const disableJavaScriptURLs = false;
23 -export const disableYielding = false;
24 -export const disableInputAttributeSyncing = false;
25 -export const enableStableConcurrentModeAPIs = false;
26 -export const warnAboutShorthandPropertyCollision = false;
27 -export const warnAboutDeprecatedSetNativeProps = false;
28 -export const enableEventAPI = false;
29 -export const enableJSXTransformAPI = false;
30 -
31 -export const enableNewScheduler = true;
packages/shared/forks/ReactFeatureFlags.persistent.js
-1
@@ -28,7 +28,6 @@ export const warnAboutShorthandPropertyCollision = false;
28 export const enableSchedulerDebugging = false;
29 export const warnAboutDeprecatedSetNativeProps = false;
30 export const enableEventAPI = false;
31 -export const enableNewScheduler = false;
31 export const enableJSXTransformAPI = false;
32
33 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -28,7 +28,6 @@ export const warnAboutShorthandPropertyCollision = false;
28 export const enableSchedulerDebugging = false;
29 export const warnAboutDeprecatedSetNativeProps = false;
30 export const enableEventAPI = false;
31 -export const enableNewScheduler = false;
31 export const enableJSXTransformAPI = false;
32
33 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-1
@@ -26,7 +26,6 @@ export const warnAboutDeprecatedSetNativeProps = false;
26 export const disableJavaScriptURLs = false;
27 export const disableYielding = false;
28 export const enableEventAPI = true;
29 -export const enableNewScheduler = false;
29 export const enableJSXTransformAPI = true;
30
31 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js deleted
-40
@@ -1,40 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 -import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www-new-scheduler';
12 -
13 -export {
14 - enableUserTimingAPI,
15 - debugRenderPhaseSideEffects,
16 - debugRenderPhaseSideEffectsForStrictMode,
17 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
18 - warnAboutDeprecatedLifecycles,
19 - enableProfilerTimer,
20 - enableSchedulerTracing,
21 - enableSuspenseServerRenderer,
22 - enableSchedulerDebugging,
23 - addUserTimingListener,
24 - disableJavaScriptURLs,
25 - disableYielding,
26 - disableInputAttributeSyncing,
27 - enableStableConcurrentModeAPIs,
28 - warnAboutShorthandPropertyCollision,
29 - warnAboutDeprecatedSetNativeProps,
30 - enableEventAPI,
31 -} from './ReactFeatureFlags.www';
32 -
33 -export const enableNewScheduler = true;
34 -export const enableJSXTransformAPI = true;
35 -
36 -// Flow magic to verify the exports of this file match the original version.
37 -// eslint-disable-next-line no-unused-vars
38 -type Check<_X, Y: _X, X: Y = _X> = null;
39 -// eslint-disable-next-line no-unused-expressions
40 -(null: Check<FeatureFlagsShimType, FeatureFlagsType>);
packages/shared/forks/ReactFeatureFlags.www.js
-5
@@ -40,11 +40,6 @@ export const enableSuspenseServerRenderer = true;
40
41 export const disableJavaScriptURLs = true;
42
43 -// I've chosen to make this a static flag instead of a dynamic flag controlled
44 -// by a GK so that it doesn't increase bundle size. It should still be easy
45 -// to rollback by reverting the commit that turns this on.
46 -export const enableNewScheduler = false;
47 -
43 let refCount = 0;
44 export function addUserTimingListener() {
45 if (__DEV__) {
scripts/circleci/test_entry_point.sh
-1
@@ -11,7 +11,6 @@ if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
11 COMMANDS_TO_RUN+=('node ./scripts/tasks/flow-ci')
12 COMMANDS_TO_RUN+=('node ./scripts/tasks/eslint')
13 COMMANDS_TO_RUN+=('yarn test --maxWorkers=2')
14 - COMMANDS_TO_RUN+=('yarn test-new-scheduler --maxWorkers=2')
14 COMMANDS_TO_RUN+=('yarn test-persistent --maxWorkers=2')
15 COMMANDS_TO_RUN+=('./scripts/circleci/check_license.sh')
16 COMMANDS_TO_RUN+=('./scripts/circleci/check_modules.sh')
scripts/jest/config.source-new-scheduler.js deleted
-11
@@ -1,11 +0,0 @@
1 -'use strict';
2 -
3 -const baseConfig = require('./config.base');
4 -
5 -module.exports = Object.assign({}, baseConfig, {
6 - setupFiles: [
7 - ...baseConfig.setupFiles,
8 - require.resolve('./setupNewScheduler.js'),
9 - require.resolve('./setupHostConfigs.js'),
10 - ],
11 -});
scripts/jest/setupNewScheduler.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -jest.mock('shared/ReactFeatureFlags', () => {
4 - const ReactFeatureFlags = require.requireActual('shared/ReactFeatureFlags');
5 - ReactFeatureFlags.enableNewScheduler = true;
6 - return ReactFeatureFlags;
7 -});
scripts/rollup/bundles.js
-16
@@ -110,22 +110,6 @@ const bundles = [
110 externals: ['react'],
111 },
112
113 - /******* React DOM (new scheduler) *******/
114 - {
115 - bundleTypes: [
116 - FB_WWW_DEV,
117 - FB_WWW_PROD,
118 - FB_WWW_PROFILING,
119 - NODE_DEV,
120 - NODE_PROD,
121 - NODE_PROFILING,
122 - ],
123 - moduleType: RENDERER,
124 - entry: 'react-dom/unstable-new-scheduler',
125 - global: 'ReactDOMNewScheduler',
126 - externals: ['react'],
127 - },
128 -
113 /******* Test Utils *******/
114 {
115 moduleType: RENDERER_UTILS,
scripts/rollup/forks.js
-19
@@ -7,9 +7,6 @@ const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
7 const UMD_DEV = bundleTypes.UMD_DEV;
8 const UMD_PROD = bundleTypes.UMD_PROD;
9 const UMD_PROFILING = bundleTypes.UMD_PROFILING;
10 -const NODE_DEV = bundleTypes.NODE_DEV;
11 -const NODE_PROD = bundleTypes.NODE_PROD;
12 -const NODE_PROFILING = bundleTypes.NODE_PROFILING;
10 const FB_WWW_DEV = bundleTypes.FB_WWW_DEV;
11 const FB_WWW_PROD = bundleTypes.FB_WWW_PROD;
12 const FB_WWW_PROFILING = bundleTypes.FB_WWW_PROFILING;
@@ -71,22 +68,6 @@ const forks = Object.freeze({
68 // We have a few forks for different environments.
69 'shared/ReactFeatureFlags': (bundleType, entry) => {
70 switch (entry) {
74 - case 'react-dom/unstable-new-scheduler': {
75 - switch (bundleType) {
76 - case FB_WWW_DEV:
77 - case FB_WWW_PROD:
78 - case FB_WWW_PROFILING:
79 - return 'shared/forks/ReactFeatureFlags.www-new-scheduler.js';
80 - case NODE_DEV:
81 - case NODE_PROD:
82 - case NODE_PROFILING:
83 - return 'shared/forks/ReactFeatureFlags.new-scheduler.js';
84 - default:
85 - throw Error(
86 - `Unexpected entry (${entry}) and bundleType (${bundleType})`
87 - );
88 - }
89 - }
71 case 'react-native-renderer':
72 switch (bundleType) {
73 case RN_FB_DEV:
scripts/shared/inlinedHostConfigs.js
+1 -5
@@ -9,11 +9,7 @@
9 module.exports = [
10 {
11 shortName: 'dom',
12 - entryPoints: [
13 - 'react-dom',
14 - 'react-dom/unstable-fizz.node',
15 - 'react-dom/unstable-new-scheduler',
16 - ],
12 + entryPoints: ['react-dom', 'react-dom/unstable-fizz.node'],
13 isFlowTyped: true,
14 isFizzSupported: true,
15 },