Skip to content

Commit e865fe0

Browse files
authored
[DevTools] Unmount instance by walking the instance tree instead of the fiber tree (#30665)
There was a comment that it's not safe to walk the unmounted fiber tree which I'm not sure is correct or not but we need to walk the instance tree to be able to clean up virtual instances anyway. We already walk the instance tree to clean up "remaining instances". This is also simpler because we don't need to special case Suspense boundaries. We simply clean up whatever branch we had before. The ultimate goal is to also walk the instance tree for updates so we don't need a fiber to instance map.
1 parent d48603a commit e865fe0

File tree

1 file changed

+35
-84
lines changed
  • packages/react-devtools-shared/src/backend/fiber

1 file changed

+35
-84
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 35 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,8 +1148,9 @@ export function attach(
11481148

11491149
// Recursively unmount all roots.
11501150
hook.getFiberRoots(rendererID).forEach(root => {
1151-
currentRootID = getFiberInstanceThrows(root.current).id;
1152-
unmountFiberRecursively(root.current);
1151+
const rootInstance = getFiberInstanceThrows(root.current);
1152+
currentRootID = rootInstance.id;
1153+
unmountInstanceRecursively(rootInstance);
11531154
flushPendingEvents(root);
11541155
currentRootID = -1;
11551156
});
@@ -2183,7 +2184,8 @@ export function attach(
21832184
return fiberInstance;
21842185
}
21852186

2186-
function recordUnmount(fiber: Fiber): null | FiberInstance {
2187+
function recordUnmount(fiberInstance: FiberInstance): void {
2188+
const fiber = fiberInstance.data;
21872189
if (__DEBUG__) {
21882190
debug('recordUnmount()', fiber, null);
21892191
}
@@ -2200,25 +2202,13 @@ export function attach(
22002202
}
22012203
}
22022204

2203-
const fiberInstance = getFiberInstanceUnsafe(fiber);
2204-
if (fiberInstance === null) {
2205-
// 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).
2206-
// In that case we can just ignore it or it will cause errors later on.
2207-
// One example of this is a Lazy component that never resolves before being unmounted.
2208-
//
2209-
// This also might indicate a Fast Refresh force-remount scenario.
2210-
//
2211-
// TODO: This is fragile and can obscure actual bugs.
2212-
return null;
2213-
}
2214-
22152205
const id = fiberInstance.id;
22162206
const isRoot = fiber.tag === HostRoot;
22172207
if (isRoot) {
22182208
// Roots must be removed only after all children have been removed.
22192209
// So we track it separately.
22202210
pendingUnmountedRootID = id;
2221-
} else if (!shouldFilterFiber(fiber)) {
2211+
} else {
22222212
// To maintain child-first ordering,
22232213
// we'll push it into one of these queues,
22242214
// and later arrange them in the correct order.
@@ -2232,7 +2222,6 @@ export function attach(
22322222
idToRootMap.delete(id);
22332223
idToTreeBaseDurationMap.delete(id);
22342224
}
2235-
return fiberInstance;
22362225
}
22372226

22382227
// Running state of the remaining children from the previous version of this parent that
@@ -2308,10 +2297,7 @@ export function attach(
23082297
function unmountRemainingChildren() {
23092298
let child = remainingReconcilingChildren;
23102299
while (child !== null) {
2311-
if (child.kind === FIBER_INSTANCE) {
2312-
unmountFiberRecursively(child.data);
2313-
}
2314-
removeChild(child);
2300+
unmountInstanceRecursively(child);
23152301
child = remainingReconcilingChildren;
23162302
}
23172303
}
@@ -2434,69 +2420,33 @@ export function attach(
24342420

24352421
// We use this to simulate unmounting for Suspense trees
24362422
// when we switch from primary to fallback, or deleting a subtree.
2437-
function unmountFiberRecursively(fiber: Fiber) {
2423+
function unmountInstanceRecursively(instance: DevToolsInstance) {
24382424
if (__DEBUG__) {
2439-
debug('unmountFiberRecursively()', fiber, null);
2425+
if (instance.kind === FIBER_INSTANCE) {
2426+
debug('unmountInstanceRecursively()', instance.data, null);
2427+
}
24402428
}
24412429

2442-
let fiberInstance = null;
2443-
2444-
const shouldIncludeInTree = !shouldFilterFiber(fiber);
24452430
const stashedParent = reconcilingParent;
24462431
const stashedPrevious = previouslyReconciledSibling;
24472432
const stashedRemaining = remainingReconcilingChildren;
2448-
if (shouldIncludeInTree) {
2449-
fiberInstance = getFiberInstanceThrows(fiber);
2450-
// Push a new DevTools instance parent while reconciling this subtree.
2451-
reconcilingParent = fiberInstance;
2452-
previouslyReconciledSibling = null;
2453-
// Move all the children of this instance to the remaining set.
2454-
// We'll move them back one by one, and anything that remains is deleted.
2455-
remainingReconcilingChildren = fiberInstance.firstChild;
2456-
fiberInstance.firstChild = null;
2457-
}
2433+
// Push a new DevTools instance parent while reconciling this subtree.
2434+
reconcilingParent = instance;
2435+
previouslyReconciledSibling = null;
2436+
// Move all the children of this instance to the remaining set.
2437+
remainingReconcilingChildren = instance.firstChild;
24582438
try {
2459-
// We might meet a nested Suspense on our way.
2460-
const isTimedOutSuspense =
2461-
fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
2462-
2463-
if (fiber.tag === HostHoistable) {
2464-
releaseHostResource(fiber, fiber.memoizedState);
2465-
}
2466-
2467-
let child = fiber.child;
2468-
if (isTimedOutSuspense) {
2469-
// If it's showing fallback tree, let's traverse it instead.
2470-
const primaryChildFragment = fiber.child;
2471-
const fallbackChildFragment = primaryChildFragment
2472-
? primaryChildFragment.sibling
2473-
: null;
2474-
// Skip over to the real Fiber child.
2475-
child = fallbackChildFragment ? fallbackChildFragment.child : null;
2476-
}
2477-
2478-
unmountChildrenRecursively(child);
2439+
// Unmount the remaining set.
2440+
unmountRemainingChildren();
24792441
} finally {
2480-
if (shouldIncludeInTree) {
2481-
reconcilingParent = stashedParent;
2482-
previouslyReconciledSibling = stashedPrevious;
2483-
remainingReconcilingChildren = stashedRemaining;
2484-
}
2485-
}
2486-
if (fiberInstance !== null) {
2487-
recordUnmount(fiber);
2488-
removeChild(fiberInstance);
2442+
reconcilingParent = stashedParent;
2443+
previouslyReconciledSibling = stashedPrevious;
2444+
remainingReconcilingChildren = stashedRemaining;
24892445
}
2490-
}
2491-
2492-
function unmountChildrenRecursively(firstChild: null | Fiber) {
2493-
let child: null | Fiber = firstChild;
2494-
while (child !== null) {
2495-
// Record simulated unmounts children-first.
2496-
// We skip nodes without return because those are real unmounts.
2497-
unmountFiberRecursively(child);
2498-
child = child.sibling;
2446+
if (instance.kind === FIBER_INSTANCE) {
2447+
recordUnmount(instance);
24992448
}
2449+
removeChild(instance);
25002450
}
25012451

25022452
function recordProfilingDurations(fiber: Fiber) {
@@ -2843,7 +2793,8 @@ export function attach(
28432793
} else if (!prevDidTimeout && nextDidTimeOut) {
28442794
// Primary -> Fallback:
28452795
// 1. Hide primary set
2846-
unmountChildrenRecursively(prevFiber.child);
2796+
// We simply don't re-add the fallback children and let
2797+
// unmountRemainingChildren() handle it.
28472798
// 2. Mount fallback set
28482799
const nextFiberChild = nextFiber.child;
28492800
const nextFallbackChildSet = nextFiberChild
@@ -3053,19 +3004,19 @@ export function attach(
30533004
const current = root.current;
30543005
const alternate = current.alternate;
30553006

3056-
const existingRoot =
3007+
let rootInstance =
30573008
fiberToFiberInstanceMap.get(current) ||
30583009
(alternate && fiberToFiberInstanceMap.get(alternate));
3059-
if (!existingRoot) {
3060-
const newRoot = createFiberInstance(current);
3061-
idToDevToolsInstanceMap.set(newRoot.id, newRoot);
3062-
fiberToFiberInstanceMap.set(current, newRoot);
3010+
if (!rootInstance) {
3011+
rootInstance = createFiberInstance(current);
3012+
idToDevToolsInstanceMap.set(rootInstance.id, rootInstance);
3013+
fiberToFiberInstanceMap.set(current, rootInstance);
30633014
if (alternate) {
3064-
fiberToFiberInstanceMap.set(alternate, newRoot);
3015+
fiberToFiberInstanceMap.set(alternate, rootInstance);
30653016
}
3066-
currentRootID = newRoot.id;
3017+
currentRootID = rootInstance.id;
30673018
} else {
3068-
currentRootID = existingRoot.id;
3019+
currentRootID = rootInstance.id;
30693020
}
30703021

30713022
// Before the traversals, remember to start tracking
@@ -3123,7 +3074,7 @@ export function attach(
31233074
} else if (wasMounted && !isMounted) {
31243075
// Unmount an existing root.
31253076
removeRootPseudoKey(currentRootID);
3126-
unmountFiberRecursively(alternate);
3077+
unmountInstanceRecursively(rootInstance);
31273078
}
31283079
} else {
31293080
// Mount a new root.

0 commit comments

Comments
 (0)