Skip to content

Commit 01ae2dd

Browse files
authored
[DevTools] Include some Filtered Fiber Instances (#30865)
When we filter Fiber Instances where have no way to recover our position in the Fiber tree. The extreme form of this is if you filter out all the Fibers and keep only Server Components. This affects operations that are performed against fibers such as collecting Host Instances for highlighting or emulating suspending/erroring. Conceptually we don't need to add this into the DevToolsInstance tree because we only need to get to some Fibers from a VirtualInstance. A Virtual Instance can contain more than one conceptual child Fiber. It would be easier if we didn't include them in the tree on one hand because we could just traverse the tree and assume it looks like the one on the frontend. But it's also tricky to manage the lifetime. So I went with a special FilteredFiberInstance node in the tree. Currently I only add it if its parent would've been a VirtualInstance since we don't need it in any other cases. If the parent was another FiberInstance it already has a Fiber. There might be need for always tracking all Instances whether they're filtered or not or just moving filtering to the frontend but for now I'm keeping the general architecture as is.
1 parent f820f5a commit 01ae2dd

File tree

1 file changed

+127
-27
lines changed
  • packages/react-devtools-shared/src/backend/fiber

1 file changed

+127
-27
lines changed

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

+127-27
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
146146
// Kinds
147147
const FIBER_INSTANCE = 0;
148148
const VIRTUAL_INSTANCE = 1;
149+
const FILTERED_FIBER_INSTANCE = 2;
149150

150151
// Flags
151152
const FORCE_SUSPENSE_FALLBACK = /* */ 0b001;
@@ -157,9 +158,9 @@ const FORCE_ERROR_RESET = /* */ 0b100;
157158
type FiberInstance = {
158159
kind: 0,
159160
id: number,
160-
parent: null | DevToolsInstance, // filtered parent, including virtual
161-
firstChild: null | DevToolsInstance, // filtered first child, including virtual
162-
nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
161+
parent: null | DevToolsInstance,
162+
firstChild: null | DevToolsInstance,
163+
nextSibling: null | DevToolsInstance,
163164
flags: number, // Force Error/Suspense
164165
source: null | string | Error | Source, // source location of this component function, or owned child stack
165166
errors: null | Map<string, number>, // error messages and count
@@ -184,6 +185,39 @@ function createFiberInstance(fiber: Fiber): FiberInstance {
184185
};
185186
}
186187

188+
type FilteredFiberInstance = {
189+
kind: 2,
190+
// We exclude id from the type to get errors if we try to access it.
191+
// However it is still in the object to preserve hidden class.
192+
// id: number,
193+
parent: null | DevToolsInstance,
194+
firstChild: null | DevToolsInstance,
195+
nextSibling: null | DevToolsInstance,
196+
flags: number, // Force Error/Suspense
197+
source: null | string | Error | Source, // always null here.
198+
errors: null, // error messages and count
199+
warnings: null, // warning messages and count
200+
treeBaseDuration: number, // the profiled time of the last render of this subtree
201+
data: Fiber, // one of a Fiber pair
202+
};
203+
204+
// This is used to represent a filtered Fiber but still lets us find its host instance.
205+
function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance {
206+
return ({
207+
kind: FILTERED_FIBER_INSTANCE,
208+
id: 0,
209+
parent: null,
210+
firstChild: null,
211+
nextSibling: null,
212+
flags: 0,
213+
componentStack: null,
214+
errors: null,
215+
warnings: null,
216+
treeBaseDuration: 0,
217+
data: fiber,
218+
}: any);
219+
}
220+
187221
// This type represents a stateful instance of a Server Component or a Component
188222
// that gets optimized away - e.g. call-through without creating a Fiber.
189223
// It's basically a virtual Fiber. This is not a semantic concept in React.
@@ -192,9 +226,9 @@ function createFiberInstance(fiber: Fiber): FiberInstance {
192226
type VirtualInstance = {
193227
kind: 1,
194228
id: number,
195-
parent: null | DevToolsInstance, // filtered parent, including virtual
196-
firstChild: null | DevToolsInstance, // filtered first child, including virtual
197-
nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
229+
parent: null | DevToolsInstance,
230+
firstChild: null | DevToolsInstance,
231+
nextSibling: null | DevToolsInstance,
198232
flags: number,
199233
source: null | string | Error | Source, // source location of this server component, or owned child stack
200234
// Errors and Warnings happen per ReactComponentInfo which can appear in
@@ -226,7 +260,7 @@ function createVirtualInstance(
226260
};
227261
}
228262

229-
type DevToolsInstance = FiberInstance | VirtualInstance;
263+
type DevToolsInstance = FiberInstance | VirtualInstance | FilteredFiberInstance;
230264

231265
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
232266
type getTypeSymbolType = (type: any) => symbol | number;
@@ -739,7 +773,8 @@ const fiberToFiberInstanceMap: Map<Fiber, FiberInstance> = new Map();
739773
// Map of id to one (arbitrary) Fiber in a pair.
740774
// This Map is used to e.g. get the display name for a Fiber or schedule an update,
741775
// operations that should be the same whether the current and work-in-progress Fiber is used.
742-
const idToDevToolsInstanceMap: Map<number, DevToolsInstance> = new Map();
776+
const idToDevToolsInstanceMap: Map<number, FiberInstance | VirtualInstance> =
777+
new Map();
743778

744779
// Map of canonical HostInstances to the nearest parent DevToolsInstance.
745780
const publicInstanceToDevToolsInstanceMap: Map<HostInstance, DevToolsInstance> =
@@ -1144,13 +1179,22 @@ export function attach(
11441179
function debugTree(instance: DevToolsInstance, indent: number = 0) {
11451180
if (__DEBUG__) {
11461181
const name =
1147-
(instance.kind === FIBER_INSTANCE
1182+
(instance.kind !== VIRTUAL_INSTANCE
11481183
? getDisplayNameForFiber(instance.data)
11491184
: instance.data.name) || '';
11501185
console.log(
1151-
' '.repeat(indent) + '- ' + instance.id + ' (' + name + ')',
1186+
' '.repeat(indent) +
1187+
'- ' +
1188+
(instance.kind === FILTERED_FIBER_INSTANCE ? 0 : instance.id) +
1189+
' (' +
1190+
name +
1191+
')',
11521192
'parent',
1153-
instance.parent === null ? ' ' : instance.parent.id,
1193+
instance.parent === null
1194+
? ' '
1195+
: instance.parent.kind === FILTERED_FIBER_INSTANCE
1196+
? 0
1197+
: instance.parent.id,
11541198
'next',
11551199
instance.nextSibling === null ? ' ' : instance.nextSibling.id,
11561200
);
@@ -2263,7 +2307,12 @@ export function attach(
22632307
ownerInstance.source = fiber._debugStack;
22642308
}
22652309
const ownerID = ownerInstance === null ? 0 : ownerInstance.id;
2266-
const parentID = parentInstance ? parentInstance.id : 0;
2310+
const parentID = parentInstance
2311+
? parentInstance.kind === FILTERED_FIBER_INSTANCE
2312+
? // A Filtered Fiber Instance will always have a Virtual Instance as a parent.
2313+
((parentInstance.parent: any): VirtualInstance).id
2314+
: parentInstance.id
2315+
: 0;
22672316

22682317
const displayNameStringID = getStringID(displayName);
22692318

@@ -2347,7 +2396,12 @@ export function attach(
23472396
ownerInstance.source = componentInfo.debugStack;
23482397
}
23492398
const ownerID = ownerInstance === null ? 0 : ownerInstance.id;
2350-
const parentID = parentInstance ? parentInstance.id : 0;
2399+
const parentID = parentInstance
2400+
? parentInstance.kind === FILTERED_FIBER_INSTANCE
2401+
? // A Filtered Fiber Instance will always have a Virtual Instance as a parent.
2402+
((parentInstance.parent: any): VirtualInstance).id
2403+
: parentInstance.id
2404+
: 0;
23512405

23522406
const displayNameStringID = getStringID(displayName);
23532407

@@ -2712,6 +2766,25 @@ export function attach(
27122766
if (shouldIncludeInTree) {
27132767
newInstance = recordMount(fiber, reconcilingParent);
27142768
insertChild(newInstance);
2769+
} else if (
2770+
reconcilingParent !== null &&
2771+
reconcilingParent.kind === VIRTUAL_INSTANCE
2772+
) {
2773+
// If the parent is a Virtual Instance and we filtered this Fiber we include a
2774+
// hidden node.
2775+
2776+
if (
2777+
reconcilingParent.data === fiber._debugOwner &&
2778+
fiber._debugStack != null &&
2779+
reconcilingParent.source === null
2780+
) {
2781+
// The new Fiber is directly owned by the parent. Therefore somewhere on the
2782+
// debugStack will be a stack frame inside parent that we can use as its soruce.
2783+
reconcilingParent.source = fiber._debugStack;
2784+
}
2785+
2786+
newInstance = createFilteredFiberInstance(fiber);
2787+
insertChild(newInstance);
27152788
}
27162789

27172790
// If we have the tree selection from previous reload, try to match this Fiber.
@@ -2724,7 +2797,7 @@ export function attach(
27242797
const stashedParent = reconcilingParent;
27252798
const stashedPrevious = previouslyReconciledSibling;
27262799
const stashedRemaining = remainingReconcilingChildren;
2727-
if (shouldIncludeInTree) {
2800+
if (newInstance !== null) {
27282801
// Push a new DevTools instance parent while reconciling this subtree.
27292802
reconcilingParent = newInstance;
27302803
previouslyReconciledSibling = null;
@@ -2809,7 +2882,7 @@ export function attach(
28092882
}
28102883
}
28112884
} finally {
2812-
if (shouldIncludeInTree) {
2885+
if (newInstance !== null) {
28132886
reconcilingParent = stashedParent;
28142887
previouslyReconciledSibling = stashedPrevious;
28152888
remainingReconcilingChildren = stashedRemaining;
@@ -2849,8 +2922,10 @@ export function attach(
28492922
}
28502923
if (instance.kind === FIBER_INSTANCE) {
28512924
recordUnmount(instance);
2852-
} else {
2925+
} else if (instance.kind === VIRTUAL_INSTANCE) {
28532926
recordVirtualUnmount(instance);
2927+
} else {
2928+
untrackFiber(instance, instance.data);
28542929
}
28552930
removeChild(instance, null);
28562931
}
@@ -2955,7 +3030,9 @@ export function attach(
29553030
virtualInstance.treeBaseDuration = treeBaseDuration;
29563031
}
29573032

2958-
function recordResetChildren(parentInstance: DevToolsInstance) {
3033+
function recordResetChildren(
3034+
parentInstance: FiberInstance | VirtualInstance,
3035+
) {
29593036
if (__DEBUG__) {
29603037
if (
29613038
parentInstance.firstChild !== null &&
@@ -2975,7 +3052,17 @@ export function attach(
29753052

29763053
let child: null | DevToolsInstance = parentInstance.firstChild;
29773054
while (child !== null) {
2978-
nextChildren.push(child.id);
3055+
if (child.kind === FILTERED_FIBER_INSTANCE) {
3056+
for (
3057+
let innerChild: null | DevToolsInstance = parentInstance.firstChild;
3058+
innerChild !== null;
3059+
innerChild = innerChild.nextSibling
3060+
) {
3061+
nextChildren.push((innerChild: any).id);
3062+
}
3063+
} else {
3064+
nextChildren.push(child.id);
3065+
}
29793066
child = child.nextSibling;
29803067
}
29813068

@@ -3791,7 +3878,7 @@ export function attach(
37913878
devtoolsInstance: DevToolsInstance,
37923879
hostInstances: Array<HostInstance>,
37933880
) {
3794-
if (devtoolsInstance.kind === FIBER_INSTANCE) {
3881+
if (devtoolsInstance.kind !== VIRTUAL_INSTANCE) {
37953882
const fiber = devtoolsInstance.data;
37963883
appendHostInstancesByFiber(fiber, hostInstances);
37973884
return;
@@ -3892,6 +3979,10 @@ export function attach(
38923979
): number | null {
38933980
const instance = publicInstanceToDevToolsInstanceMap.get(publicInstance);
38943981
if (instance !== undefined) {
3982+
if (instance.kind === FILTERED_FIBER_INSTANCE) {
3983+
// A Filtered Fiber Instance will always have a Virtual Instance as a parent.
3984+
return ((instance.parent: any): VirtualInstance).id;
3985+
}
38953986
return instance.id;
38963987
}
38973988
return null;
@@ -3944,7 +4035,7 @@ export function attach(
39444035
}
39454036

39464037
function instanceToSerializedElement(
3947-
instance: DevToolsInstance,
4038+
instance: FiberInstance | VirtualInstance,
39484039
): SerializedElement {
39494040
if (instance.kind === FIBER_INSTANCE) {
39504041
const fiber = instance.data;
@@ -4039,7 +4130,7 @@ export function attach(
40394130
function findNearestOwnerInstance(
40404131
parentInstance: null | DevToolsInstance,
40414132
owner: void | null | ReactComponentInfo | Fiber,
4042-
): null | DevToolsInstance {
4133+
): null | FiberInstance | VirtualInstance {
40434134
if (owner == null) {
40444135
return null;
40454136
}
@@ -4054,6 +4145,9 @@ export function attach(
40544145
// needs a duck type check anyway.
40554146
parentInstance.data === (owner: any).alternate
40564147
) {
4148+
if (parentInstance.kind === FILTERED_FIBER_INSTANCE) {
4149+
return null;
4150+
}
40574151
return parentInstance;
40584152
}
40594153
parentInstance = parentInstance.parent;
@@ -4131,7 +4225,11 @@ export function attach(
41314225
if (devtoolsInstance.kind === VIRTUAL_INSTANCE) {
41324226
return inspectVirtualInstanceRaw(devtoolsInstance);
41334227
}
4134-
return inspectFiberInstanceRaw(devtoolsInstance);
4228+
if (devtoolsInstance.kind === FIBER_INSTANCE) {
4229+
return inspectFiberInstanceRaw(devtoolsInstance);
4230+
}
4231+
(devtoolsInstance: FilteredFiberInstance); // assert exhaustive
4232+
throw new Error('Unsupported instance kind');
41354233
}
41364234

41374235
function inspectFiberInstanceRaw(
@@ -4434,7 +4532,7 @@ export function attach(
44344532
let targetErrorBoundaryID = null;
44354533
let parent = virtualInstance.parent;
44364534
while (parent !== null) {
4437-
if (parent.kind === FIBER_INSTANCE) {
4535+
if (parent.kind !== VIRTUAL_INSTANCE) {
44384536
targetErrorBoundaryID = getNearestErrorBoundaryID(parent.data);
44394537
let current = parent.data;
44404538
while (current.return !== null) {
@@ -5225,7 +5323,9 @@ export function attach(
52255323
) {
52265324
// We don't need to convert milliseconds to microseconds in this case,
52275325
// because the profiling summary is JSON serialized.
5228-
target.push([instance.id, instance.treeBaseDuration]);
5326+
if (instance.kind !== FILTERED_FIBER_INSTANCE) {
5327+
target.push([instance.id, instance.treeBaseDuration]);
5328+
}
52295329
for (
52305330
let child = instance.firstChild;
52315331
child !== null;
@@ -5444,7 +5544,7 @@ export function attach(
54445544
// In that case, we'll do some extra checks for matching mounts.
54455545
let trackedPath: Array<PathFrame> | null = null;
54465546
let trackedPathMatchFiber: Fiber | null = null; // This is the deepest unfiltered match of a Fiber.
5447-
let trackedPathMatchInstance: DevToolsInstance | null = null; // This is the deepest matched filtered Instance.
5547+
let trackedPathMatchInstance: FiberInstance | VirtualInstance | null = null; // This is the deepest matched filtered Instance.
54485548
let trackedPathMatchDepth = -1;
54495549
let mightBeOnTrackedPath = false;
54505550

@@ -5463,7 +5563,7 @@ export function attach(
54635563
// The return value signals whether we should keep matching siblings or not.
54645564
function updateTrackedPathStateBeforeMount(
54655565
fiber: Fiber,
5466-
fiberInstance: null | FiberInstance,
5566+
fiberInstance: null | FiberInstance | FilteredFiberInstance,
54675567
): boolean {
54685568
if (trackedPath === null || !mightBeOnTrackedPath) {
54695569
// Fast path: there's nothing to track so do nothing and ignore siblings.
@@ -5492,7 +5592,7 @@ export function attach(
54925592
) {
54935593
// We have our next match.
54945594
trackedPathMatchFiber = fiber;
5495-
if (fiberInstance !== null) {
5595+
if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
54965596
trackedPathMatchInstance = fiberInstance;
54975597
}
54985598
trackedPathMatchDepth++;

0 commit comments

Comments
 (0)