Skip to content

Commit 2b9fd99

Browse files
committed
Inspect the Screen when inspecting a Root
The Screen is the union of all Roots of all Renderers registered in the Store.
1 parent 31b6954 commit 2b9fd99

File tree

13 files changed

+319
-190
lines changed

13 files changed

+319
-190
lines changed

packages/react-devtools-shared/src/__tests__/store-test.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -974,12 +974,8 @@ describe('Store', () => {
974974
<Suspense name="three" rects={[{x:1,y:2,width:5,height:1}]}>
975975
`);
976976

977-
const rendererID = getRendererID();
978-
const rootID = store.getRootIDForElement(store.getElementIDAtIndex(0));
979977
await actAsync(() => {
980978
agent.overrideSuspenseMilestone({
981-
rendererID,
982-
rootID,
983979
suspendedSet: [
984980
store.getElementIDAtIndex(4),
985981
store.getElementIDAtIndex(8),
@@ -1009,8 +1005,6 @@ describe('Store', () => {
10091005

10101006
await actAsync(() => {
10111007
agent.overrideSuspenseMilestone({
1012-
rendererID,
1013-
rootID,
10141008
suspendedSet: [],
10151009
});
10161010
});

packages/react-devtools-shared/src/backend/agent.js

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
RendererID,
2727
RendererInterface,
2828
DevToolsHookSettings,
29+
InspectedElementPayload,
2930
} from './types';
3031
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
3132
import type {GroupItem} from './views/TraceUpdates/canvas';
@@ -73,6 +74,13 @@ type InspectElementParams = {
7374
requestID: number,
7475
};
7576

77+
type InspectScreenParams = {
78+
forceFullData: boolean,
79+
id: number,
80+
path: Array<string | number> | null,
81+
requestID: number,
82+
};
83+
7684
type OverrideHookParams = {
7785
id: number,
7886
hookID: number,
@@ -131,8 +139,6 @@ type OverrideSuspenseParams = {
131139
};
132140

133141
type OverrideSuspenseMilestoneParams = {
134-
rendererID: number,
135-
rootID: number,
136142
suspendedSet: Array<number>,
137143
};
138144

@@ -201,6 +207,7 @@ export default class Agent extends EventEmitter<{
201207
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
202208
bridge.addListener('getOwnersList', this.getOwnersList);
203209
bridge.addListener('inspectElement', this.inspectElement);
210+
bridge.addListener('inspectScreen', this.inspectScreen);
204211
bridge.addListener('logElementToConsole', this.logElementToConsole);
205212
bridge.addListener('overrideError', this.overrideError);
206213
bridge.addListener('overrideSuspense', this.overrideSuspense);
@@ -531,6 +538,48 @@ export default class Agent extends EventEmitter<{
531538
}
532539
};
533540

541+
inspectScreen: InspectScreenParams => void = ({
542+
requestID,
543+
id,
544+
forceFullData,
545+
path,
546+
}) => {
547+
const payload: InspectedElementPayload = {
548+
type: 'no-change',
549+
id,
550+
responseID: requestID,
551+
};
552+
for (const rendererID in this._rendererInterfaces) {
553+
const renderer = ((this._rendererInterfaces[
554+
(rendererID: any)
555+
]: any): RendererInterface);
556+
const inspectedRoots = renderer.inspectRoots(
557+
requestID,
558+
id,
559+
path,
560+
forceFullData,
561+
);
562+
switch (inspectedRoots.type) {
563+
case 'hydrated-path':
564+
case 'full-data':
565+
// TODO: We can't merge different elements since they might have dehydrated data.
566+
// For now we assume only handle the first renderer that returns data
567+
// The Frontend needs a dedicated InspectedScreen type because most of the
568+
// InspectedElement properties can't be sequashed (e.g. rendererVersion)
569+
this._bridge.send('inspectedScreen', inspectedRoots);
570+
return;
571+
case 'not-found':
572+
continue;
573+
case 'error':
574+
// bail out and show the error
575+
this._bridge.send('inspectedScreen', inspectedRoots);
576+
return;
577+
}
578+
}
579+
580+
this._bridge.send('inspectedScreen', payload);
581+
};
582+
534583
logElementToConsole: ElementAndRendererID => void = ({id, rendererID}) => {
535584
const renderer = this._rendererInterfaces[rendererID];
536585
if (renderer == null) {
@@ -567,17 +616,13 @@ export default class Agent extends EventEmitter<{
567616
};
568617

569618
overrideSuspenseMilestone: OverrideSuspenseMilestoneParams => void = ({
570-
rendererID,
571-
rootID,
572619
suspendedSet,
573620
}) => {
574-
const renderer = this._rendererInterfaces[rendererID];
575-
if (renderer == null) {
576-
console.warn(
577-
`Invalid renderer id "${rendererID}" to override suspense milestone`,
578-
);
579-
} else {
580-
renderer.overrideSuspenseMilestone(rootID, suspendedSet);
621+
for (const rendererID in this._rendererInterfaces) {
622+
const renderer = ((this._rendererInterfaces[
623+
(rendererID: any)
624+
]: any): RendererInterface);
625+
renderer.overrideSuspenseMilestone(suspendedSet);
581626
}
582627
};
583628

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

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4832,7 +4832,11 @@ export function attach(
48324832
fiberInstance.data = nextFiber;
48334833
if (
48344834
mostRecentlyInspectedElement !== null &&
4835-
mostRecentlyInspectedElement.id === fiberInstance.id &&
4835+
(mostRecentlyInspectedElement.id === fiberInstance.id ||
4836+
// If we're inspecting a Root, we inspect the Screen.
4837+
// Invalidating any Root invalidates the Screen too.
4838+
(mostRecentlyInspectedElement.type === ElementTypeRoot &&
4839+
nextFiber.tag === HostRoot)) &&
48364840
didFiberRender(prevFiber, nextFiber)
48374841
) {
48384842
// If this Fiber has updated, clear cached inspected data.
@@ -6335,7 +6339,10 @@ export function attach(
63356339
return inspectVirtualInstanceRaw(devtoolsInstance);
63366340
}
63376341
if (devtoolsInstance.kind === FIBER_INSTANCE) {
6338-
return inspectFiberInstanceRaw(devtoolsInstance);
6342+
const isRoot = devtoolsInstance.parent === null;
6343+
return isRoot
6344+
? inspectRootsRaw(devtoolsInstance.id)
6345+
: inspectFiberInstanceRaw(devtoolsInstance);
63396346
}
63406347
(devtoolsInstance: FilteredFiberInstance); // assert exhaustive
63416348
throw new Error('Unsupported instance kind');
@@ -7122,6 +7129,98 @@ export function attach(
71227129
};
71237130
}
71247131
7132+
function inspectRootsRaw(id: number): InspectedElement | null {
7133+
const roots = hook.getFiberRoots(rendererID);
7134+
if (roots.size === 0) {
7135+
return null;
7136+
}
7137+
7138+
const inspectedRoots: InspectedElement = {
7139+
// invariants
7140+
id: id,
7141+
rendererPackageName: renderer.rendererPackageName,
7142+
rendererVersion: renderer.version,
7143+
type: ElementTypeRoot,
7144+
// Properties we merge
7145+
isErrored: false,
7146+
errors: [],
7147+
warnings: [],
7148+
suspendedBy: [],
7149+
suspendedByRange: null,
7150+
// Properties where merging doesn't make sense so we ignore them entirely in the UI
7151+
rootType: null,
7152+
plugins: {stylex: null},
7153+
nativeTag: null,
7154+
env: null,
7155+
source: null,
7156+
stack: null,
7157+
unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
7158+
// These don't make sense for a Root. They're just bottom values.
7159+
key: null,
7160+
canEditFunctionProps: false,
7161+
canEditHooks: false,
7162+
canEditFunctionPropsDeletePaths: false,
7163+
canEditFunctionPropsRenamePaths: false,
7164+
canEditHooksAndDeletePaths: false,
7165+
canEditHooksAndRenamePaths: false,
7166+
canToggleError: false,
7167+
canToggleSuspense: false,
7168+
isSuspended: false,
7169+
hasLegacyContext: false,
7170+
context: null,
7171+
hooks: null,
7172+
props: null,
7173+
state: null,
7174+
owners: null,
7175+
};
7176+
7177+
let minSuspendedByRange = Infinity;
7178+
let maxSuspendedByRange = -Infinity;
7179+
roots.forEach(root => {
7180+
const rootInstance = rootToFiberInstanceMap.get(root);
7181+
if (rootInstance === undefined) {
7182+
throw new Error(
7183+
'Expected a root instance to exist for this Fiber root',
7184+
);
7185+
}
7186+
const inspectedRoot = inspectFiberInstanceRaw(rootInstance);
7187+
if (inspectedRoot === null) {
7188+
return;
7189+
}
7190+
7191+
if (inspectedRoot.isErrored) {
7192+
inspectedRoots.isErrored = true;
7193+
}
7194+
for (let i = 0; i < inspectedRoot.errors.length; i++) {
7195+
inspectedRoots.errors.push(inspectedRoot.errors[i]);
7196+
}
7197+
for (let i = 0; i < inspectedRoot.warnings.length; i++) {
7198+
inspectedRoots.warnings.push(inspectedRoot.warnings[i]);
7199+
}
7200+
for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) {
7201+
inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]);
7202+
}
7203+
const suspendedByRange = inspectedRoot.suspendedByRange;
7204+
if (suspendedByRange !== null) {
7205+
if (suspendedByRange[0] < minSuspendedByRange) {
7206+
minSuspendedByRange = suspendedByRange[0];
7207+
}
7208+
if (suspendedByRange[1] > maxSuspendedByRange) {
7209+
maxSuspendedByRange = suspendedByRange[1];
7210+
}
7211+
}
7212+
});
7213+
7214+
if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) {
7215+
inspectedRoots.suspendedByRange = [
7216+
minSuspendedByRange,
7217+
maxSuspendedByRange,
7218+
];
7219+
}
7220+
7221+
return inspectedRoots;
7222+
}
7223+
71257224
function logElementToConsole(id: number) {
71267225
const result = isMostRecentlyInspectedElementCurrent(id)
71277226
? mostRecentlyInspectedElement
@@ -7768,13 +7867,9 @@ export function attach(
77687867
77697868
/**
77707869
* Resets the all other roots of this renderer.
7771-
* @param rootID The root that contains this milestone
77727870
* @param suspendedSet List of IDs of SuspenseComponent Fibers
77737871
*/
7774-
function overrideSuspenseMilestone(
7775-
rootID: FiberInstance['id'],
7776-
suspendedSet: Array<FiberInstance['id']>,
7777-
) {
7872+
function overrideSuspenseMilestone(suspendedSet: Array<FiberInstance['id']>) {
77787873
if (
77797874
typeof setSuspenseHandler !== 'function' ||
77807875
typeof scheduleUpdate !== 'function'
@@ -7784,7 +7879,6 @@ export function attach(
77847879
);
77857880
}
77867881
7787-
// TODO: Allow overriding the timeline for the specified root.
77887882
forceFallbackForFibers.forEach(fiber => {
77897883
scheduleUpdate(fiber);
77907884
});
@@ -8314,6 +8408,7 @@ export function attach(
83148408
handlePostCommitFiberRoot,
83158409
hasElementWithId,
83168410
inspectElement,
8411+
inspectRoots: inspectElement,
83178412
logElementToConsole,
83188413
getComponentStack,
83198414
getElementAttributeByPath,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ export function attach(
198198
type: 'not-found',
199199
};
200200
},
201+
inspectRoots(requestID: number, arbitraryRootID: number) {
202+
return {
203+
id: arbitraryRootID,
204+
responseID: requestID,
205+
type: 'not-found',
206+
};
207+
},
201208
logElementToConsole() {},
202209
getElementAttributeByPath() {},
203210
getElementSourceFunctionById() {},

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,20 @@ export function attach(
793793
};
794794
}
795795

796+
function inspectRoots(
797+
requestID: number,
798+
arbitraryRootID: number,
799+
path: Array<string | number> | null,
800+
forceFullData: boolean,
801+
): InspectedElementPayload {
802+
// TODO: Implement once we figured out how to merge InspectedElements on the Frontend.
803+
return {
804+
id: arbitraryRootID,
805+
responseID: requestID,
806+
type: 'not-found',
807+
};
808+
}
809+
796810
function inspectElementRaw(id: number): InspectedElement | null {
797811
const internalInstance = idToInternalInstanceMap.get(id);
798812

@@ -1176,6 +1190,7 @@ export function attach(
11761190
handlePostCommitFiberRoot,
11771191
hasElementWithId,
11781192
inspectElement,
1193+
inspectRoots,
11791194
logElementToConsole,
11801195
overrideError,
11811196
overrideSuspense,

packages/react-devtools-shared/src/backend/types.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,14 +433,17 @@ export type RendererInterface = {
433433
inspectedPaths: Object,
434434
forceFullData: boolean,
435435
) => InspectedElementPayload,
436+
inspectRoots: (
437+
requestID: number,
438+
arbitraryRootID: number,
439+
path: Array<string | number> | null,
440+
forceFullData: boolean,
441+
) => InspectedElementPayload,
436442
logElementToConsole: (id: number) => void,
437443
onErrorOrWarning?: OnErrorOrWarning,
438444
overrideError: (id: number, forceError: boolean) => void,
439445
overrideSuspense: (id: number, forceFallback: boolean) => void,
440-
overrideSuspenseMilestone: (
441-
rootID: number,
442-
suspendedSet: Array<number>,
443-
) => void,
446+
overrideSuspenseMilestone: (suspendedSet: Array<number>) => void,
444447
overrideValueAtPath: (
445448
type: Type,
446449
id: number,

packages/react-devtools-shared/src/backendAPI.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export function inspectElement(
9595
id: number,
9696
path: InspectedElementPath | null,
9797
rendererID: number,
98-
shouldListenToPauseEvents: boolean = false,
98+
shouldListenToPauseEvents: boolean,
9999
): Promise<InspectedElementPayload> {
100100
const requestID = requestCounter++;
101101
const promise = getPromiseForRequestID<InspectedElementPayload>(
@@ -117,6 +117,32 @@ export function inspectElement(
117117
return promise;
118118
}
119119

120+
export function inspectScreen(
121+
bridge: FrontendBridge,
122+
forceFullData: boolean,
123+
arbitraryRootID: number,
124+
path: InspectedElementPath | null,
125+
shouldListenToPauseEvents: boolean,
126+
): Promise<InspectedElementPayload> {
127+
const requestID = requestCounter++;
128+
const promise = getPromiseForRequestID<InspectedElementPayload>(
129+
requestID,
130+
'inspectedScreen',
131+
bridge,
132+
`Timed out while inspecting screen.`,
133+
shouldListenToPauseEvents,
134+
);
135+
136+
bridge.send('inspectScreen', {
137+
requestID,
138+
id: arbitraryRootID,
139+
path,
140+
forceFullData,
141+
});
142+
143+
return promise;
144+
}
145+
120146
let storeAsGlobalCount = 0;
121147

122148
export function storeAsGlobal({

0 commit comments

Comments
 (0)