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
},