15
ReactIOInfo,
16
ReactStackTrace,
17
ReactCallSite,
18
+ Wakeable,
19
} from 'shared/ReactTypes';
20
21
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
88
SUSPENSE_TREE_OPERATION_REMOVE,
89
SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,
90
SUSPENSE_TREE_OPERATION_RESIZE,
91
+ UNKNOWN_SUSPENDERS_NONE,
92
+ UNKNOWN_SUSPENDERS_REASON_PRODUCTION,
93
+ UNKNOWN_SUSPENDERS_REASON_OLD_VERSION,
94
+ UNKNOWN_SUSPENDERS_REASON_THROWN_PROMISE,
95
} from '../../constants';
96
import {inspectHooksOfFiber} from 'react-debug-tools';
97
import {
301
// Track whether any of the items in suspendedBy are unique this this Suspense boundaries or if they're all
302
// also in the parent sets. This determine whether this could contribute in the loading sequence.
303
hasUniqueSuspenders: boolean,
304
+ // Track whether anything suspended in this boundary that we can't track either because it was using throw
305
+ // a promise, an older version of React or because we're inspecting prod.
306
+ hasUnknownSuspenders: boolean,
307
};
308
309
function createSuspenseNode(
317
rects: null,
318
suspendedBy: new Map(),
319
hasUniqueSuspenders: false,
320
+ hasUnknownSuspenders: false,
321
});
322
}
323
2754
parentSuspenseNode.hasUniqueSuspenders = true;
2755
}
2756
}
2757
+ // We have observed at least one known reason this might have been suspended.
2758
+ parentSuspenseNode.hasUnknownSuspenders = false;
2759
// Suspending right below the root is not attributed to any particular component in UI
2760
// other than the SuspenseNode and the HostRoot's FiberInstance.
2761
const suspendedBy = parentInstance.suspendedBy;
2794
// It can now be marked as having unique suspenders. We can skip its children
2795
// since they'll still be blocked by this one.
2796
node.hasUniqueSuspenders = true;
2797
+ node.hasUnknownSuspenders = false;
2798
} else if (node.firstChild !== null) {
2799
node = node.firstChild;
2800
continue;
3470
insertSuspendedBy(asyncInfo);
3471
}
3472
3473
+ function trackThrownPromisesFromRetryCache(
3474
+ suspenseNode: SuspenseNode,
3475
+ retryCache: ?WeakSet<Wakeable>,
3476
+ ): void {
3477
+ if (retryCache != null) {
3478
+ // If a Suspense boundary ever committed in fallback state with a retryCache, that
3479
+ // suggests that something unique to that boundary was suspensey since otherwise
3480
+ // it wouldn't have thrown and so never created the retryCache.
3481
+ // Unfortunately if we don't have any DEV time debug info or debug thenables then
3482
+ // we have no meta data to show. However, we still mark this Suspense boundary as
3483
+ // participating in the loading sequence since apparently it can suspend.
3484
+ suspenseNode.hasUniqueSuspenders = true;
3485
+ // We have not seen any reason yet for why this suspense node might have been
3486
+ // suspended but it clearly has been at some point. If we later discover a reason
3487
+ // we'll clear this flag again.
3488
+ suspenseNode.hasUnknownSuspenders = true;
3489
+ }
3490
+ }
3491
+
3492
function mountVirtualChildrenRecursively(
3493
firstChild: Fiber,
3494
lastChild: null | Fiber, // non-inclusive
3780
} else if (fiber.tag === SuspenseComponent && OffscreenComponent === -1) {
3781
// Legacy Suspense without the Offscreen wrapper. For the modern Suspense we just handle the
3782
// Offscreen wrapper itself specially.
3783
+ if (newSuspenseNode !== null) {
3784
+ trackThrownPromisesFromRetryCache(newSuspenseNode, fiber.stateNode);
3785
+ }
3786
const isTimedOut = fiber.memoizedState !== null;
3787
if (isTimedOut) {
3788
// Special case: if Suspense mounts in a timed-out state,
3825
'There should always be an Offscreen Fiber child in a Suspense boundary.',
3826
);
3827
}
3828
+
3829
+ trackThrownPromisesFromRetryCache(newSuspenseNode, fiber.stateNode);
3830
+
3831
const fallbackFiber = contentFiber.sibling;
3832
3833
// First update only the Offscreen boundary. I.e. the main content.
4637
const prevWasHidden = isOffscreen && prevFiber.memoizedState !== null;
4638
const nextIsHidden = isOffscreen && nextFiber.memoizedState !== null;
4639
4640
+ if (isLegacySuspense) {
4641
+ if (
4642
+ fiberInstance !== null &&
4643
+ fiberInstance.suspenseNode !== null &&
4644
+ (prevFiber.stateNode === null) !== (nextFiber.stateNode === null)
4645
+ ) {
4646
+ trackThrownPromisesFromRetryCache(
4647
+ fiberInstance.suspenseNode,
4648
+ nextFiber.stateNode,
4649
+ );
4650
+ }
4651
+ }
4652
// The logic below is inspired by the code paths in updateSuspenseComponent()
4653
// inside ReactFiberBeginWork in the React source code.
4654
if (prevDidTimeout && nextDidTimeOut) {
4775
const prevFallbackFiber = prevContentFiber.sibling;
4776
const nextFallbackFiber = nextContentFiber.sibling;
4777
4778
+ if ((prevFiber.stateNode === null) !== (nextFiber.stateNode === null)) {
4779
+ trackThrownPromisesFromRetryCache(
4780
+ fiberInstance.suspenseNode,
4781
+ nextFiber.stateNode,
4782
+ );
4783
+ }
4784
+
4785
// First update only the Offscreen boundary. I.e. the main content.
4786
updateFlags |= updateVirtualChildrenRecursively(
4787
nextContentFiber,
6156
getNearestSuspenseNode(fiberInstance),
6157
);
6158
6159
+ let unknownSuspenders = UNKNOWN_SUSPENDERS_NONE;
6160
+ if (
6161
+ fiberInstance.suspenseNode !== null &&
6162
+ fiberInstance.suspenseNode.hasUnknownSuspenders &&
6163
+ !isTimedOutSuspense
6164
+ ) {
6165
+ // Something unknown threw to suspended this boundary. Let's figure out why that might be.
6166
+ if (renderer.bundleType === 0) {
6167
+ unknownSuspenders = UNKNOWN_SUSPENDERS_REASON_PRODUCTION;
6168
+ } else if (!('_debugInfo' in fiber)) {
6169
+ // TODO: We really should detect _debugThenable and the auto-instrumentation for lazy/thenables too.
6170
+ unknownSuspenders = UNKNOWN_SUSPENDERS_REASON_OLD_VERSION;
6171
+ } else {
6172
+ unknownSuspenders = UNKNOWN_SUSPENDERS_REASON_THROWN_PROMISE;
6173
+ }
6174
+ }
6175
+
6176
return {
6177
id: fiberInstance.id,
6178
6237
6238
suspendedBy: suspendedBy,
6239
suspendedByRange: suspendedByRange,
6240
+ unknownSuspenders: unknownSuspenders,
6241
6242
// List of owners
6243
owners,
6354
serializeAsyncInfo(info, virtualInstance, null),
6355
),
6356
suspendedByRange: suspendedByRange,
6357
+ unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
6358
6359
// List of owners
6360
owners,