Skip to content

Commit b993fb8

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 18f44c6 commit b993fb8

File tree

8 files changed

+98
-46
lines changed

8 files changed

+98
-46
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.getIDForHostInstance(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.getIDForHostInstance(ref.current);
12211221

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

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

+66-18
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import type {
4343
ComponentFilter,
4444
BrowserTheme,
4545
} from 'react-devtools-shared/src/frontend/types';
46-
import {isSynchronousXHRSupported} from './utils';
46+
import {isSynchronousXHRSupported, isReactNativeEnvironment} from './utils';
4747

4848
const debug = (methodName: string, ...args: Array<string>) => {
4949
if (__DEBUG__) {
@@ -343,31 +343,79 @@ export default class Agent extends EventEmitter<{
343343
return renderer.getInstanceAndStyle(id);
344344
}
345345

346-
getBestMatchingRendererInterface(node: Object): RendererInterface | null {
347-
let bestMatch = null;
346+
getIDForHostInstance(target: HostInstance): number | null {
347+
let bestMatch: null | HostInstance = 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: null = renderer.getNearestMountedHostInstance(target);
355+
if (nearestNode !== null) {
356+
if (nearestNode === target) {
357+
// Exact match we can exit early.
358+
bestMatch = nearestNode;
359+
bestRenderer = renderer;
360+
break;
361+
}
362+
if (
363+
bestMatch === null ||
364+
(!isReactNativeEnvironment() && bestMatch.contains(nearestNode))
365+
) {
366+
// If this is the first match or the previous match contains the new match,
367+
// so the new match is a deeper and therefore better match.
368+
bestMatch = nearestNode;
369+
bestRenderer = renderer;
359370
}
360371
}
361372
}
362-
// if an exact match is not found, return the first valid renderer as fallback
363-
return bestMatch;
373+
if (bestRenderer != null && bestMatch != null) {
374+
try {
375+
return bestRenderer.getElementIDForHostInstance(bestMatch, true);
376+
} catch (error) {
377+
// Some old React versions might throw if they can't find a match.
378+
// If so we should ignore it...
379+
}
380+
}
381+
return null;
364382
}
365383

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

619-
selectNode(target: Object): void {
620-
const id = this.getIDForNode(target);
667+
selectNode(target: HostInstance): void {
668+
const id = this.getIDForHostInstance(target);
621669
if (id !== null) {
622670
this._bridge.send('selectElement', id);
623671
}

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(
@@ -4659,7 +4665,7 @@ export function attach(
46594665
flushInitialOperations,
46604666
getBestMatchForTrackedPath,
46614667
getDisplayNameForElementID,
4662-
getFiberForNative,
4668+
getNearestMountedHostInstance,
46634669
getElementIDForHostInstance,
46644670
getInstanceAndStyle,
46654671
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.getComponentNameForHostInstance(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.getIDForHostInstance(node);
197197
if (id !== null) {
198198
bridge.send('selectElement', id);
199199
}

0 commit comments

Comments
 (0)