Skip to content

Commit e33a723

Browse files
authored
[DevTools] Track virtual instances on the tracked path for selections (#30802)
This appends a (filtered) virtual instance path at the end of the fiber path. If a virtual instance is selected inside the fiber. The main part of the path is still just the fiber path since that's the semantically stateful part. Then we just tack on a few virtual path frames at the end if we're currently selecting a specific Server Component within the nearest Fiber. I also took the opportunity to fix a bug which caused selections inside Suspense boundaries to not be tracked.
1 parent 18bf7bf commit e33a723

1 file changed

Lines changed: 121 additions & 39 deletions

File tree

  • packages/react-devtools-shared/src/backend/fiber

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

Lines changed: 121 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,16 +2266,11 @@ export function attach(
22662266
debug('recordUnmount()', fiber, null);
22672267
}
22682268

2269-
if (trackedPathMatchFiber !== null) {
2269+
if (trackedPathMatchInstance === fiberInstance) {
22702270
// We're in the process of trying to restore previous selection.
22712271
// If this fiber matched but is being unmounted, there's no use trying.
22722272
// Reset the state so we don't keep holding onto it.
2273-
if (
2274-
fiber === trackedPathMatchFiber ||
2275-
fiber === trackedPathMatchFiber.alternate
2276-
) {
2277-
setTrackedPath(null);
2278-
}
2273+
setTrackedPath(null);
22792274
}
22802275

22812276
const id = fiberInstance.id;
@@ -2386,6 +2381,14 @@ export function attach(
23862381
traceNearestHostComponentUpdate: boolean,
23872382
virtualLevel: number, // the nth level of virtual instances
23882383
): void {
2384+
// If we have the tree selection from previous reload, try to match this Instance.
2385+
// Also remember whether to do the same for siblings.
2386+
const mightSiblingsBeOnTrackedPath =
2387+
updateVirtualTrackedPathStateBeforeMount(
2388+
virtualInstance,
2389+
reconcilingParent,
2390+
);
2391+
23892392
const stashedParent = reconcilingParent;
23902393
const stashedPrevious = previouslyReconciledSibling;
23912394
const stashedRemaining = remainingReconcilingChildren;
@@ -2406,13 +2409,16 @@ export function attach(
24062409
reconcilingParent = stashedParent;
24072410
previouslyReconciledSibling = stashedPrevious;
24082411
remainingReconcilingChildren = stashedRemaining;
2412+
updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath);
24092413
}
24102414
}
24112415

24122416
function recordVirtualUnmount(instance: VirtualInstance) {
2413-
if (trackedPathMatchFiber !== null) {
2417+
if (trackedPathMatchInstance === instance) {
24142418
// We're in the process of trying to restore previous selection.
2415-
// TODO: Handle virtual instances on the tracked path.
2419+
// If this fiber matched but is being unmounted, there's no use trying.
2420+
// Reset the state so we don't keep holding onto it.
2421+
setTrackedPath(null);
24162422
}
24172423

24182424
const id = instance.id;
@@ -2521,17 +2527,20 @@ export function attach(
25212527
debug('mountFiberRecursively()', fiber, reconcilingParent);
25222528
}
25232529

2524-
// If we have the tree selection from previous reload, try to match this Fiber.
2525-
// Also remember whether to do the same for siblings.
2526-
const mightSiblingsBeOnTrackedPath =
2527-
updateTrackedPathStateBeforeMount(fiber);
2528-
25292530
const shouldIncludeInTree = !shouldFilterFiber(fiber);
25302531
let newInstance = null;
25312532
if (shouldIncludeInTree) {
25322533
newInstance = recordMount(fiber, reconcilingParent);
25332534
insertChild(newInstance);
25342535
}
2536+
2537+
// If we have the tree selection from previous reload, try to match this Fiber.
2538+
// Also remember whether to do the same for siblings.
2539+
const mightSiblingsBeOnTrackedPath = updateTrackedPathStateBeforeMount(
2540+
fiber,
2541+
newInstance,
2542+
);
2543+
25352544
const stashedParent = reconcilingParent;
25362545
const stashedPrevious = previouslyReconciledSibling;
25372546
const stashedRemaining = remainingReconcilingChildren;
@@ -2570,14 +2579,15 @@ export function attach(
25702579
const fallbackChildFragment = primaryChildFragment
25712580
? primaryChildFragment.sibling
25722581
: null;
2573-
const fallbackChild = fallbackChildFragment
2574-
? fallbackChildFragment.child
2575-
: null;
2576-
if (fallbackChild !== null) {
2577-
mountChildrenRecursively(
2578-
fallbackChild,
2579-
traceNearestHostComponentUpdate,
2580-
);
2582+
if (fallbackChildFragment) {
2583+
const fallbackChild = fallbackChildFragment.child;
2584+
if (fallbackChild !== null) {
2585+
updateTrackedPathStateBeforeMount(fallbackChildFragment, null);
2586+
mountChildrenRecursively(
2587+
fallbackChild,
2588+
traceNearestHostComponentUpdate,
2589+
);
2590+
}
25812591
}
25822592
} else {
25832593
let primaryChild: Fiber | null = null;
@@ -2587,6 +2597,7 @@ export function attach(
25872597
primaryChild = fiber.child;
25882598
} else if (fiber.child !== null) {
25892599
primaryChild = fiber.child.child;
2600+
updateTrackedPathStateBeforeMount(fiber.child, null);
25902601
}
25912602
if (primaryChild !== null) {
25922603
mountChildrenRecursively(
@@ -5262,13 +5273,15 @@ export function attach(
52625273
// Remember if we're trying to restore the selection after reload.
52635274
// In that case, we'll do some extra checks for matching mounts.
52645275
let trackedPath: Array<PathFrame> | null = null;
5265-
let trackedPathMatchFiber: Fiber | null = null;
5276+
let trackedPathMatchFiber: Fiber | null = null; // This is the deepest unfiltered match of a Fiber.
5277+
let trackedPathMatchInstance: DevToolsInstance | null = null; // This is the deepest matched filtered Instance.
52665278
let trackedPathMatchDepth = -1;
52675279
let mightBeOnTrackedPath = false;
52685280

52695281
function setTrackedPath(path: Array<PathFrame> | null) {
52705282
if (path === null) {
52715283
trackedPathMatchFiber = null;
5284+
trackedPathMatchInstance = null;
52725285
trackedPathMatchDepth = -1;
52735286
mightBeOnTrackedPath = false;
52745287
}
@@ -5278,7 +5291,10 @@ export function attach(
52785291
// We call this before traversing a new mount.
52795292
// It remembers whether this Fiber is the next best match for tracked path.
52805293
// The return value signals whether we should keep matching siblings or not.
5281-
function updateTrackedPathStateBeforeMount(fiber: Fiber): boolean {
5294+
function updateTrackedPathStateBeforeMount(
5295+
fiber: Fiber,
5296+
fiberInstance: null | FiberInstance,
5297+
): boolean {
52825298
if (trackedPath === null || !mightBeOnTrackedPath) {
52835299
// Fast path: there's nothing to track so do nothing and ignore siblings.
52845300
return false;
@@ -5306,6 +5322,9 @@ export function attach(
53065322
) {
53075323
// We have our next match.
53085324
trackedPathMatchFiber = fiber;
5325+
if (fiberInstance !== null) {
5326+
trackedPathMatchInstance = fiberInstance;
5327+
}
53095328
trackedPathMatchDepth++;
53105329
// Are we out of frames to match?
53115330
// $FlowFixMe[incompatible-use] found when upgrading Flow
@@ -5322,13 +5341,69 @@ export function attach(
53225341
return false;
53235342
}
53245343
}
5344+
if (trackedPathMatchFiber === null && fiberInstance === null) {
5345+
// We're now looking for a Virtual Instance. It might be inside filtered Fibers
5346+
// so we keep looking below.
5347+
return true;
5348+
}
53255349
// This Fiber's parent is on the path, but this Fiber itself isn't.
53265350
// There's no need to check its children--they won't be on the path either.
53275351
mightBeOnTrackedPath = false;
53285352
// However, one of its siblings may be on the path so keep searching.
53295353
return true;
53305354
}
53315355

5356+
function updateVirtualTrackedPathStateBeforeMount(
5357+
virtualInstance: VirtualInstance,
5358+
parentInstance: null | DevToolsInstance,
5359+
): boolean {
5360+
if (trackedPath === null || !mightBeOnTrackedPath) {
5361+
// Fast path: there's nothing to track so do nothing and ignore siblings.
5362+
return false;
5363+
}
5364+
// Check if we've matched our nearest unfiltered parent so far.
5365+
if (trackedPathMatchInstance === parentInstance) {
5366+
const actualFrame = getVirtualPathFrame(virtualInstance);
5367+
// $FlowFixMe[incompatible-use] found when upgrading Flow
5368+
const expectedFrame = trackedPath[trackedPathMatchDepth + 1];
5369+
if (expectedFrame === undefined) {
5370+
throw new Error('Expected to see a frame at the next depth.');
5371+
}
5372+
if (
5373+
actualFrame.index === expectedFrame.index &&
5374+
actualFrame.key === expectedFrame.key &&
5375+
actualFrame.displayName === expectedFrame.displayName
5376+
) {
5377+
// We have our next match.
5378+
trackedPathMatchFiber = null; // Don't bother looking in Fibers anymore. We're deeper now.
5379+
trackedPathMatchInstance = virtualInstance;
5380+
trackedPathMatchDepth++;
5381+
// Are we out of frames to match?
5382+
// $FlowFixMe[incompatible-use] found when upgrading Flow
5383+
if (trackedPathMatchDepth === trackedPath.length - 1) {
5384+
// There's nothing that can possibly match afterwards.
5385+
// Don't check the children.
5386+
mightBeOnTrackedPath = false;
5387+
} else {
5388+
// Check the children, as they might reveal the next match.
5389+
mightBeOnTrackedPath = true;
5390+
}
5391+
// In either case, since we have a match, we don't need
5392+
// to check the siblings. They'll never match.
5393+
return false;
5394+
}
5395+
}
5396+
if (trackedPathMatchFiber !== null) {
5397+
// We're still looking for a Fiber which might be underneath this instance.
5398+
return true;
5399+
}
5400+
// This Instance's parent is on the path, but this Instance itself isn't.
5401+
// There's no need to check its children--they won't be on the path either.
5402+
mightBeOnTrackedPath = false;
5403+
// However, one of its siblings may be on the path so keep searching.
5404+
return true;
5405+
}
5406+
53325407
function updateTrackedPathStateAfterMount(
53335408
mightSiblingsBeOnTrackedPath: boolean,
53345409
) {
@@ -5428,6 +5503,14 @@ export function attach(
54285503
};
54295504
}
54305505

5506+
function getVirtualPathFrame(virtualInstance: VirtualInstance): PathFrame {
5507+
return {
5508+
displayName: virtualInstance.data.name || '',
5509+
key: virtualInstance.data.key == null ? null : virtualInstance.data.key,
5510+
index: -1, // We use -1 to indicate that this is a virtual path frame.
5511+
};
5512+
}
5513+
54315514
// Produces a serializable representation that does a best effort
54325515
// of identifying a particular Fiber between page reloads.
54335516
// The return path will contain Fibers that are "invisible" to the store
@@ -5437,13 +5520,20 @@ export function attach(
54375520
if (devtoolsInstance === undefined) {
54385521
return null;
54395522
}
5440-
if (devtoolsInstance.kind !== FIBER_INSTANCE) {
5441-
// TODO: Handle VirtualInstance.
5442-
return null;
5443-
}
54445523

5445-
let fiber: null | Fiber = devtoolsInstance.data;
54465524
const keyPath = [];
5525+
5526+
let inst: DevToolsInstance = devtoolsInstance;
5527+
while (inst.kind === VIRTUAL_INSTANCE) {
5528+
keyPath.push(getVirtualPathFrame(inst));
5529+
if (inst.parent === null) {
5530+
// This is a bug but non-essential. We should've found a root instance.
5531+
return null;
5532+
}
5533+
inst = inst.parent;
5534+
}
5535+
5536+
let fiber: null | Fiber = inst.data;
54475537
while (fiber !== null) {
54485538
// $FlowFixMe[incompatible-call] found when upgrading Flow
54495539
keyPath.push(getPathFrame(fiber));
@@ -5459,20 +5549,12 @@ export function attach(
54595549
// Nothing to match.
54605550
return null;
54615551
}
5462-
if (trackedPathMatchFiber === null) {
5552+
if (trackedPathMatchInstance === null) {
54635553
// We didn't find anything.
54645554
return null;
54655555
}
5466-
// Find the closest Fiber store is aware of.
5467-
let fiber: null | Fiber = trackedPathMatchFiber;
5468-
while (fiber !== null && shouldFilterFiber(fiber)) {
5469-
fiber = fiber.return;
5470-
}
5471-
if (fiber === null) {
5472-
return null;
5473-
}
54745556
return {
5475-
id: getFiberIDThrows(fiber),
5557+
id: trackedPathMatchInstance.id,
54765558
// $FlowFixMe[incompatible-use] found when upgrading Flow
54775559
isFullMatch: trackedPathMatchDepth === trackedPath.length - 1,
54785560
};

0 commit comments

Comments
 (0)