147
type FiberInstance = {
148
kind: 0,
149
id: number,
150
- parent: null | DevToolsInstance, // virtual parent
150
+ parent: null | DevToolsInstance, // filtered parent, including virtual
151
+ firstChild: null | DevToolsInstance, // filtered first child, including virtual
152
+ previousSibling: null | DevToolsInstance, // filtered next sibling, including virtual
153
+ nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
154
flags: number, // Force Error/Suspense
155
componentStack: null | string,
156
errors: null | Map<string, number>, // error messages and count
160
161
function createFiberInstance(fiber: Fiber): FiberInstance {
162
return {
160
- kind: 0,
163
+ kind: FIBER_INSTANCE,
164
id: getUID(),
165
parent: null,
166
+ firstChild: null,
167
+ previousSibling: null,
168
+ nextSibling: null,
169
flags: 0,
170
componentStack: null,
171
errors: null,
182
type VirtualInstance = {
183
kind: 1,
184
id: number,
179
- parent: null | DevToolsInstance, // virtual parent
185
+ parent: null | DevToolsInstance, // filtered parent, including virtual
186
+ firstChild: null | DevToolsInstance, // filtered first child, including virtual
187
+ previousSibling: null | DevToolsInstance, // filtered next sibling, including virtual
188
+ nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
189
flags: number,
190
componentStack: null | string,
191
// Errors and Warnings happen per ReactComponentInfo which can appear in
1040
}
1041
};
1042
1043
+ // eslint-disable-next-line no-unused-vars
1044
+ function debugTree(instance: DevToolsInstance, indent: number = 0) {
1045
+ if (__DEBUG__) {
1046
+ const name =
1047
+ (instance.kind === FIBER_INSTANCE
1048
+ ? getDisplayNameForFiber(instance.data)
1049
+ : instance.data.name) || '';
1050
+ console.log(
1051
+ ' '.repeat(indent) + '- ' + instance.id + ' (' + name + ')',
1052
+ 'parent',
1053
+ instance.parent === null ? ' ' : instance.parent.id,
1054
+ 'prev',
1055
+ instance.previousSibling === null ? ' ' : instance.previousSibling.id,
1056
+ 'next',
1057
+ instance.nextSibling === null ? ' ' : instance.nextSibling.id,
1058
+ );
1059
+ let child = instance.firstChild;
1060
+ while (child !== null) {
1061
+ debugTree(child, indent + 1);
1062
+ child = child.nextSibling;
1063
+ }
1064
+ }
1065
+ }
1066
+
1067
// Configurable Components tree filters.
1068
const hideElementsWithDisplayNames: Set<RegExp> = new Set();
1069
const hideElementsWithPaths: Set<RegExp> = new Set();
1161
hook.getFiberRoots(rendererID).forEach(root => {
1162
currentRootID = getOrGenerateFiberInstance(root.current).id;
1163
setRootPseudoKey(currentRootID, root.current);
1131
- mountFiberRecursively(root.current, null, false);
1164
+ mountFiberRecursively(root.current, false);
1165
flushPendingEvents(root);
1166
currentRootID = -1;
1167
});
2129
debug('recordMount()', fiber, parentInstance);
2130
}
2131
2099
- // We're placing it in its parent below.
2100
- fiberInstance.parent = parentInstance;
2101
-
2132
const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
2133
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
2134
2227
return fiberInstance;
2228
}
2229
2200
- function recordUnmount(fiber: Fiber, isSimulated: boolean) {
2230
+ function recordUnmount(
2231
+ fiber: Fiber,
2232
+ isSimulated: boolean,
2233
+ ): null | FiberInstance {
2234
if (__DEBUG__) {
2235
debug(
2236
'recordUnmount()',
2261
// This also might indicate a Fast Refresh force-remount scenario.
2262
//
2263
// TODO: This is fragile and can obscure actual bugs.
2231
- return;
2264
+ return null;
2265
}
2266
2234
- // We're about to remove this from its parent.
2235
- fiberInstance.parent = null;
2236
-
2267
const id = fiberInstance.id;
2268
const isRoot = fiber.tag === HostRoot;
2269
if (isRoot) {
2290
idToTreeBaseDurationMap.delete(id);
2291
}
2292
}
2293
+ return fiberInstance;
2294
+ }
2295
+
2296
+ // Running state of the remaining children from the previous version of this parent that
2297
+ // we haven't yet added back. This should be reset anytime we change parent.
2298
+ // Any remaining ones at the end will be deleted.
2299
+ let remainingReconcilingChildren: null | DevToolsInstance = null;
2300
+ // The previously placed child.
2301
+ let previouslyReconciledSibling: null | DevToolsInstance = null;
2302
+ // To save on stack allocation and ensure that they are updated as a pair, we also store
2303
+ // the current parent here as well.
2304
+ let reconcilingParent: null | DevToolsInstance = null;
2305
+
2306
+ function insertChild(instance: DevToolsInstance): void {
2307
+ const parentInstance = reconcilingParent;
2308
+ if (parentInstance === null) {
2309
+ // This instance is at the root.
2310
+ return;
2311
+ }
2312
+ // Place it in the parent.
2313
+ instance.parent = parentInstance;
2314
+ if (previouslyReconciledSibling === null) {
2315
+ previouslyReconciledSibling = instance;
2316
+ parentInstance.firstChild = instance;
2317
+ instance.previousSibling = null;
2318
+ } else {
2319
+ previouslyReconciledSibling.nextSibling = instance;
2320
+ instance.previousSibling = previouslyReconciledSibling;
2321
+ previouslyReconciledSibling = instance;
2322
+ }
2323
+ instance.nextSibling = null;
2324
+ }
2325
+
2326
+ function moveChild(instance: DevToolsInstance): void {
2327
+ removeChild(instance);
2328
+ insertChild(instance);
2329
+ }
2330
+
2331
+ function removeChild(instance: DevToolsInstance): void {
2332
+ if (instance.parent === null) {
2333
+ // Already deleted.
2334
+ return;
2335
+ }
2336
+ const parentInstance = reconcilingParent;
2337
+ if (parentInstance === null) {
2338
+ throw new Error('Should not have a parent if we are at the root');
2339
+ }
2340
+ if (instance.parent !== parentInstance) {
2341
+ throw new Error(
2342
+ 'Cannot remove a node from a different parent than is being reconciled.',
2343
+ );
2344
+ }
2345
+ // Remove an existing child from its current position, which we assume is in the
2346
+ // remainingReconcilingChildren set.
2347
+ if (instance.previousSibling === null) {
2348
+ // We're first in the remaining set. Remove us.
2349
+ if (remainingReconcilingChildren !== instance) {
2350
+ throw new Error(
2351
+ 'Expected a placed child to be moved from the remaining set.',
2352
+ );
2353
+ }
2354
+ remainingReconcilingChildren = instance.nextSibling;
2355
+ } else {
2356
+ instance.previousSibling.nextSibling = instance.nextSibling;
2357
+ }
2358
+ if (instance.nextSibling !== null) {
2359
+ instance.nextSibling.previousSibling = instance.previousSibling;
2360
+ }
2361
+ instance.nextSibling = null;
2362
+ instance.previousSibling = null;
2363
+ instance.parent = null;
2364
}
2365
2366
function mountChildrenRecursively(
2367
firstChild: Fiber,
2267
- parentInstance: DevToolsInstance | null,
2368
traceNearestHostComponentUpdate: boolean,
2369
): void {
2370
// Iterate over siblings rather than recursing.
2371
// This reduces the chance of stack overflow for wide trees (e.g. lists with many items).
2372
let fiber: Fiber | null = firstChild;
2373
while (fiber !== null) {
2274
- mountFiberRecursively(
2275
- fiber,
2276
- parentInstance,
2277
- traceNearestHostComponentUpdate,
2278
- );
2374
+ mountFiberRecursively(fiber, traceNearestHostComponentUpdate);
2375
fiber = fiber.sibling;
2376
}
2377
}
2378
2379
function mountFiberRecursively(
2380
fiber: Fiber,
2285
- parentInstance: DevToolsInstance | null,
2381
traceNearestHostComponentUpdate: boolean,
2382
): void {
2383
// Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
2385
getOrGenerateFiberInstance(fiber);
2386
2387
if (__DEBUG__) {
2293
- debug('mountFiberRecursively()', fiber, parentInstance);
2388
+ debug('mountFiberRecursively()', fiber, reconcilingParent);
2389
}
2390
2391
// If we have the tree selection from previous reload, try to match this Fiber.
2394
updateTrackedPathStateBeforeMount(fiber);
2395
2396
const shouldIncludeInTree = !shouldFilterFiber(fiber);
2302
- const newParentInstance = shouldIncludeInTree
2303
- ? recordMount(fiber, parentInstance)
2304
- : parentInstance;
2305
-
2306
- if (traceUpdatesEnabled) {
2307
- if (traceNearestHostComponentUpdate) {
2308
- const elementType = getElementTypeForFiber(fiber);
2309
- // If an ancestor updated, we should mark the nearest host nodes for highlighting.
2310
- if (elementType === ElementTypeHostComponent) {
2311
- traceUpdatesForNodes.add(fiber.stateNode);
2312
- traceNearestHostComponentUpdate = false;
2397
+ let newInstance = null;
2398
+ if (shouldIncludeInTree) {
2399
+ newInstance = recordMount(fiber, reconcilingParent);
2400
+ insertChild(newInstance);
2401
+ }
2402
+ const stashedParent = reconcilingParent;
2403
+ const stashedPrevious = previouslyReconciledSibling;
2404
+ const stashedRemaining = remainingReconcilingChildren;
2405
+ if (shouldIncludeInTree) {
2406
+ // Push a new DevTools instance parent while reconciling this subtree.
2407
+ reconcilingParent = newInstance;
2408
+ previouslyReconciledSibling = null;
2409
+ remainingReconcilingChildren = null;
2410
+ }
2411
+ try {
2412
+ if (traceUpdatesEnabled) {
2413
+ if (traceNearestHostComponentUpdate) {
2414
+ const elementType = getElementTypeForFiber(fiber);
2415
+ // If an ancestor updated, we should mark the nearest host nodes for highlighting.
2416
+ if (elementType === ElementTypeHostComponent) {
2417
+ traceUpdatesForNodes.add(fiber.stateNode);
2418
+ traceNearestHostComponentUpdate = false;
2419
+ }
2420
}
2314
- }
2421
2316
- // We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch,
2317
- // because we don't want to highlight every host node inside of a newly mounted subtree.
2318
- }
2422
+ // We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch,
2423
+ // because we don't want to highlight every host node inside of a newly mounted subtree.
2424
+ }
2425
2320
- if (fiber.tag === HostHoistable) {
2321
- aquireHostResource(fiber, fiber.memoizedState);
2322
- }
2426
+ if (fiber.tag === HostHoistable) {
2427
+ aquireHostResource(fiber, fiber.memoizedState);
2428
+ }
2429
2324
- if (fiber.tag === SuspenseComponent) {
2325
- const isTimedOut = fiber.memoizedState !== null;
2326
- if (isTimedOut) {
2327
- // Special case: if Suspense mounts in a timed-out state,
2328
- // get the fallback child from the inner fragment and mount
2329
- // it as if it was our own child. Updates handle this too.
2330
- const primaryChildFragment = fiber.child;
2331
- const fallbackChildFragment = primaryChildFragment
2332
- ? primaryChildFragment.sibling
2333
- : null;
2334
- const fallbackChild = fallbackChildFragment
2335
- ? fallbackChildFragment.child
2336
- : null;
2337
- if (fallbackChild !== null) {
2338
- mountChildrenRecursively(
2339
- fallbackChild,
2340
- newParentInstance,
2341
- traceNearestHostComponentUpdate,
2342
- );
2430
+ if (fiber.tag === SuspenseComponent) {
2431
+ const isTimedOut = fiber.memoizedState !== null;
2432
+ if (isTimedOut) {
2433
+ // Special case: if Suspense mounts in a timed-out state,
2434
+ // get the fallback child from the inner fragment and mount
2435
+ // it as if it was our own child. Updates handle this too.
2436
+ const primaryChildFragment = fiber.child;
2437
+ const fallbackChildFragment = primaryChildFragment
2438
+ ? primaryChildFragment.sibling
2439
+ : null;
2440
+ const fallbackChild = fallbackChildFragment
2441
+ ? fallbackChildFragment.child
2442
+ : null;
2443
+ if (fallbackChild !== null) {
2444
+ mountChildrenRecursively(
2445
+ fallbackChild,
2446
+ traceNearestHostComponentUpdate,
2447
+ );
2448
+ }
2449
+ } else {
2450
+ let primaryChild: Fiber | null = null;
2451
+ const areSuspenseChildrenConditionallyWrapped =
2452
+ OffscreenComponent === -1;
2453
+ if (areSuspenseChildrenConditionallyWrapped) {
2454
+ primaryChild = fiber.child;
2455
+ } else if (fiber.child !== null) {
2456
+ primaryChild = fiber.child.child;
2457
+ }
2458
+ if (primaryChild !== null) {
2459
+ mountChildrenRecursively(
2460
+ primaryChild,
2461
+ traceNearestHostComponentUpdate,
2462
+ );
2463
+ }
2464
}
2465
} else {
2345
- let primaryChild: Fiber | null = null;
2346
- const areSuspenseChildrenConditionallyWrapped =
2347
- OffscreenComponent === -1;
2348
- if (areSuspenseChildrenConditionallyWrapped) {
2349
- primaryChild = fiber.child;
2350
- } else if (fiber.child !== null) {
2351
- primaryChild = fiber.child.child;
2352
- }
2353
- if (primaryChild !== null) {
2466
+ if (fiber.child !== null) {
2467
mountChildrenRecursively(
2355
- primaryChild,
2356
- newParentInstance,
2468
+ fiber.child,
2469
traceNearestHostComponentUpdate,
2470
);
2471
}
2472
}
2361
- } else {
2362
- if (fiber.child !== null) {
2363
- mountChildrenRecursively(
2364
- fiber.child,
2365
- newParentInstance,
2366
- traceNearestHostComponentUpdate,
2367
- );
2473
+ } finally {
2474
+ if (shouldIncludeInTree) {
2475
+ reconcilingParent = stashedParent;
2476
+ previouslyReconciledSibling = stashedPrevious;
2477
+ remainingReconcilingChildren = stashedRemaining;
2478
}
2479
}
2480
2490
debug('unmountFiberRecursively()', fiber, null);
2491
}
2492
2383
- // We might meet a nested Suspense on our way.
2384
- const isTimedOutSuspense =
2385
- fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
2493
+ let fiberInstance = null;
2494
2387
- if (fiber.tag === HostHoistable) {
2388
- releaseHostResource(fiber, fiber.memoizedState);
2495
+ const shouldIncludeInTree = !shouldFilterFiber(fiber);
2496
+ const stashedParent = reconcilingParent;
2497
+ const stashedPrevious = previouslyReconciledSibling;
2498
+ const stashedRemaining = remainingReconcilingChildren;
2499
+ if (shouldIncludeInTree) {
2500
+ fiberInstance = getFiberInstanceThrows(fiber);
2501
+ // Push a new DevTools instance parent while reconciling this subtree.
2502
+ reconcilingParent = fiberInstance;
2503
+ previouslyReconciledSibling = null;
2504
+ // Move all the children of this instance to the remaining set.
2505
+ // We'll move them back one by one, and anything that remains is deleted.
2506
+ remainingReconcilingChildren = fiberInstance.firstChild;
2507
+ fiberInstance.firstChild = null;
2508
}
2509
+ try {
2510
+ // We might meet a nested Suspense on our way.
2511
+ const isTimedOutSuspense =
2512
+ fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
2513
2391
- let child = fiber.child;
2392
- if (isTimedOutSuspense) {
2393
- // If it's showing fallback tree, let's traverse it instead.
2394
- const primaryChildFragment = fiber.child;
2395
- const fallbackChildFragment = primaryChildFragment
2396
- ? primaryChildFragment.sibling
2397
- : null;
2398
- // Skip over to the real Fiber child.
2399
- child = fallbackChildFragment ? fallbackChildFragment.child : null;
2400
- }
2514
+ if (fiber.tag === HostHoistable) {
2515
+ releaseHostResource(fiber, fiber.memoizedState);
2516
+ }
2517
+
2518
+ let child = fiber.child;
2519
+ if (isTimedOutSuspense) {
2520
+ // If it's showing fallback tree, let's traverse it instead.
2521
+ const primaryChildFragment = fiber.child;
2522
+ const fallbackChildFragment = primaryChildFragment
2523
+ ? primaryChildFragment.sibling
2524
+ : null;
2525
+ // Skip over to the real Fiber child.
2526
+ child = fallbackChildFragment ? fallbackChildFragment.child : null;
2527
+ }
2528
2402
- unmountChildrenRecursively(child);
2529
+ unmountChildrenRecursively(child);
2530
+ } finally {
2531
+ if (shouldIncludeInTree) {
2532
+ reconcilingParent = stashedParent;
2533
+ previouslyReconciledSibling = stashedPrevious;
2534
+ remainingReconcilingChildren = stashedRemaining;
2535
+ }
2536
+ }
2537
+ if (fiberInstance !== null) {
2538
+ recordUnmount(fiber, true);
2539
+ removeChild(fiberInstance);
2540
+ }
2541
}
2542
2543
function unmountChildrenRecursively(firstChild: null | Fiber) {
2547
// We skip nodes without return because those are real unmounts.
2548
if (child.return !== null) {
2549
unmountFiberRecursively(child);
2412
- recordUnmount(child, true);
2550
}
2551
child = child.sibling;
2552
}
2688
function updateChildrenRecursively(
2689
nextFirstChild: null | Fiber,
2690
prevFirstChild: null | Fiber,
2554
- parentInstance: DevToolsInstance | null,
2691
traceNearestHostComponentUpdate: boolean,
2692
): boolean {
2693
let shouldResetChildren = false;
2700
// they are either new mounts or alternates of previous children.
2701
// Schedule updates and mounts depending on whether alternates exist.
2702
// We don't track deletions here because they are reported separately.
2567
- if (nextChild.alternate) {
2703
+ if (prevChildAtSameIndex === nextChild) {
2704
+ // This set is unchanged. We're just going through it to place all the
2705
+ // children again.
2706
+ if (
2707
+ updateFiberRecursively(
2708
+ nextChild,
2709
+ nextChild,
2710
+ traceNearestHostComponentUpdate,
2711
+ )
2712
+ ) {
2713
+ throw new Error('Updating the same fiber should not cause reorder');
2714
+ }
2715
+ } else if (nextChild.alternate) {
2716
const prevChild = nextChild.alternate;
2717
if (
2718
updateFiberRecursively(
2719
nextChild,
2720
prevChild,
2573
- parentInstance,
2721
traceNearestHostComponentUpdate,
2722
)
2723
) {
2733
shouldResetChildren = true;
2734
}
2735
} else {
2589
- mountFiberRecursively(
2590
- nextChild,
2591
- parentInstance,
2592
- traceNearestHostComponentUpdate,
2593
- );
2736
+ mountFiberRecursively(nextChild, traceNearestHostComponentUpdate);
2737
shouldResetChildren = true;
2738
}
2739
// Try the next child.
2755
function updateFiberRecursively(
2756
nextFiber: Fiber,
2757
prevFiber: Fiber,
2615
- parentInstance: DevToolsInstance | null,
2758
traceNearestHostComponentUpdate: boolean,
2759
): boolean {
2760
// TODO: Do we really need to give this an instance eagerly if it's filtered?
2762
const id = fiberInstance.id;
2763
2764
if (__DEBUG__) {
2623
- debug('updateFiberRecursively()', nextFiber, parentInstance);
2765
+ debug('updateFiberRecursively()', nextFiber, reconcilingParent);
2766
}
2767
2768
if (traceUpdatesEnabled) {
2801
}
2802
2803
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
2662
- const newParentInstance = shouldIncludeInTree
2663
- ? fiberInstance
2664
- : parentInstance;
2665
-
2666
- if (nextFiber.tag === HostHoistable) {
2667
- releaseHostResource(prevFiber, prevFiber.memoizedState);
2668
- aquireHostResource(nextFiber, nextFiber.memoizedState);
2804
+ if (shouldIncludeInTree) {
2805
+ // Update the Fiber so we that we always keep the current Fiber on the data.
2806
+ fiberInstance.data = nextFiber;
2807
+ moveChild(fiberInstance);
2808
}
2809
+ const stashedParent = reconcilingParent;
2810
+ const stashedPrevious = previouslyReconciledSibling;
2811
+ const stashedRemaining = remainingReconcilingChildren;
2812
+ if (shouldIncludeInTree) {
2813
+ // Push a new DevTools instance parent while reconciling this subtree.
2814
+ reconcilingParent = fiberInstance;
2815
+ previouslyReconciledSibling = null;
2816
+ // Move all the children of this instance to the remaining set.
2817
+ // We'll move them back one by one, and anything that remains is deleted.
2818
+ remainingReconcilingChildren = fiberInstance.firstChild;
2819
+ fiberInstance.firstChild = null;
2820
+ }
2821
+ try {
2822
+ if (nextFiber.tag === HostHoistable) {
2823
+ releaseHostResource(prevFiber, prevFiber.memoizedState);
2824
+ aquireHostResource(nextFiber, nextFiber.memoizedState);
2825
+ }
2826
2671
- const isSuspense = nextFiber.tag === SuspenseComponent;
2672
- let shouldResetChildren = false;
2673
- // The behavior of timed-out Suspense trees is unique.
2674
- // Rather than unmount the timed out content (and possibly lose important state),
2675
- // React re-parents this content within a hidden Fragment while the fallback is showing.
2676
- // This behavior doesn't need to be observable in the DevTools though.
2677
- // It might even result in a bad user experience for e.g. node selection in the Elements panel.
2678
- // The easiest fix is to strip out the intermediate Fragment fibers,
2679
- // so the Elements panel and Profiler don't need to special case them.
2680
- // Suspense components only have a non-null memoizedState if they're timed-out.
2681
- const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
2682
- const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
2683
- // The logic below is inspired by the code paths in updateSuspenseComponent()
2684
- // inside ReactFiberBeginWork in the React source code.
2685
- if (prevDidTimeout && nextDidTimeOut) {
2686
- // Fallback -> Fallback:
2687
- // 1. Reconcile fallback set.
2688
- const nextFiberChild = nextFiber.child;
2689
- const nextFallbackChildSet = nextFiberChild
2690
- ? nextFiberChild.sibling
2691
- : null;
2692
- // Note: We can't use nextFiber.child.sibling.alternate
2693
- // because the set is special and alternate may not exist.
2694
- const prevFiberChild = prevFiber.child;
2695
- const prevFallbackChildSet = prevFiberChild
2696
- ? prevFiberChild.sibling
2697
- : null;
2827
+ const isSuspense = nextFiber.tag === SuspenseComponent;
2828
+ let shouldResetChildren = false;
2829
+ // The behavior of timed-out Suspense trees is unique.
2830
+ // Rather than unmount the timed out content (and possibly lose important state),
2831
+ // React re-parents this content within a hidden Fragment while the fallback is showing.
2832
+ // This behavior doesn't need to be observable in the DevTools though.
2833
+ // It might even result in a bad user experience for e.g. node selection in the Elements panel.
2834
+ // The easiest fix is to strip out the intermediate Fragment fibers,
2835
+ // so the Elements panel and Profiler don't need to special case them.
2836
+ // Suspense components only have a non-null memoizedState if they're timed-out.
2837
+ const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
2838
+ const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
2839
+ // The logic below is inspired by the code paths in updateSuspenseComponent()
2840
+ // inside ReactFiberBeginWork in the React source code.
2841
+ if (prevDidTimeout && nextDidTimeOut) {
2842
+ // Fallback -> Fallback:
2843
+ // 1. Reconcile fallback set.
2844
+ const nextFiberChild = nextFiber.child;
2845
+ const nextFallbackChildSet = nextFiberChild
2846
+ ? nextFiberChild.sibling
2847
+ : null;
2848
+ // Note: We can't use nextFiber.child.sibling.alternate
2849
+ // because the set is special and alternate may not exist.
2850
+ const prevFiberChild = prevFiber.child;
2851
+ const prevFallbackChildSet = prevFiberChild
2852
+ ? prevFiberChild.sibling
2853
+ : null;
2854
2699
- if (prevFallbackChildSet == null && nextFallbackChildSet != null) {
2700
- mountChildrenRecursively(
2701
- nextFallbackChildSet,
2702
- newParentInstance,
2703
- traceNearestHostComponentUpdate,
2704
- );
2855
+ if (prevFallbackChildSet == null && nextFallbackChildSet != null) {
2856
+ mountChildrenRecursively(
2857
+ nextFallbackChildSet,
2858
+ traceNearestHostComponentUpdate,
2859
+ );
2860
2706
- shouldResetChildren = true;
2707
- }
2861
+ shouldResetChildren = true;
2862
+ }
2863
2709
- if (
2710
- nextFallbackChildSet != null &&
2711
- prevFallbackChildSet != null &&
2712
- updateFiberRecursively(
2713
- nextFallbackChildSet,
2714
- prevFallbackChildSet,
2715
- newParentInstance,
2716
- traceNearestHostComponentUpdate,
2717
- )
2718
- ) {
2719
- shouldResetChildren = true;
2720
- }
2721
- } else if (prevDidTimeout && !nextDidTimeOut) {
2722
- // Fallback -> Primary:
2723
- // 1. Unmount fallback set
2724
- // Note: don't emulate fallback unmount because React actually did it.
2725
- // 2. Mount primary set
2726
- const nextPrimaryChildSet = nextFiber.child;
2727
- if (nextPrimaryChildSet !== null) {
2728
- mountChildrenRecursively(
2729
- nextPrimaryChildSet,
2730
- newParentInstance,
2731
- traceNearestHostComponentUpdate,
2732
- );
2733
- }
2734
- shouldResetChildren = true;
2735
- } else if (!prevDidTimeout && nextDidTimeOut) {
2736
- // Primary -> Fallback:
2737
- // 1. Hide primary set
2738
- // This is not a real unmount, so it won't get reported by React.
2739
- // We need to manually walk the previous tree and record unmounts.
2740
- unmountFiberRecursively(prevFiber);
2741
- // 2. Mount fallback set
2742
- const nextFiberChild = nextFiber.child;
2743
- const nextFallbackChildSet = nextFiberChild
2744
- ? nextFiberChild.sibling
2745
- : null;
2746
- if (nextFallbackChildSet != null) {
2747
- mountChildrenRecursively(
2748
- nextFallbackChildSet,
2749
- newParentInstance,
2750
- traceNearestHostComponentUpdate,
2751
- );
2752
- shouldResetChildren = true;
2753
- }
2754
- } else {
2755
- // Common case: Primary -> Primary.
2756
- // This is the same code path as for non-Suspense fibers.
2757
- if (nextFiber.child !== prevFiber.child) {
2864
if (
2759
- updateChildrenRecursively(
2760
- nextFiber.child,
2761
- prevFiber.child,
2762
- newParentInstance,
2865
+ nextFallbackChildSet != null &&
2866
+ prevFallbackChildSet != null &&
2867
+ updateFiberRecursively(
2868
+ nextFallbackChildSet,
2869
+ prevFallbackChildSet,
2870
traceNearestHostComponentUpdate,
2871
)
2872
) {
2873
shouldResetChildren = true;
2874
}
2875
+ } else if (prevDidTimeout && !nextDidTimeOut) {
2876
+ // Fallback -> Primary:
2877
+ // 1. Unmount fallback set
2878
+ // Note: don't emulate fallback unmount because React actually did it.
2879
+ // 2. Mount primary set
2880
+ const nextPrimaryChildSet = nextFiber.child;
2881
+ if (nextPrimaryChildSet !== null) {
2882
+ mountChildrenRecursively(
2883
+ nextPrimaryChildSet,
2884
+ traceNearestHostComponentUpdate,
2885
+ );
2886
+ }
2887
+ shouldResetChildren = true;
2888
+ } else if (!prevDidTimeout && nextDidTimeOut) {
2889
+ // Primary -> Fallback:
2890
+ // 1. Hide primary set
2891
+ // This is not a real unmount, so it won't get reported by React.
2892
+ // We need to manually walk the previous tree and record unmounts.
2893
+ unmountChildrenRecursively(prevFiber.child);
2894
+ // 2. Mount fallback set
2895
+ const nextFiberChild = nextFiber.child;
2896
+ const nextFallbackChildSet = nextFiberChild
2897
+ ? nextFiberChild.sibling
2898
+ : null;
2899
+ if (nextFallbackChildSet != null) {
2900
+ mountChildrenRecursively(
2901
+ nextFallbackChildSet,
2902
+ traceNearestHostComponentUpdate,
2903
+ );
2904
+ shouldResetChildren = true;
2905
+ }
2906
} else {
2769
- if (traceUpdatesEnabled) {
2770
- // If we're tracing updates and we've bailed out before reaching a host node,
2771
- // we should fall back to recursively marking the nearest host descendants for highlight.
2772
- if (traceNearestHostComponentUpdate) {
2773
- const hostInstances = findAllCurrentHostInstances(
2774
- getFiberInstanceThrows(nextFiber),
2775
- );
2776
- hostInstances.forEach(hostInstance => {
2777
- traceUpdatesForNodes.add(hostInstance);
2778
- });
2907
+ // Common case: Primary -> Primary.
2908
+ // This is the same code path as for non-Suspense fibers.
2909
+ if (nextFiber.child !== prevFiber.child) {
2910
+ if (
2911
+ updateChildrenRecursively(
2912
+ nextFiber.child,
2913
+ prevFiber.child,
2914
+ traceNearestHostComponentUpdate,
2915
+ )
2916
+ ) {
2917
+ shouldResetChildren = true;
2918
+ }
2919
+ } else {
2920
+ // Children are unchanged.
2921
+ if (shouldIncludeInTree) {
2922
+ // All the remaining children will be children of this same fiber so we can just reuse them.
2923
+ // I.e. we just restore them by undoing what we did above.
2924
+ fiberInstance.firstChild = remainingReconcilingChildren;
2925
+ } else {
2926
+ // If this fiber is filtered there might be changes to this set elsewhere so we have
2927
+ // to visit each child to place it back in the set. We let the child bail out instead.
2928
+ if (
2929
+ updateChildrenRecursively(nextFiber.child, prevFiber.child, false)
2930
+ ) {
2931
+ throw new Error(
2932
+ 'The children should not have changed if we pass in the same set.',
2933
+ );
2934
+ }
2935
+ }
2936
+
2937
+ if (traceUpdatesEnabled) {
2938
+ // If we're tracing updates and we've bailed out before reaching a host node,
2939
+ // we should fall back to recursively marking the nearest host descendants for highlight.
2940
+ if (traceNearestHostComponentUpdate) {
2941
+ const hostInstances = findAllCurrentHostInstances(
2942
+ getFiberInstanceThrows(nextFiber),
2943
+ );
2944
+ hostInstances.forEach(hostInstance => {
2945
+ traceUpdatesForNodes.add(hostInstance);
2946
+ });
2947
+ }
2948
}
2949
}
2950
}
2782
- }
2951
2784
- if (shouldIncludeInTree) {
2785
- const isProfilingSupported = nextFiber.hasOwnProperty('treeBaseDuration');
2786
- if (isProfilingSupported) {
2787
- recordProfilingDurations(nextFiber);
2788
- }
2789
- }
2790
- if (shouldResetChildren) {
2791
- // We need to crawl the subtree for closest non-filtered Fibers
2792
- // so that we can display them in a flat children set.
2952
if (shouldIncludeInTree) {
2794
- // Normally, search for children from the rendered child.
2795
- let nextChildSet = nextFiber.child;
2796
- if (nextDidTimeOut) {
2797
- // Special case: timed-out Suspense renders the fallback set.
2798
- const nextFiberChild = nextFiber.child;
2799
- nextChildSet = nextFiberChild ? nextFiberChild.sibling : null;
2953
+ const isProfilingSupported =
2954
+ nextFiber.hasOwnProperty('treeBaseDuration');
2955
+ if (isProfilingSupported) {
2956
+ recordProfilingDurations(nextFiber);
2957
}
2801
- if (nextChildSet != null) {
2802
- recordResetChildren(fiberInstance, nextChildSet);
2958
+ }
2959
+ if (shouldResetChildren) {
2960
+ // We need to crawl the subtree for closest non-filtered Fibers
2961
+ // so that we can display them in a flat children set.
2962
+ if (shouldIncludeInTree) {
2963
+ // Normally, search for children from the rendered child.
2964
+ let nextChildSet = nextFiber.child;
2965
+ if (nextDidTimeOut) {
2966
+ // Special case: timed-out Suspense renders the fallback set.
2967
+ const nextFiberChild = nextFiber.child;
2968
+ nextChildSet = nextFiberChild ? nextFiberChild.sibling : null;
2969
+ }
2970
+ if (nextChildSet != null) {
2971
+ if (reconcilingParent !== null) {
2972
+ recordResetChildren(reconcilingParent, nextChildSet);
2973
+ }
2974
+ }
2975
+ // We've handled the child order change for this Fiber.
2976
+ // Since it's included, there's no need to invalidate parent child order.
2977
+ return false;
2978
+ } else {
2979
+ // Let the closest unfiltered parent Fiber reset its child order instead.
2980
+ return true;
2981
}
2804
- // We've handled the child order change for this Fiber.
2805
- // Since it's included, there's no need to invalidate parent child order.
2806
- return false;
2982
} else {
2808
- // Let the closest unfiltered parent Fiber reset its child order instead.
2809
- return true;
2983
+ return false;
2984
+ }
2985
+ } finally {
2986
+ if (shouldIncludeInTree) {
2987
+ reconcilingParent = stashedParent;
2988
+ previouslyReconciledSibling = stashedPrevious;
2989
+ remainingReconcilingChildren = stashedRemaining;
2990
}
2811
- } else {
2812
- return false;
2991
}
2992
}
2993
3052
};
3053
}
3054
2877
- mountFiberRecursively(root.current, null, false);
3055
+ mountFiberRecursively(root.current, false);
3056
flushPendingEvents(root);
3057
currentRootID = -1;
3058
});
3151
if (!wasMounted && isMounted) {
3152
// Mount a new root.
3153
setRootPseudoKey(currentRootID, current);
2976
- mountFiberRecursively(current, null, false);
3154
+ mountFiberRecursively(current, false);
3155
} else if (wasMounted && isMounted) {
3156
// Update an existing root.
2979
- updateFiberRecursively(current, alternate, null, false);
3157
+ updateFiberRecursively(current, alternate, false);
3158
} else if (wasMounted && !isMounted) {
3159
// Unmount an existing root.
3160
removeRootPseudoKey(currentRootID);
3163
} else {
3164
// Mount a new root.
3165
setRootPseudoKey(currentRootID, current);
2988
- mountFiberRecursively(current, null, false);
3166
+ mountFiberRecursively(current, false);
3167
}
3168
3169
if (isProfiling && isProfilingSupported) {