959
const debug = (
960
name: string,
961
fiber: Fiber,
962
- parentFiber: ?Fiber,
962
+ parentInstance: null | DevToolsInstance,
963
extraString: string = '',
964
): void => {
965
if (__DEBUG__) {
967
fiber.tag + ':' + (getDisplayNameForFiber(fiber) || 'null');
968
969
const maybeID = getFiberIDUnsafe(fiber) || '<no id>';
970
- const parentDisplayName = parentFiber
971
- ? parentFiber.tag +
970
+
971
+ let parentDisplayName;
972
+ let maybeParentID;
973
+ if (parentInstance !== null && parentInstance.kind === FIBER_INSTANCE) {
974
+ const parentFiber = parentInstance.data;
975
+ parentDisplayName =
976
+ parentFiber.tag +
977
':' +
973
- (getDisplayNameForFiber(parentFiber) || 'null')
974
- : '';
975
- const maybeParentID = parentFiber
976
- ? getFiberIDUnsafe(parentFiber) || '<no-id>'
977
- : '';
978
+ (getDisplayNameForFiber(parentFiber) || 'null');
979
+ maybeParentID = String(parentInstance.id);
980
+ } else {
981
+ // TODO: Handle VirtualInstance
982
+ parentDisplayName = '';
983
+ maybeParentID = '<no-id>';
984
+ }
985
986
console.groupCollapsed(
987
`[renderer] %c${name} %c${displayName} (${maybeID}) %c${
981
- parentFiber ? `${parentDisplayName} (${maybeParentID})` : ''
988
+ parentInstance ? `${parentDisplayName} (${maybeParentID})` : ''
989
} %c${extraString}`,
990
'color: red; font-weight: bold;',
991
'color: blue;',
1076
1077
// Recursively unmount all roots.
1078
hook.getFiberRoots(rendererID).forEach(root => {
1072
- currentRootID = getOrGenerateFiberID(root.current);
1079
+ currentRootID = getOrGenerateFiberInstance(root.current).id;
1080
// The TREE_OPERATION_REMOVE_ROOT operation serves two purposes:
1081
// 1. It avoids sending unnecessary bridge traffic to clear a root.
1082
// 2. It preserves Fiber IDs when remounting (below) which in turn ID to error/warning mapping.
1092
1093
// Recursively re-mount all roots with new filter criteria applied.
1094
hook.getFiberRoots(rendererID).forEach(root => {
1088
- currentRootID = getOrGenerateFiberID(root.current);
1095
+ currentRootID = getOrGenerateFiberInstance(root.current).id;
1096
setRootPseudoKey(currentRootID, root.current);
1097
mountFiberRecursively(root.current, null, false, false);
1098
flushPendingEvents(root);
1104
flushPendingEvents();
1105
}
1106
1107
+ function shouldFilterVirtual(data: ReactComponentInfo): boolean {
1108
+ // TODO: Apply filters to VirtualInstances.
1109
+ return false;
1110
+ }
1111
+
1112
// NOTICE Keep in sync with get*ForFiber methods
1113
function shouldFilterFiber(fiber: Fiber): boolean {
1114
const {tag, type, key} = fiber;
1257
1258
// Returns the unique ID for a Fiber or generates and caches a new one if the Fiber hasn't been seen before.
1259
// Once this method has been called for a Fiber, untrackFiberID() should always be called later to avoid leaking.
1248
- function getOrGenerateFiberID(fiber: Fiber): number {
1260
+ function getOrGenerateFiberInstance(fiber: Fiber): FiberInstance {
1261
let fiberInstance = fiberToFiberInstanceMap.get(fiber);
1262
if (fiberInstance === undefined) {
1263
const {alternate} = fiber;
1281
if (__DEBUG__) {
1282
if (didGenerateID) {
1283
debug(
1272
- 'getOrGenerateFiberID()',
1284
+ 'getOrGenerateFiberInstance()',
1285
fiber,
1274
- fiber.return,
1286
+ fiberInstance.parent,
1287
'Generated a new UID',
1288
);
1289
}
1290
}
1291
1280
- return fiberInstance.id;
1292
+ return fiberInstance;
1293
+ }
1294
+
1295
+ // Returns a FiberInstance if one has already been generated for the Fiber or throws.
1296
+ function getFiberInstanceThrows(fiber: Fiber): FiberInstance {
1297
+ const fiberInstance = getFiberInstanceUnsafe(fiber);
1298
+ if (fiberInstance !== null) {
1299
+ return fiberInstance;
1300
+ }
1301
+ throw Error(
1302
+ `Could not find ID for Fiber "${getDisplayNameForFiber(fiber) || ''}"`,
1303
+ );
1304
}
1305
1283
- // Returns an ID if one has already been generated for the Fiber or throws.
1306
function getFiberIDThrows(fiber: Fiber): number {
1285
- const maybeID = getFiberIDUnsafe(fiber);
1286
- if (maybeID !== null) {
1287
- return maybeID;
1307
+ const fiberInstance = getFiberInstanceUnsafe(fiber);
1308
+ if (fiberInstance !== null) {
1309
+ return fiberInstance.id;
1310
}
1311
throw Error(
1312
`Could not find ID for Fiber "${getDisplayNameForFiber(fiber) || ''}"`,
1313
);
1314
}
1315
1294
- // Returns an ID if one has already been generated for the Fiber or null if one has not been generated.
1316
+ // Returns a FiberInstance if one has already been generated for the Fiber or null if one has not been generated.
1317
// Use this method while e.g. logging to avoid over-retaining Fibers.
1296
- function getFiberIDUnsafe(fiber: Fiber): number | null {
1318
+ function getFiberInstanceUnsafe(fiber: Fiber): FiberInstance | null {
1319
const fiberInstance = fiberToFiberInstanceMap.get(fiber);
1320
if (fiberInstance !== undefined) {
1299
- return fiberInstance.id;
1321
+ return fiberInstance;
1322
} else {
1323
const {alternate} = fiber;
1324
if (alternate !== null) {
1325
const alternateInstance = fiberToFiberInstanceMap.get(alternate);
1326
if (alternateInstance !== undefined) {
1305
- return alternateInstance.id;
1327
+ return alternateInstance;
1328
}
1329
}
1330
}
1331
return null;
1332
}
1333
1334
+ function getFiberIDUnsafe(fiber: Fiber): number | null {
1335
+ const fiberInstance = getFiberInstanceUnsafe(fiber);
1336
+ return fiberInstance === null ? null : fiberInstance.id;
1337
+ }
1338
+
1339
// Removes a Fiber (and its alternate) from the Maps used to track their id.
1340
// This method should always be called when a Fiber is unmounting.
1341
function untrackFiberID(fiber: Fiber) {
1342
if (__DEBUG__) {
1316
- debug('untrackFiberID()', fiber, fiber.return, 'schedule after delay');
1343
+ debug('untrackFiberID()', fiber, null, 'schedule after delay');
1344
}
1345
1346
// Untrack Fibers after a slight delay in order to support a Fast Refresh edge case:
2050
return id;
2051
}
2052
2026
- function recordMount(fiber: Fiber, parentFiber: Fiber | null) {
2053
+ function recordMount(
2054
+ fiber: Fiber,
2055
+ parentInstance: DevToolsInstance | null,
2056
+ ): FiberInstance {
2057
const isRoot = fiber.tag === HostRoot;
2028
- const id = getOrGenerateFiberID(fiber);
2058
+ const fiberInstance = getOrGenerateFiberInstance(fiber);
2059
+ const id = fiberInstance.id;
2060
2061
if (__DEBUG__) {
2031
- debug('recordMount()', fiber, parentFiber);
2062
+ debug('recordMount()', fiber, parentInstance);
2063
}
2064
2065
+ // We're placing it in its parent below.
2066
+ fiberInstance.parent = parentInstance;
2067
+
2068
const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
2069
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
2070
2111
let ownerID: number;
2112
if (debugOwner != null) {
2113
if (typeof debugOwner.tag === 'number') {
2080
- ownerID = getOrGenerateFiberID((debugOwner: any));
2114
+ ownerID = getOrGenerateFiberInstance((debugOwner: any)).id;
2115
} else {
2116
// TODO: Track Server Component Owners.
2117
ownerID = 0;
2119
} else {
2120
ownerID = 0;
2121
}
2088
- const parentID = parentFiber ? getFiberIDThrows(parentFiber) : 0;
2122
+ const parentID = parentInstance ? parentInstance.id : 0;
2123
2124
const displayNameStringID = getStringID(displayName);
2125
2137
pushOperation(keyStringID);
2138
2139
// If this subtree has a new mode, let the frontend know.
2106
- if (
2107
- (fiber.mode & StrictModeBits) !== 0 &&
2108
- (((parentFiber: any): Fiber).mode & StrictModeBits) === 0
2109
- ) {
2110
- pushOperation(TREE_OPERATION_SET_SUBTREE_MODE);
2111
- pushOperation(id);
2112
- pushOperation(StrictMode);
2140
+ if ((fiber.mode & StrictModeBits) !== 0) {
2141
+ let parentFiber = null;
2142
+ let parentFiberInstance = parentInstance;
2143
+ while (parentFiberInstance !== null) {
2144
+ if (parentFiberInstance.kind === FIBER_INSTANCE) {
2145
+ parentFiber = parentFiberInstance.data;
2146
+ break;
2147
+ }
2148
+ parentFiberInstance = parentFiberInstance.parent;
2149
+ }
2150
+ if (parentFiber === null || (parentFiber.mode & StrictModeBits) === 0) {
2151
+ pushOperation(TREE_OPERATION_SET_SUBTREE_MODE);
2152
+ pushOperation(id);
2153
+ pushOperation(StrictMode);
2154
+ }
2155
}
2156
}
2157
2160
2161
recordProfilingDurations(fiber);
2162
}
2163
+ return fiberInstance;
2164
}
2165
2166
function recordUnmount(fiber: Fiber, isSimulated: boolean) {
2185
}
2186
}
2187
2145
- const unsafeID = getFiberIDUnsafe(fiber);
2146
- if (unsafeID === null) {
2188
+ const fiberInstance = getFiberInstanceUnsafe(fiber);
2189
+ if (fiberInstance === null) {
2190
// If we've never seen this Fiber, it might be inside of a legacy render Suspense fragment (so the store is not even aware of it).
2191
// In that case we can just ignore it or it will cause errors later on.
2192
// One example of this is a Lazy component that never resolves before being unmounted.
2197
return;
2198
}
2199
2157
- // Flow refinement.
2158
- const id = ((unsafeID: any): number);
2200
+ // We're about to remove this from its parent.
2201
+ fiberInstance.parent = null;
2202
+
2203
+ const id = fiberInstance.id;
2204
const isRoot = fiber.tag === HostRoot;
2205
if (isRoot) {
2206
// Roots must be removed only after all children (pending and simulated) have been removed.
2230
2231
function mountFiberRecursively(
2232
firstChild: Fiber,
2188
- parentFiber: Fiber | null,
2233
+ parentInstance: DevToolsInstance | null,
2234
traverseSiblings: boolean,
2235
traceNearestHostComponentUpdate: boolean,
2236
) {
2239
let fiber: Fiber | null = firstChild;
2240
while (fiber !== null) {
2241
// Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
2197
- getOrGenerateFiberID(fiber);
2242
+ // TODO: Do we really need to do this eagerly?
2243
+ getOrGenerateFiberInstance(fiber);
2244
2245
if (__DEBUG__) {
2200
- debug('mountFiberRecursively()', fiber, parentFiber);
2246
+ debug('mountFiberRecursively()', fiber, parentInstance);
2247
}
2248
2249
// If we have the tree selection from previous reload, try to match this Fiber.
2252
updateTrackedPathStateBeforeMount(fiber);
2253
2254
const shouldIncludeInTree = !shouldFilterFiber(fiber);
2209
- if (shouldIncludeInTree) {
2210
- recordMount(fiber, parentFiber);
2211
- }
2255
+ const newParentInstance = shouldIncludeInTree
2256
+ ? recordMount(fiber, parentInstance)
2257
+ : parentInstance;
2258
2259
if (traceUpdatesEnabled) {
2260
if (traceNearestHostComponentUpdate) {
2287
if (fallbackChild !== null) {
2288
mountFiberRecursively(
2289
fallbackChild,
2244
- shouldIncludeInTree ? fiber : parentFiber,
2290
+ newParentInstance,
2291
true,
2292
traceNearestHostComponentUpdate,
2293
);
2304
if (primaryChild !== null) {
2305
mountFiberRecursively(
2306
primaryChild,
2261
- shouldIncludeInTree ? fiber : parentFiber,
2307
+ newParentInstance,
2308
true,
2309
traceNearestHostComponentUpdate,
2310
);
2314
if (fiber.child !== null) {
2315
mountFiberRecursively(
2316
fiber.child,
2271
- shouldIncludeInTree ? fiber : parentFiber,
2317
+ newParentInstance,
2318
true,
2319
traceNearestHostComponentUpdate,
2320
);
2333
// when we switch from primary to fallback.
2334
function unmountFiberChildrenRecursively(fiber: Fiber) {
2335
if (__DEBUG__) {
2290
- debug('unmountFiberChildrenRecursively()', fiber);
2336
+ debug('unmountFiberChildrenRecursively()', fiber, null);
2337
}
2338
2339
// We might meet a nested Suspense on our way.
2430
}
2431
}
2432
2387
- function recordResetChildren(fiber: Fiber, childSet: Fiber) {
2433
+ function recordResetChildren(
2434
+ parentInstance: DevToolsInstance,
2435
+ childSet: Fiber,
2436
+ ) {
2437
if (__DEBUG__) {
2389
- debug('recordResetChildren()', childSet, fiber);
2438
+ debug('recordResetChildren()', childSet, parentInstance);
2439
}
2440
// The frontend only really cares about the displayName, key, and children.
2441
// The first two don't really change, so we are only concerned with the order of children here.
2456
return;
2457
}
2458
pushOperation(TREE_OPERATION_REORDER_CHILDREN);
2410
- pushOperation(getFiberIDThrows(fiber));
2459
+ pushOperation(parentInstance.id);
2460
pushOperation(numChildren);
2461
for (let i = 0; i < nextChildren.length; i++) {
2462
pushOperation(nextChildren[i]);
2499
function updateFiberRecursively(
2500
nextFiber: Fiber,
2501
prevFiber: Fiber,
2453
- parentFiber: Fiber | null,
2502
+ parentInstance: DevToolsInstance | null,
2503
traceNearestHostComponentUpdate: boolean,
2504
): boolean {
2456
- const id = getOrGenerateFiberID(nextFiber);
2505
+ // TODO: Do we really need to give this an instance eagerly if it's filtered?
2506
+ const fiberInstance = getOrGenerateFiberInstance(nextFiber);
2507
+ const id = fiberInstance.id;
2508
2509
if (__DEBUG__) {
2459
- debug('updateFiberRecursively()', nextFiber, parentFiber);
2510
+ debug('updateFiberRecursively()', nextFiber, parentInstance);
2511
}
2512
2513
if (traceUpdatesEnabled) {
2546
}
2547
2548
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
2549
+ const newParentInstance = shouldIncludeInTree
2550
+ ? fiberInstance
2551
+ : parentInstance;
2552
const isSuspense = nextFiber.tag === SuspenseComponent;
2553
let shouldResetChildren = false;
2554
// The behavior of timed-out Suspense trees is unique.
2580
if (prevFallbackChildSet == null && nextFallbackChildSet != null) {
2581
mountFiberRecursively(
2582
nextFallbackChildSet,
2529
- shouldIncludeInTree ? nextFiber : parentFiber,
2583
+ newParentInstance,
2584
true,
2585
traceNearestHostComponentUpdate,
2586
);
2594
updateFiberRecursively(
2595
nextFallbackChildSet,
2596
prevFallbackChildSet,
2543
- nextFiber,
2597
+ newParentInstance,
2598
traceNearestHostComponentUpdate,
2599
)
2600
) {
2609
if (nextPrimaryChildSet !== null) {
2610
mountFiberRecursively(
2611
nextPrimaryChildSet,
2558
- shouldIncludeInTree ? nextFiber : parentFiber,
2612
+ newParentInstance,
2613
true,
2614
traceNearestHostComponentUpdate,
2615
);
2629
if (nextFallbackChildSet != null) {
2630
mountFiberRecursively(
2631
nextFallbackChildSet,
2578
- shouldIncludeInTree ? nextFiber : parentFiber,
2632
+ newParentInstance,
2633
true,
2634
traceNearestHostComponentUpdate,
2635
);
2654
updateFiberRecursively(
2655
nextChild,
2656
prevChild,
2603
- shouldIncludeInTree ? nextFiber : parentFiber,
2657
+ newParentInstance,
2658
traceNearestHostComponentUpdate,
2659
)
2660
) {
2672
} else {
2673
mountFiberRecursively(
2674
nextChild,
2621
- shouldIncludeInTree ? nextFiber : parentFiber,
2675
+ newParentInstance,
2676
false,
2677
traceNearestHostComponentUpdate,
2678
);
2696
// we should fall back to recursively marking the nearest host descendants for highlight.
2697
if (traceNearestHostComponentUpdate) {
2698
const hostFibers = findAllCurrentHostFibers(
2645
- getFiberIDThrows(nextFiber),
2699
+ getFiberInstanceThrows(nextFiber),
2700
);
2701
hostFibers.forEach(hostFiber => {
2702
traceUpdatesForNodes.add(hostFiber.stateNode);
2724
nextChildSet = nextFiberChild ? nextFiberChild.sibling : null;
2725
}
2726
if (nextChildSet != null) {
2673
- recordResetChildren(nextFiber, nextChildSet);
2727
+ recordResetChildren(fiberInstance, nextChildSet);
2728
}
2729
// We've handled the child order change for this Fiber.
2730
// Since it's included, there's no need to invalidate parent child order.
2780
}
2781
// If we have not been profiling, then we can just walk the tree and build up its current state as-is.
2782
hook.getFiberRoots(rendererID).forEach(root => {
2729
- currentRootID = getOrGenerateFiberID(root.current);
2783
+ currentRootID = getOrGenerateFiberInstance(root.current).id;
2784
setRootPseudoKey(currentRootID, root.current);
2785
2786
// Handle multi-renderer edge-case where only some v16 renderers support profiling.
2848
// If we don't do this, we might end up double-deleting Fibers in some cases (like Legacy Suspense).
2849
untrackFibers();
2850
2797
- currentRootID = getOrGenerateFiberID(current);
2851
+ currentRootID = getOrGenerateFiberInstance(current).id;
2852
2853
// Before the traversals, remember to start tracking
2854
// our path in case we have selection to restore.
2943
currentRootID = -1;
2944
}
2945
2892
- function findAllCurrentHostFibers(id: number): $ReadOnlyArray<Fiber> {
2946
+ function findAllCurrentHostFibers(
2947
+ fiberInstance: FiberInstance,
2948
+ ): $ReadOnlyArray<Fiber> {
2949
const fibers = [];
2894
- const devtoolsInstance = idToDevToolsInstanceMap.get(id);
2895
- if (devtoolsInstance === undefined) {
2896
- console.warn(`Could not find DevToolsInstance with id "${id}"`);
2897
- return fibers;
2898
- }
2899
- if (devtoolsInstance.kind !== FIBER_INSTANCE) {
2900
- // TODO: Handle VirtualInstance.
2901
- return fibers;
2902
- }
2903
- const fiber =
2904
- findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
2950
+ const fiber = findCurrentFiberUsingSlowPathByFiberInstance(fiberInstance);
2951
if (!fiber) {
2952
return fibers;
2953
}
2996
return null;
2997
}
2998
2953
- const hostFibers = findAllCurrentHostFibers(id);
2999
+ const hostFibers = findAllCurrentHostFibers(devtoolsInstance);
3000
return hostFibers.map(hostFiber => hostFiber.stateNode).filter(Boolean);
3001
} catch (err) {
3002
// The fiber might have unmounted by now.
3029
function getElementIDForHostInstance(
3030
hostInstance: HostInstance,
3031
findNearestUnfilteredAncestor: boolean = false,
2986
- ) {
3032
+ ): number | null {
3033
let fiber = renderer.findFiberByHostInstance(hostInstance);
3034
if (fiber != null) {
2989
- if (findNearestUnfilteredAncestor) {
2990
- while (fiber !== null && shouldFilterFiber(fiber)) {
2991
- fiber = fiber.return;
3035
+ if (!findNearestUnfilteredAncestor) {
3036
+ // TODO: Remove this option. It's not used.
3037
+ return getFiberIDThrows(fiber);
3038
+ }
3039
+ while (fiber !== null) {
3040
+ const fiberInstance = getFiberInstanceUnsafe(fiber);
3041
+ if (fiberInstance !== null) {
3042
+ // TODO: Ideally we would not have any filtered FiberInstances which
3043
+ // would make this logic much simpler. Unfortunately, we sometimes
3044
+ // eagerly add to the map and some times don't eagerly clean it up.
3045
+ // TODO: If the fiber is filtered, the FiberInstance wouldn't really
3046
+ // exist which would mean that we also don't have a way to get to the
3047
+ // VirtualInstances.
3048
+ if (!shouldFilterFiber(fiberInstance.data)) {
3049
+ return fiberInstance.id;
3050
+ }
3051
+ // We couldn't use this Fiber but we might have a VirtualInstance
3052
+ // that is the nearest unfiltered instance.
3053
+ let parentInstance = fiberInstance.parent;
3054
+ while (parentInstance !== null) {
3055
+ if (parentInstance.kind === FIBER_INSTANCE) {
3056
+ // If we find a parent Fiber, it might not be the nearest parent
3057
+ // so we break out and continue walking the Fiber tree instead.
3058
+ break;
3059
+ } else {
3060
+ if (!shouldFilterVirtual(parentInstance.data)) {
3061
+ return parentInstance.id;
3062
+ }
3063
+ }
3064
+ parentInstance = parentInstance.parent;
3065
+ }
3066
}
3067
+ fiber = fiber.return;
3068
}
2994
- return getFiberIDThrows(((fiber: any): Fiber));
3069
}
3070
return null;
3071
}
3415
let parent = fiber.return;
3416
while (parent !== null) {
3417
if (isErrorBoundary(parent)) {
3418
+ // TODO: If this boundary is filtered it won't have an ID.
3419
return getFiberIDUnsafe(parent);
3420
}
3421
parent = parent.return;