Skip to content

Commit a53c94e

Browse files
committed
Implement "best renderer" by taking the inner most matched node
Don't leak the "Fiber" type outside the renderer interface.
1 parent 889d280 commit a53c94e

File tree

8 files changed

+90
-45
lines changed

8 files changed

+90
-45
lines changed

packages/react-devtools-shared/src/__tests__/legacy/storeLegacy-v15-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ describe('Store (legacy)', () => {
905905
[root]
906906
▸ <Wrapper>
907907
`);
908-
const deepestedNodeID = global.agent.getIDForNode(ref);
908+
const deepestedNodeID = global.agent.getIDForDOMNode(ref);
909909
act(() => store.toggleIsCollapsed(deepestedNodeID, false));
910910
expect(store).toMatchInlineSnapshot(`
911911
[root]

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,7 @@ describe('Store', () => {
12171217
▸ <Wrapper>
12181218
`);
12191219

1220-
const deepestedNodeID = agent.getIDForNode(ref.current);
1220+
const deepestedNodeID = agent.getIDForDOMNode(ref.current);
12211221

12221222
act(() => store.toggleIsCollapsed(deepestedNodeID, false));
12231223
expect(store).toMatchInlineSnapshot(`

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

+58-17
Original file line numberDiff line numberDiff line change
@@ -343,31 +343,72 @@ export default class Agent extends EventEmitter<{
343343
return renderer.getInstanceAndStyle(id);
344344
}
345345

346-
getBestMatchingRendererInterface(node: Object): RendererInterface | null {
347-
let bestMatch = null;
346+
getIDForDOMNode(node: HTMLElement): number | null {
347+
let bestMatch: null | HTMLElement = null;
348+
let bestRenderer: null | RendererInterface = null;
349+
// Find the nearest ancestor which is mounted by a React.
348350
for (const rendererID in this._rendererInterfaces) {
349351
const renderer = ((this._rendererInterfaces[
350352
(rendererID: any)
351353
]: any): RendererInterface);
352-
const fiber = renderer.getFiberForNative(node);
353-
if (fiber !== null) {
354-
// check if fiber.stateNode is matching the original hostInstance
355-
if (fiber.stateNode === node) {
356-
return renderer;
357-
} else if (bestMatch === null) {
358-
bestMatch = renderer;
354+
const nearestNode = renderer.getNearestMountedHostInstance(node);
355+
if (nearestNode !== null) {
356+
if (nearestNode === node) {
357+
// Exact match we can exit early.
358+
bestMatch = nearestNode;
359+
bestRenderer = renderer;
360+
break;
361+
}
362+
if (bestMatch === null || bestMatch.contains(nearestNode)) {
363+
// If this is the first match or the previous match contains the new match,
364+
// so the new match is a deeper and therefore better match.
365+
bestMatch = nearestNode;
366+
bestRenderer = renderer;
359367
}
360368
}
361369
}
362-
// if an exact match is not found, return the first valid renderer as fallback
363-
return bestMatch;
370+
if (bestRenderer != null && bestMatch != null) {
371+
try {
372+
return bestRenderer.getElementIDForHostInstance(bestMatch, true);
373+
} catch (error) {
374+
// Some old React versions might throw if they can't find a match.
375+
// If so we should ignore it...
376+
}
377+
}
378+
return null;
364379
}
365380

366-
getIDForNode(node: Object): number | null {
367-
const rendererInterface = this.getBestMatchingRendererInterface(node);
368-
if (rendererInterface != null) {
381+
getComponentNameForDOMNode(node: HTMLElement): string | null {
382+
let bestMatch: null | HTMLElement = null;
383+
let bestRenderer: null | RendererInterface = null;
384+
// Find the nearest ancestor which is mounted by a React.
385+
// We duplicate this code from getIDForDOMNode to avoid an object allocation.
386+
for (const rendererID in this._rendererInterfaces) {
387+
const renderer = ((this._rendererInterfaces[
388+
(rendererID: any)
389+
]: any): RendererInterface);
390+
const nearestNode = renderer.getNearestMountedHostInstance(node);
391+
if (nearestNode !== null) {
392+
if (nearestNode === node) {
393+
// Exact match we can exit early.
394+
bestMatch = nearestNode;
395+
bestRenderer = renderer;
396+
break;
397+
}
398+
if (bestMatch === null || bestMatch.contains(nearestNode)) {
399+
// If this is the first match or the previous match contains the new match,
400+
// so the new match is a deeper and therefore better match.
401+
bestMatch = nearestNode;
402+
bestRenderer = renderer;
403+
}
404+
}
405+
}
406+
if (bestRenderer != null && bestMatch != null) {
369407
try {
370-
return rendererInterface.getElementIDForHostInstance(node, true);
408+
const id = bestRenderer.getElementIDForHostInstance(node, true);
409+
if (id) {
410+
return bestRenderer.getDisplayNameForElementID(id);
411+
}
371412
} catch (error) {
372413
// Some old React versions might throw if they can't find a match.
373414
// If so we should ignore it...
@@ -616,8 +657,8 @@ export default class Agent extends EventEmitter<{
616657
}
617658
};
618659

619-
selectNode(target: Object): void {
620-
const id = this.getIDForNode(target);
660+
selectNode(target: HTMLElement): void {
661+
const id = this.getIDForDOMNode(target);
621662
if (id !== null) {
622663
this._bridge.send('selectElement', id);
623664
}

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -2882,8 +2882,14 @@ export function attach(
28822882
return fiber != null ? getDisplayNameForFiber(fiber) : null;
28832883
}
28842884

2885-
function getFiberForNative(hostInstance: HostInstance) {
2886-
return renderer.findFiberByHostInstance(hostInstance);
2885+
function getNearestMountedHostInstance(
2886+
hostInstance: HostInstance,
2887+
): null | HostInstance {
2888+
const mountedHostInstance = renderer.findFiberByHostInstance(hostInstance);
2889+
if (mountedHostInstance != null) {
2890+
return mountedHostInstance.stateNode;
2891+
}
2892+
return null;
28872893
}
28882894

28892895
function getElementIDForHostInstance(
@@ -4661,7 +4667,7 @@ export function attach(
46614667
getComponentStackForFiber,
46624668
getSourceForFiber,
46634669
getDisplayNameForElementID,
4664-
getFiberForNative,
4670+
getNearestMountedHostInstance,
46654671
getElementIDForHostInstance,
46664672
getInstanceAndStyle,
46674673
getOwnersList,

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

+13-4
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ export function attach(
145145
let getElementIDForHostInstance: GetElementIDForHostInstance =
146146
((null: any): GetElementIDForHostInstance);
147147
let findHostInstanceForInternalID: (id: number) => ?HostInstance;
148-
let getFiberForNative = (node: HostInstance) => {
148+
let getNearestMountedHostInstance = (
149+
node: HostInstance,
150+
): null | HostInstance => {
149151
// Not implemented.
150152
return null;
151153
};
@@ -160,8 +162,15 @@ export function attach(
160162
const internalInstance = idToInternalInstanceMap.get(id);
161163
return renderer.ComponentTree.getNodeFromInstance(internalInstance);
162164
};
163-
getFiberForNative = (node: HostInstance) => {
164-
return renderer.ComponentTree.getClosestInstanceFromNode(node);
165+
getNearestMountedHostInstance = (
166+
node: HostInstance,
167+
): null | HostInstance => {
168+
const internalInstance =
169+
renderer.ComponentTree.getClosestInstanceFromNode(node);
170+
if (internalInstance != null) {
171+
return renderer.ComponentTree.getNodeFromInstance(internalInstance);
172+
}
173+
return null;
165174
};
166175
} else if (renderer.Mount.getID && renderer.Mount.getNode) {
167176
getElementIDForHostInstance = (node, findNearestUnfilteredAncestor) => {
@@ -1111,7 +1120,7 @@ export function attach(
11111120
flushInitialOperations,
11121121
getBestMatchForTrackedPath,
11131122
getDisplayNameForElementID,
1114-
getFiberForNative,
1123+
getNearestMountedHostInstance,
11151124
getElementIDForHostInstance,
11161125
getInstanceAndStyle,
11171126
findHostInstancesForElementID: (id: number) => {

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,7 @@ type SharedInternalsSubset = {
8686
};
8787
export type CurrentDispatcherRef = SharedInternalsSubset;
8888

89-
export type GetDisplayNameForElementID = (
90-
id: number,
91-
findNearestUnfilteredAncestor?: boolean,
92-
) => string | null;
89+
export type GetDisplayNameForElementID = (id: number) => string | null;
9390

9491
export type GetElementIDForHostInstance = (
9592
component: HostInstance,
@@ -363,7 +360,9 @@ export type RendererInterface = {
363360
findHostInstancesForElementID: FindHostInstancesForElementID,
364361
flushInitialOperations: () => void,
365362
getBestMatchForTrackedPath: () => PathMatch | null,
366-
getFiberForNative: (component: HostInstance) => Fiber | null,
363+
getNearestMountedHostInstance: (
364+
component: HostInstance,
365+
) => HostInstance | null,
367366
getElementIDForHostInstance: GetElementIDForHostInstance,
368367
getDisplayNameForElementID: GetDisplayNameForElementID,
369368
getInstanceAndStyle(id: number): InstanceAndStyle,

packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js

+3-13
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,9 @@ export default class Overlay {
233233
name = elements[0].nodeName.toLowerCase();
234234

235235
const node = elements[0];
236-
const rendererInterface =
237-
this.agent.getBestMatchingRendererInterface(node);
238-
if (rendererInterface) {
239-
const id = rendererInterface.getElementIDForHostInstance(node, true);
240-
if (id) {
241-
const ownerName = rendererInterface.getDisplayNameForElementID(
242-
id,
243-
true,
244-
);
245-
if (ownerName) {
246-
name += ' (in ' + ownerName + ')';
247-
}
248-
}
236+
const ownerName = this.agent.getComponentNameForDOMNode(node);
237+
if (ownerName) {
238+
name += ' (in ' + ownerName + ')';
249239
}
250240
}
251241

packages/react-devtools-shared/src/backend/views/Highlighter/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export default function setupHighlighter(
193193

194194
const selectElementForNode = throttle(
195195
memoize((node: HTMLElement) => {
196-
const id = agent.getIDForNode(node);
196+
const id = agent.getIDForDOMNode(node);
197197
if (id !== null) {
198198
bridge.send('selectElement', id);
199199
}

0 commit comments

Comments
 (0)