@samitouri / QOS-React / commits / 2b00018347

[DevTools] Track the parent DevToolsInstance while mounting a tree (#30542)

This just tracks the `.parent` field properly and uses DevToolsInstances in more places that used to use IDs or Fibers. I also use this new parent path when looking up a DevToolsInstance from a DOM node. This should ideally be simple because the `.parent` field represents only the unfiltered parents and include any virtual parents. So we should be able to just get one from nearest Fiber that has one. However, because we don't currently always clean up the map of DevToolsInstances (e.g. updateComponentFilters doesn't recursively clean out everything) it can leave matches hanging that shouldn't be there. So we need to run the shouldFilterFiber filter to ignore those. Another interesting implication is that without a FiberInstance we don't have a way to get to a VirtualInstance from a HostComponent. Which means that even filtered Fibers need to have a FiberInstance if they have a VirtualInstance parent. Even if we don't actually mount them into the front-end.

Sebastian Markbåge committed Jul 31, 2024 at 10:07 UTC 2b0001834769cab6d73e68627a8b15c4708961e6
1 file changed +159 -84
packages/react-devtools-shared/src/backend/fiber/renderer.js
+159 -84
@@ -959,7 +959,7 @@ export function attach(
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,18 +967,25 @@ export function attach(
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;',
@@ -1069,7 +1076,7 @@ export function attach(
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.
@@ -1085,7 +1092,7 @@ export function attach(
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);
@@ -1097,6 +1104,11 @@ export function attach(
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;
@@ -1245,7 +1257,7 @@ export function attach(
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;
@@ -1269,51 +1281,66 @@ export function attach(
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:
@@ -2023,14 +2050,21 @@ export function attach(
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
@@ -2077,7 +2111,7 @@ export function attach(
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;
@@ -2085,7 +2119,7 @@ export function attach(
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
@@ -2103,13 +2137,21 @@ export function attach(
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
@@ -2118,6 +2160,7 @@ export function attach(
2160
2161 recordProfilingDurations(fiber);
2162 }
2163 + return fiberInstance;
2164 }
2165
2166 function recordUnmount(fiber: Fiber, isSimulated: boolean) {
@@ -2142,8 +2185,8 @@ export function attach(
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.
@@ -2154,8 +2197,10 @@ export function attach(
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.
@@ -2185,7 +2230,7 @@ export function attach(
2230
2231 function mountFiberRecursively(
2232 firstChild: Fiber,
2188 - parentFiber: Fiber | null,
2233 + parentInstance: DevToolsInstance | null,
2234 traverseSiblings: boolean,
2235 traceNearestHostComponentUpdate: boolean,
2236 ) {
@@ -2194,10 +2239,11 @@ export function attach(
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.
@@ -2206,9 +2252,9 @@ export function attach(
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) {
@@ -2241,7 +2287,7 @@ export function attach(
2287 if (fallbackChild !== null) {
2288 mountFiberRecursively(
2289 fallbackChild,
2244 - shouldIncludeInTree ? fiber : parentFiber,
2290 + newParentInstance,
2291 true,
2292 traceNearestHostComponentUpdate,
2293 );
@@ -2258,7 +2304,7 @@ export function attach(
2304 if (primaryChild !== null) {
2305 mountFiberRecursively(
2306 primaryChild,
2261 - shouldIncludeInTree ? fiber : parentFiber,
2307 + newParentInstance,
2308 true,
2309 traceNearestHostComponentUpdate,
2310 );
@@ -2268,7 +2314,7 @@ export function attach(
2314 if (fiber.child !== null) {
2315 mountFiberRecursively(
2316 fiber.child,
2271 - shouldIncludeInTree ? fiber : parentFiber,
2317 + newParentInstance,
2318 true,
2319 traceNearestHostComponentUpdate,
2320 );
@@ -2287,7 +2333,7 @@ export function attach(
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.
@@ -2384,9 +2430,12 @@ export function attach(
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.
@@ -2407,7 +2456,7 @@ export function attach(
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]);
@@ -2450,13 +2499,15 @@ export function attach(
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) {
@@ -2495,6 +2546,9 @@ export function attach(
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.
@@ -2526,7 +2580,7 @@ export function attach(
2580 if (prevFallbackChildSet == null && nextFallbackChildSet != null) {
2581 mountFiberRecursively(
2582 nextFallbackChildSet,
2529 - shouldIncludeInTree ? nextFiber : parentFiber,
2583 + newParentInstance,
2584 true,
2585 traceNearestHostComponentUpdate,
2586 );
@@ -2540,7 +2594,7 @@ export function attach(
2594 updateFiberRecursively(
2595 nextFallbackChildSet,
2596 prevFallbackChildSet,
2543 - nextFiber,
2597 + newParentInstance,
2598 traceNearestHostComponentUpdate,
2599 )
2600 ) {
@@ -2555,7 +2609,7 @@ export function attach(
2609 if (nextPrimaryChildSet !== null) {
2610 mountFiberRecursively(
2611 nextPrimaryChildSet,
2558 - shouldIncludeInTree ? nextFiber : parentFiber,
2612 + newParentInstance,
2613 true,
2614 traceNearestHostComponentUpdate,
2615 );
@@ -2575,7 +2629,7 @@ export function attach(
2629 if (nextFallbackChildSet != null) {
2630 mountFiberRecursively(
2631 nextFallbackChildSet,
2578 - shouldIncludeInTree ? nextFiber : parentFiber,
2632 + newParentInstance,
2633 true,
2634 traceNearestHostComponentUpdate,
2635 );
@@ -2600,7 +2654,7 @@ export function attach(
2654 updateFiberRecursively(
2655 nextChild,
2656 prevChild,
2603 - shouldIncludeInTree ? nextFiber : parentFiber,
2657 + newParentInstance,
2658 traceNearestHostComponentUpdate,
2659 )
2660 ) {
@@ -2618,7 +2672,7 @@ export function attach(
2672 } else {
2673 mountFiberRecursively(
2674 nextChild,
2621 - shouldIncludeInTree ? nextFiber : parentFiber,
2675 + newParentInstance,
2676 false,
2677 traceNearestHostComponentUpdate,
2678 );
@@ -2642,7 +2696,7 @@ export function attach(
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);
@@ -2670,7 +2724,7 @@ export function attach(
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.
@@ -2726,7 +2780,7 @@ export function attach(
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.
@@ -2794,7 +2848,7 @@ export function attach(
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.
@@ -2889,19 +2943,11 @@ export function attach(
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 }
@@ -2950,7 +2996,7 @@ export function attach(
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.
@@ -2983,15 +3029,43 @@ export function attach(
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 }
@@ -3341,6 +3415,7 @@ export function attach(
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;