Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DevTools] Unmount instance by walking the instance tree instead of the fiber tree #30665

Merged
merged 2 commits into from
Aug 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 35 additions & 84 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1148,8 +1148,9 @@ export function attach(

// Recursively unmount all roots.
hook.getFiberRoots(rendererID).forEach(root => {
currentRootID = getFiberInstanceThrows(root.current).id;
unmountFiberRecursively(root.current);
const rootInstance = getFiberInstanceThrows(root.current);
currentRootID = rootInstance.id;
unmountInstanceRecursively(rootInstance);
flushPendingEvents(root);
currentRootID = -1;
});
Expand Down Expand Up @@ -2183,7 +2184,8 @@ export function attach(
return fiberInstance;
}

function recordUnmount(fiber: Fiber): null | FiberInstance {
function recordUnmount(fiberInstance: FiberInstance): void {
const fiber = fiberInstance.data;
if (__DEBUG__) {
debug('recordUnmount()', fiber, null);
}
Expand All @@ -2200,25 +2202,13 @@ export function attach(
}
}

const fiberInstance = getFiberInstanceUnsafe(fiber);
if (fiberInstance === null) {
// 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).
// In that case we can just ignore it or it will cause errors later on.
// One example of this is a Lazy component that never resolves before being unmounted.
//
// This also might indicate a Fast Refresh force-remount scenario.
//
// TODO: This is fragile and can obscure actual bugs.
return null;
}

const id = fiberInstance.id;
const isRoot = fiber.tag === HostRoot;
if (isRoot) {
// Roots must be removed only after all children have been removed.
// So we track it separately.
pendingUnmountedRootID = id;
} else if (!shouldFilterFiber(fiber)) {
} else {
// To maintain child-first ordering,
// we'll push it into one of these queues,
// and later arrange them in the correct order.
Expand All @@ -2232,7 +2222,6 @@ export function attach(
idToRootMap.delete(id);
idToTreeBaseDurationMap.delete(id);
}
return fiberInstance;
}

// Running state of the remaining children from the previous version of this parent that
Expand Down Expand Up @@ -2308,10 +2297,7 @@ export function attach(
function unmountRemainingChildren() {
let child = remainingReconcilingChildren;
while (child !== null) {
if (child.kind === FIBER_INSTANCE) {
unmountFiberRecursively(child.data);
}
removeChild(child);
unmountInstanceRecursively(child);
child = remainingReconcilingChildren;
}
}
Expand Down Expand Up @@ -2434,69 +2420,33 @@ export function attach(

// We use this to simulate unmounting for Suspense trees
// when we switch from primary to fallback, or deleting a subtree.
function unmountFiberRecursively(fiber: Fiber) {
function unmountInstanceRecursively(instance: DevToolsInstance) {
if (__DEBUG__) {
debug('unmountFiberRecursively()', fiber, null);
if (instance.kind === FIBER_INSTANCE) {
debug('unmountInstanceRecursively()', instance.data, null);
}
}

let fiberInstance = null;

const shouldIncludeInTree = !shouldFilterFiber(fiber);
const stashedParent = reconcilingParent;
const stashedPrevious = previouslyReconciledSibling;
const stashedRemaining = remainingReconcilingChildren;
if (shouldIncludeInTree) {
fiberInstance = getFiberInstanceThrows(fiber);
// Push a new DevTools instance parent while reconciling this subtree.
reconcilingParent = fiberInstance;
previouslyReconciledSibling = null;
// Move all the children of this instance to the remaining set.
// We'll move them back one by one, and anything that remains is deleted.
remainingReconcilingChildren = fiberInstance.firstChild;
fiberInstance.firstChild = null;
}
// Push a new DevTools instance parent while reconciling this subtree.
reconcilingParent = instance;
previouslyReconciledSibling = null;
// Move all the children of this instance to the remaining set.
remainingReconcilingChildren = instance.firstChild;
try {
// We might meet a nested Suspense on our way.
const isTimedOutSuspense =
fiber.tag === SuspenseComponent && fiber.memoizedState !== null;

if (fiber.tag === HostHoistable) {
releaseHostResource(fiber, fiber.memoizedState);
}

let child = fiber.child;
if (isTimedOutSuspense) {
// If it's showing fallback tree, let's traverse it instead.
const primaryChildFragment = fiber.child;
const fallbackChildFragment = primaryChildFragment
? primaryChildFragment.sibling
: null;
// Skip over to the real Fiber child.
child = fallbackChildFragment ? fallbackChildFragment.child : null;
}

unmountChildrenRecursively(child);
// Unmount the remaining set.
unmountRemainingChildren();
} finally {
if (shouldIncludeInTree) {
reconcilingParent = stashedParent;
previouslyReconciledSibling = stashedPrevious;
remainingReconcilingChildren = stashedRemaining;
}
}
if (fiberInstance !== null) {
recordUnmount(fiber);
removeChild(fiberInstance);
reconcilingParent = stashedParent;
previouslyReconciledSibling = stashedPrevious;
remainingReconcilingChildren = stashedRemaining;
}
}

function unmountChildrenRecursively(firstChild: null | Fiber) {
let child: null | Fiber = firstChild;
while (child !== null) {
// Record simulated unmounts children-first.
// We skip nodes without return because those are real unmounts.
unmountFiberRecursively(child);
child = child.sibling;
if (instance.kind === FIBER_INSTANCE) {
recordUnmount(instance);
}
removeChild(instance);
}

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

const existingRoot =
let rootInstance =
fiberToFiberInstanceMap.get(current) ||
(alternate && fiberToFiberInstanceMap.get(alternate));
if (!existingRoot) {
const newRoot = createFiberInstance(current);
idToDevToolsInstanceMap.set(newRoot.id, newRoot);
fiberToFiberInstanceMap.set(current, newRoot);
if (!rootInstance) {
rootInstance = createFiberInstance(current);
idToDevToolsInstanceMap.set(rootInstance.id, rootInstance);
fiberToFiberInstanceMap.set(current, rootInstance);
if (alternate) {
fiberToFiberInstanceMap.set(alternate, newRoot);
fiberToFiberInstanceMap.set(alternate, rootInstance);
}
currentRootID = newRoot.id;
currentRootID = rootInstance.id;
} else {
currentRootID = existingRoot.id;
currentRootID = rootInstance.id;
}

// Before the traversals, remember to start tracking
Expand Down Expand Up @@ -3123,7 +3074,7 @@ export function attach(
} else if (wasMounted && !isMounted) {
// Unmount an existing root.
removeRootPseudoKey(currentRootID);
unmountFiberRecursively(alternate);
unmountInstanceRecursively(rootInstance);
}
} else {
// Mount a new root.
Expand Down
Loading