@samitouri / QOS-React / commits / d1afcb43fd

[DevTools] Track all public HostInstances in a Map (#30831)

This lets us get from a HostInstance to the nearest DevToolsInstance without relying on `findFiberByHostInstance` and `fiberToDevToolsInstanceMap`. We already did the equivalent of this for Resources in HostHoistables. One issue before was that we'd ideally get away from the `fiberToDevToolsInstanceMap` map in general since we should ideally not treat Fibers as stateful but they could be replaced by something else stateful in principle. This PR also addresses Virtual Instances. Now you can select a DOM node and have it select a Virtual Instance if that's the nearest parent since the parent doesn't have to be a Fiber anymore. However, the other reason for this change is that I'd like to get rid of the need for the `findFiberByHostInstance` from being injected. A renderer should not need to store a reference back from its instance to a Fiber. Without the Synthetic Event system this wouldn't be needed by the renderer so we should be able to remove it. We also don't really need it since we have all the information by just walking the commit to collect the nodes if we just maintain our own Map. There's one subtle nuance that the different renderers do. Typically a HostInstance is the same thing as a PublicInstance in React but technically in Fabric they're not the same. So we need to translate between PublicInstance and HostInstance. I just hardcoded the Fabric implementation of this since it's the only known one that does this but could feature detect other ones too if necessary. On one hand it's more resilient to refactors to not rely on injected helpers and on hand it doesn't follow changes to things like this. For the conflict resolution I added in #30494 I had to make that specific to DOM so we can move the DOM traversal to the backend instead of the injected helper.

Sebastian Markbåge committed Sep 3, 2024 at 17:28 UTC d1afcb43fd506297109c32ff462f6f659f9110ae
6 files changed +280 -218
packages/react-devtools-shared/src/backend/agent.js
+102 -63
@@ -342,84 +342,123 @@ export default class Agent extends EventEmitter<{
342 }
343
344 getIDForHostInstance(target: HostInstance): number | null {
345 - let bestMatch: null | HostInstance = null;
346 - let bestRenderer: null | RendererInterface = null;
347 - // Find the nearest ancestor which is mounted by a React.
348 - for (const rendererID in this._rendererInterfaces) {
349 - const renderer = ((this._rendererInterfaces[
350 - (rendererID: any)
351 - ]: any): RendererInterface);
352 - const nearestNode: null = renderer.getNearestMountedHostInstance(target);
353 - if (nearestNode !== null) {
354 - if (nearestNode === target) {
355 - // Exact match we can exit early.
356 - bestMatch = nearestNode;
357 - bestRenderer = renderer;
358 - break;
345 + if (isReactNativeEnvironment() || typeof target.nodeType !== 'number') {
346 + // In React Native or non-DOM we simply pick any renderer that has a match.
347 + for (const rendererID in this._rendererInterfaces) {
348 + const renderer = ((this._rendererInterfaces[
349 + (rendererID: any)
350 + ]: any): RendererInterface);
351 + try {
352 + const match = renderer.getElementIDForHostInstance(target);
353 + if (match != null) {
354 + return match;
355 + }
356 + } catch (error) {
357 + // Some old React versions might throw if they can't find a match.
358 + // If so we should ignore it...
359 }
360 - if (
361 - bestMatch === null ||
362 - (!isReactNativeEnvironment() && bestMatch.contains(nearestNode))
363 - ) {
364 - // If this is the first match or the previous match contains the new match,
365 - // so the new match is a deeper and therefore better match.
366 - bestMatch = nearestNode;
367 - bestRenderer = renderer;
360 + }
361 + return null;
362 + } else {
363 + // In the DOM we use a smarter mechanism to find the deepest a DOM node
364 + // that is registered if there isn't an exact match.
365 + let bestMatch: null | Element = null;
366 + let bestRenderer: null | RendererInterface = null;
367 + // Find the nearest ancestor which is mounted by a React.
368 + for (const rendererID in this._rendererInterfaces) {
369 + const renderer = ((this._rendererInterfaces[
370 + (rendererID: any)
371 + ]: any): RendererInterface);
372 + const nearestNode: null | Element = renderer.getNearestMountedDOMNode(
373 + (target: any),
374 + );
375 + if (nearestNode !== null) {
376 + if (nearestNode === target) {
377 + // Exact match we can exit early.
378 + bestMatch = nearestNode;
379 + bestRenderer = renderer;
380 + break;
381 + }
382 + if (bestMatch === null || bestMatch.contains(nearestNode)) {
383 + // If this is the first match or the previous match contains the new match,
384 + // so the new match is a deeper and therefore better match.
385 + bestMatch = nearestNode;
386 + bestRenderer = renderer;
387 + }
388 }
389 }
370 - }
371 - if (bestRenderer != null && bestMatch != null) {
372 - try {
373 - return bestRenderer.getElementIDForHostInstance(bestMatch, true);
374 - } catch (error) {
375 - // Some old React versions might throw if they can't find a match.
376 - // If so we should ignore it...
390 + if (bestRenderer != null && bestMatch != null) {
391 + try {
392 + return bestRenderer.getElementIDForHostInstance(bestMatch);
393 + } catch (error) {
394 + // Some old React versions might throw if they can't find a match.
395 + // If so we should ignore it...
396 + }
397 }
398 + return null;
399 }
379 - return null;
400 }
401
402 getComponentNameForHostInstance(target: HostInstance): string | null {
403 // We duplicate this code from getIDForHostInstance to avoid an object allocation.
384 - let bestMatch: null | HostInstance = null;
385 - let bestRenderer: null | RendererInterface = null;
386 - // Find the nearest ancestor which is mounted by a React.
387 - for (const rendererID in this._rendererInterfaces) {
388 - const renderer = ((this._rendererInterfaces[
389 - (rendererID: any)
390 - ]: any): RendererInterface);
391 - const nearestNode = renderer.getNearestMountedHostInstance(target);
392 - if (nearestNode !== null) {
393 - if (nearestNode === target) {
394 - // Exact match we can exit early.
395 - bestMatch = nearestNode;
396 - bestRenderer = renderer;
397 - break;
404 + if (isReactNativeEnvironment() || typeof target.nodeType !== 'number') {
405 + // In React Native or non-DOM we simply pick any renderer that has a match.
406 + for (const rendererID in this._rendererInterfaces) {
407 + const renderer = ((this._rendererInterfaces[
408 + (rendererID: any)
409 + ]: any): RendererInterface);
410 + try {
411 + const id = renderer.getElementIDForHostInstance(target);
412 + if (id) {
413 + return renderer.getDisplayNameForElementID(id);
414 + }
415 + } catch (error) {
416 + // Some old React versions might throw if they can't find a match.
417 + // If so we should ignore it...
418 }
399 - if (
400 - bestMatch === null ||
401 - (!isReactNativeEnvironment() && bestMatch.contains(nearestNode))
402 - ) {
403 - // If this is the first match or the previous match contains the new match,
404 - // so the new match is a deeper and therefore better match.
405 - bestMatch = nearestNode;
406 - bestRenderer = renderer;
419 + }
420 + return null;
421 + } else {
422 + // In the DOM we use a smarter mechanism to find the deepest a DOM node
423 + // that is registered if there isn't an exact match.
424 + let bestMatch: null | Element = null;
425 + let bestRenderer: null | RendererInterface = null;
426 + // Find the nearest ancestor which is mounted by a React.
427 + for (const rendererID in this._rendererInterfaces) {
428 + const renderer = ((this._rendererInterfaces[
429 + (rendererID: any)
430 + ]: any): RendererInterface);
431 + const nearestNode: null | Element = renderer.getNearestMountedDOMNode(
432 + (target: any),
433 + );
434 + if (nearestNode !== null) {
435 + if (nearestNode === target) {
436 + // Exact match we can exit early.
437 + bestMatch = nearestNode;
438 + bestRenderer = renderer;
439 + break;
440 + }
441 + if (bestMatch === null || bestMatch.contains(nearestNode)) {
442 + // If this is the first match or the previous match contains the new match,
443 + // so the new match is a deeper and therefore better match.
444 + bestMatch = nearestNode;
445 + bestRenderer = renderer;
446 + }
447 }
448 }
409 - }
410 -
411 - if (bestRenderer != null && bestMatch != null) {
412 - try {
413 - const id = bestRenderer.getElementIDForHostInstance(bestMatch, true);
414 - if (id) {
415 - return bestRenderer.getDisplayNameForElementID(id);
449 + if (bestRenderer != null && bestMatch != null) {
450 + try {
451 + const id = bestRenderer.getElementIDForHostInstance(bestMatch);
452 + if (id) {
453 + return bestRenderer.getDisplayNameForElementID(id);
454 + }
455 + } catch (error) {
456 + // Some old React versions might throw if they can't find a match.
457 + // If so we should ignore it...
458 }
417 - } catch (error) {
418 - // Some old React versions might throw if they can't find a match.
419 - // If so we should ignore it...
459 }
460 + return null;
461 }
422 - return null;
462 }
463
464 getBackendVersion: () => void = () => {
packages/react-devtools-shared/src/backend/console.js
+1 -11
@@ -135,17 +135,7 @@ export function registerRenderer(
135 renderer: ReactRenderer,
136 onErrorOrWarning?: OnErrorOrWarning,
137 ): void {
138 - const {
139 - currentDispatcherRef,
140 - getCurrentFiber,
141 - findFiberByHostInstance,
142 - version,
143 - } = renderer;
144 -
145 - // Ignore React v15 and older because they don't expose a component stack anyway.
146 - if (typeof findFiberByHostInstance !== 'function') {
147 - return;
148 - }
138 + const {currentDispatcherRef, getCurrentFiber, version} = renderer;
139
140 // currentDispatcherRef gets injected for v16.8+ to support hooks inspection.
141 // getCurrentFiber gets injected for v16.9+.
packages/react-devtools-shared/src/backend/fiber/renderer.js
+163 -129
@@ -738,35 +738,93 @@ const fiberToFiberInstanceMap: Map<Fiber, FiberInstance> = new Map();
738 // operations that should be the same whether the current and work-in-progress Fiber is used.
739 const idToDevToolsInstanceMap: Map<number, DevToolsInstance> = new Map();
740
741 -// Map of resource DOM nodes to all the Fibers that depend on it.
742 -const hostResourceToFiberMap: Map<HostInstance, Set<Fiber>> = new Map();
741 +// Map of canonical HostInstances to the nearest parent DevToolsInstance.
742 +const publicInstanceToDevToolsInstanceMap: Map<HostInstance, DevToolsInstance> =
743 + new Map();
744 +// Map of resource DOM nodes to all the nearest DevToolsInstances that depend on it.
745 +const hostResourceToDevToolsInstanceMap: Map<
746 + HostInstance,
747 + Set<DevToolsInstance>,
748 +> = new Map();
749 +
750 +function getPublicInstance(instance: HostInstance): HostInstance {
751 + // Typically the PublicInstance and HostInstance is the same thing but not in Fabric.
752 + // So we need to detect this and use that as the public instance.
753 + return typeof instance === 'object' &&
754 + instance !== null &&
755 + typeof instance.canonical === 'object'
756 + ? (instance.canonical: any)
757 + : typeof instance._nativeTag === 'number'
758 + ? instance._nativeTag
759 + : instance;
760 +}
761 +
762 +function aquireHostInstance(
763 + nearestInstance: DevToolsInstance,
764 + hostInstance: HostInstance,
765 +): void {
766 + const publicInstance = getPublicInstance(hostInstance);
767 + publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);
768 +}
769 +
770 +function releaseHostInstance(
771 + nearestInstance: DevToolsInstance,
772 + hostInstance: HostInstance,
773 +): void {
774 + const publicInstance = getPublicInstance(hostInstance);
775 + if (
776 + publicInstanceToDevToolsInstanceMap.get(publicInstance) === nearestInstance
777 + ) {
778 + publicInstanceToDevToolsInstanceMap.delete(publicInstance);
779 + }
780 +}
781
782 function aquireHostResource(
745 - fiber: Fiber,
783 + nearestInstance: DevToolsInstance,
784 resource: ?{instance?: HostInstance},
785 ): void {
786 const hostInstance = resource && resource.instance;
787 if (hostInstance) {
750 - let resourceFibers = hostResourceToFiberMap.get(hostInstance);
751 - if (resourceFibers === undefined) {
752 - resourceFibers = new Set();
753 - hostResourceToFiberMap.set(hostInstance, resourceFibers);
788 + const publicInstance = getPublicInstance(hostInstance);
789 + let resourceInstances =
790 + hostResourceToDevToolsInstanceMap.get(publicInstance);
791 + if (resourceInstances === undefined) {
792 + resourceInstances = new Set();
793 + hostResourceToDevToolsInstanceMap.set(publicInstance, resourceInstances);
794 + // Store the first match in the main map for quick access when selecting DOM node.
795 + publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);
796 }
755 - resourceFibers.add(fiber);
797 + resourceInstances.add(nearestInstance);
798 }
799 }
800
801 function releaseHostResource(
760 - fiber: Fiber,
802 + nearestInstance: DevToolsInstance,
803 resource: ?{instance?: HostInstance},
804 ): void {
805 const hostInstance = resource && resource.instance;
806 if (hostInstance) {
765 - const resourceFibers = hostResourceToFiberMap.get(hostInstance);
766 - if (resourceFibers !== undefined) {
767 - resourceFibers.delete(fiber);
768 - if (resourceFibers.size === 0) {
769 - hostResourceToFiberMap.delete(hostInstance);
807 + const publicInstance = getPublicInstance(hostInstance);
808 + const resourceInstances =
809 + hostResourceToDevToolsInstanceMap.get(publicInstance);
810 + if (resourceInstances !== undefined) {
811 + resourceInstances.delete(nearestInstance);
812 + if (resourceInstances.size === 0) {
813 + hostResourceToDevToolsInstanceMap.delete(publicInstance);
814 + publicInstanceToDevToolsInstanceMap.delete(publicInstance);
815 + } else if (
816 + publicInstanceToDevToolsInstanceMap.get(publicInstance) ===
817 + nearestInstance
818 + ) {
819 + // This was the first one. Store the next first one in the main map for easy access.
820 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
821 + for (const firstInstance of resourceInstances) {
822 + publicInstanceToDevToolsInstanceMap.set(
823 + firstInstance,
824 + nearestInstance,
825 + );
826 + break;
827 + }
828 }
829 }
830 }
@@ -1467,50 +1525,29 @@ export function attach(
1525
1526 // Removes a Fiber (and its alternate) from the Maps used to track their id.
1527 // This method should always be called when a Fiber is unmounting.
1470 - function untrackFiber(fiberInstance: FiberInstance) {
1528 + function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) {
1529 if (__DEBUG__) {
1472 - debug('untrackFiber()', fiberInstance.data, null);
1473 - }
1474 -
1475 - idToDevToolsInstanceMap.delete(fiberInstance.id);
1476 -
1477 - const fiber = fiberInstance.data;
1478 -
1479 - // Restore any errors/warnings associated with this fiber to the pending
1480 - // map. I.e. treat it as before we tracked the instances. This lets us
1481 - // restore them if we remount the same Fibers later. Otherwise we rely
1482 - // on the GC of the Fibers to clean them up.
1483 - if (fiberInstance.errors !== null) {
1484 - pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
1485 - fiberInstance.errors = null;
1486 - }
1487 - if (fiberInstance.warnings !== null) {
1488 - pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
1489 - fiberInstance.warnings = null;
1530 + debug('untrackFiber()', fiber, null);
1531 }
1532 + // TODO: Consider using a WeakMap instead. The only thing where that doesn't work
1533 + // is React Native Paper which tracks tags but that support is eventually going away
1534 + // and can use the old findFiberByHostInstance strategy.
1535
1492 - if (fiberInstance.flags & FORCE_ERROR) {
1493 - fiberInstance.flags &= ~FORCE_ERROR;
1494 - forceErrorCount--;
1495 - if (forceErrorCount === 0 && setErrorHandler != null) {
1496 - setErrorHandler(shouldErrorFiberAlwaysNull);
1497 - }
1498 - }
1499 - if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
1500 - fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
1501 - forceFallbackCount--;
1502 - if (forceFallbackCount === 0 && setSuspenseHandler != null) {
1503 - setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
1504 - }
1536 + if (fiber.tag === HostHoistable) {
1537 + releaseHostResource(nearestInstance, fiber.memoizedState);
1538 + } else if (
1539 + fiber.tag === HostComponent ||
1540 + fiber.tag === HostText ||
1541 + fiber.tag === HostSingleton
1542 + ) {
1543 + releaseHostInstance(nearestInstance, fiber.stateNode);
1544 }
1545
1507 - if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
1508 - fiberToFiberInstanceMap.delete(fiber);
1509 - }
1510 - const {alternate} = fiber;
1511 - if (alternate !== null) {
1512 - if (fiberToFiberInstanceMap.get(alternate) === fiberInstance) {
1513 - fiberToFiberInstanceMap.delete(alternate);
1546 + // Recursively clean up any filtered Fibers below this one as well since
1547 + // we won't recordUnmount on those.
1548 + for (let child = fiber.child; child !== null; child = child.sibling) {
1549 + if (shouldFilterFiber(child)) {
1550 + untrackFiber(nearestInstance, child);
1551 }
1552 }
1553 }
@@ -2355,7 +2392,47 @@ export function attach(
2392 pendingRealUnmountedIDs.push(id);
2393 }
2394
2358 - untrackFiber(fiberInstance);
2395 + idToDevToolsInstanceMap.delete(fiberInstance.id);
2396 +
2397 + // Restore any errors/warnings associated with this fiber to the pending
2398 + // map. I.e. treat it as before we tracked the instances. This lets us
2399 + // restore them if we remount the same Fibers later. Otherwise we rely
2400 + // on the GC of the Fibers to clean them up.
2401 + if (fiberInstance.errors !== null) {
2402 + pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
2403 + fiberInstance.errors = null;
2404 + }
2405 + if (fiberInstance.warnings !== null) {
2406 + pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
2407 + fiberInstance.warnings = null;
2408 + }
2409 +
2410 + if (fiberInstance.flags & FORCE_ERROR) {
2411 + fiberInstance.flags &= ~FORCE_ERROR;
2412 + forceErrorCount--;
2413 + if (forceErrorCount === 0 && setErrorHandler != null) {
2414 + setErrorHandler(shouldErrorFiberAlwaysNull);
2415 + }
2416 + }
2417 + if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
2418 + fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
2419 + forceFallbackCount--;
2420 + if (forceFallbackCount === 0 && setSuspenseHandler != null) {
2421 + setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
2422 + }
2423 + }
2424 +
2425 + if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
2426 + fiberToFiberInstanceMap.delete(fiber);
2427 + }
2428 + const {alternate} = fiber;
2429 + if (alternate !== null) {
2430 + if (fiberToFiberInstanceMap.get(alternate) === fiberInstance) {
2431 + fiberToFiberInstanceMap.delete(alternate);
2432 + }
2433 + }
2434 +
2435 + untrackFiber(fiberInstance, fiber);
2436 }
2437
2438 // Running state of the remaining children from the previous version of this parent that
@@ -2670,7 +2747,21 @@ export function attach(
2747 }
2748
2749 if (fiber.tag === HostHoistable) {
2673 - aquireHostResource(fiber, fiber.memoizedState);
2750 + const nearestInstance = reconcilingParent;
2751 + if (nearestInstance === null) {
2752 + throw new Error('Did not expect a host hoistable to be the root');
2753 + }
2754 + aquireHostResource(nearestInstance, fiber.memoizedState);
2755 + } else if (
2756 + fiber.tag === HostComponent ||
2757 + fiber.tag === HostText ||
2758 + fiber.tag === HostSingleton
2759 + ) {
2760 + const nearestInstance = reconcilingParent;
2761 + if (nearestInstance === null) {
2762 + throw new Error('Did not expect a host hoistable to be the root');
2763 + }
2764 + aquireHostInstance(nearestInstance, fiber.stateNode);
2765 }
2766
2767 if (fiber.tag === SuspenseComponent) {
@@ -3291,8 +3382,12 @@ export function attach(
3382 }
3383 try {
3384 if (nextFiber.tag === HostHoistable) {
3294 - releaseHostResource(prevFiber, prevFiber.memoizedState);
3295 - aquireHostResource(nextFiber, nextFiber.memoizedState);
3385 + const nearestInstance = reconcilingParent;
3386 + if (nearestInstance === null) {
3387 + throw new Error('Did not expect a host hoistable to be the root');
3388 + }
3389 + releaseHostResource(nearestInstance, prevFiber.memoizedState);
3390 + aquireHostResource(nearestInstance, nextFiber.memoizedState);
3391 }
3392
3393 const isSuspense = nextFiber.tag === SuspenseComponent;
@@ -3780,82 +3875,21 @@ export function attach(
3875 }
3876 }
3877
3783 - function getNearestMountedHostInstance(
3784 - hostInstance: HostInstance,
3785 - ): null | HostInstance {
3786 - const mountedFiber = renderer.findFiberByHostInstance(hostInstance);
3787 - if (mountedFiber != null) {
3788 - if (mountedFiber.stateNode !== hostInstance) {
3789 - // If it's not a perfect match the specific one might be a resource.
3790 - // We don't need to look at any parents because host resources don't have
3791 - // children so it won't be in any parent if it's not this one.
3792 - if (hostResourceToFiberMap.has(hostInstance)) {
3793 - return hostInstance;
3794 - }
3795 - }
3796 - return mountedFiber.stateNode;
3797 - }
3798 - if (hostResourceToFiberMap.has(hostInstance)) {
3799 - return hostInstance;
3800 - }
3801 - return null;
3802 - }
3803 -
3804 - function findNearestUnfilteredElementID(searchFiber: Fiber) {
3805 - let fiber: null | Fiber = searchFiber;
3806 - while (fiber !== null) {
3807 - const fiberInstance = getFiberInstanceUnsafe(fiber);
3808 - if (fiberInstance !== null) {
3809 - // TODO: Ideally we would not have any filtered FiberInstances which
3810 - // would make this logic much simpler. Unfortunately, we sometimes
3811 - // eagerly add to the map and some times don't eagerly clean it up.
3812 - // TODO: If the fiber is filtered, the FiberInstance wouldn't really
3813 - // exist which would mean that we also don't have a way to get to the
3814 - // VirtualInstances.
3815 - if (!shouldFilterFiber(fiberInstance.data)) {
3816 - return fiberInstance.id;
3817 - }
3818 - // We couldn't use this Fiber but we might have a VirtualInstance
3819 - // that is the nearest unfiltered instance.
3820 - const parentInstance = fiberInstance.parent;
3821 - if (
3822 - parentInstance !== null &&
3823 - parentInstance.kind === VIRTUAL_INSTANCE
3824 - ) {
3825 - // Virtual Instances only exist if they're unfiltered.
3826 - return parentInstance.id;
3827 - }
3828 - // If we find a parent Fiber, it might not be the nearest parent
3829 - // so we break out and continue walking the Fiber tree instead.
3830 - }
3831 - fiber = fiber.return;
3878 + function getNearestMountedDOMNode(publicInstance: Element): null | Element {
3879 + let domNode: null | Element = publicInstance;
3880 + while (domNode && !publicInstanceToDevToolsInstanceMap.has(domNode)) {
3881 + // $FlowFixMe: In practice this is either null or Element.
3882 + domNode = domNode.parentNode;
3883 }
3833 - return null;
3884 + return domNode;
3885 }
3886
3887 function getElementIDForHostInstance(
3837 - hostInstance: HostInstance,
3838 - findNearestUnfilteredAncestor: boolean = false,
3888 + publicInstance: HostInstance,
3889 ): number | null {
3840 - const resourceFibers = hostResourceToFiberMap.get(hostInstance);
3841 - if (resourceFibers !== undefined) {
3842 - // This is a resource. Find the first unfiltered instance.
3843 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
3844 - for (const resourceFiber of resourceFibers) {
3845 - const elementID = findNearestUnfilteredElementID(resourceFiber);
3846 - if (elementID !== null) {
3847 - return elementID;
3848 - }
3849 - }
3850 - // If we don't find one, fallthrough to select the parent instead.
3851 - }
3852 - const fiber = renderer.findFiberByHostInstance(hostInstance);
3853 - if (fiber != null) {
3854 - if (!findNearestUnfilteredAncestor) {
3855 - // TODO: Remove this option. It's not used.
3856 - return getFiberIDThrows(fiber);
3857 - }
3858 - return findNearestUnfilteredElementID(fiber);
3890 + const instance = publicInstanceToDevToolsInstanceMap.get(publicInstance);
3891 + if (instance !== undefined) {
3892 + return instance.id;
3893 }
3894 return null;
3895 }
@@ -5788,7 +5822,7 @@ export function attach(
5822 flushInitialOperations,
5823 getBestMatchForTrackedPath,
5824 getDisplayNameForElementID,
5791 - getNearestMountedHostInstance,
5825 + getNearestMountedDOMNode,
5826 getElementIDForHostInstance,
5827 getInstanceAndStyle,
5828 getOwnersList,
packages/react-devtools-shared/src/backend/index.js
+6 -1
@@ -73,7 +73,12 @@ export function initBackend(
73
74 // Inject any not-yet-injected renderers (if we didn't reload-and-profile)
75 if (rendererInterface == null) {
76 - if (typeof renderer.findFiberByHostInstance === 'function') {
76 + if (
77 + // v16-19
78 + typeof renderer.findFiberByHostInstance === 'function' ||
79 + // v16.8+
80 + renderer.currentDispatcherRef != null
81 + ) {
82 // react-reconciler v16+
83 rendererInterface = attach(hook, id, renderer, global);
84 } else if (renderer.ComponentTree) {
packages/react-devtools-shared/src/backend/legacy/renderer.js
+5 -9
@@ -145,15 +145,13 @@ export function attach(
145 let getElementIDForHostInstance: GetElementIDForHostInstance =
146 ((null: any): GetElementIDForHostInstance);
147 let findHostInstanceForInternalID: (id: number) => ?HostInstance;
148 - let getNearestMountedHostInstance = (
149 - node: HostInstance,
150 - ): null | HostInstance => {
148 + let getNearestMountedDOMNode = (node: Element): null | Element => {
149 // Not implemented.
150 return null;
151 };
152
153 if (renderer.ComponentTree) {
156 - getElementIDForHostInstance = (node, findNearestUnfilteredAncestor) => {
154 + getElementIDForHostInstance = node => {
155 const internalInstance =
156 renderer.ComponentTree.getClosestInstanceFromNode(node);
157 return internalInstanceToIDMap.get(internalInstance) || null;
@@ -162,9 +160,7 @@ export function attach(
160 const internalInstance = idToInternalInstanceMap.get(id);
161 return renderer.ComponentTree.getNodeFromInstance(internalInstance);
162 };
165 - getNearestMountedHostInstance = (
166 - node: HostInstance,
167 - ): null | HostInstance => {
163 + getNearestMountedDOMNode = (node: Element): null | Element => {
164 const internalInstance =
165 renderer.ComponentTree.getClosestInstanceFromNode(node);
166 if (internalInstance != null) {
@@ -173,7 +169,7 @@ export function attach(
169 return null;
170 };
171 } else if (renderer.Mount.getID && renderer.Mount.getNode) {
176 - getElementIDForHostInstance = (node, findNearestUnfilteredAncestor) => {
172 + getElementIDForHostInstance = node => {
173 // Not implemented.
174 return null;
175 };
@@ -1126,7 +1122,7 @@ export function attach(
1122 flushInitialOperations,
1123 getBestMatchForTrackedPath,
1124 getDisplayNameForElementID,
1129 - getNearestMountedHostInstance,
1125 + getNearestMountedDOMNode,
1126 getElementIDForHostInstance,
1127 getInstanceAndStyle,
1128 findHostInstancesForElementID: (id: number) => {
packages/react-devtools-shared/src/backend/types.js
+3 -5
@@ -90,7 +90,6 @@ export type GetDisplayNameForElementID = (id: number) => string | null;
90
91 export type GetElementIDForHostInstance = (
92 component: HostInstance,
93 - findNearestUnfilteredAncestor?: boolean,
93 ) => number | null;
94 export type FindHostInstancesForElementID = (
95 id: number,
@@ -106,10 +105,11 @@ export type Lane = number;
105 export type Lanes = number;
106
107 export type ReactRenderer = {
109 - findFiberByHostInstance: (hostInstance: HostInstance) => Fiber | null,
108 version: string,
109 rendererPackageName: string,
110 bundleType: BundleType,
111 + // 16.0+ - To be removed in future versions.
112 + findFiberByHostInstance?: (hostInstance: HostInstance) => Fiber | null,
113 // 16.9+
114 overrideHookState?: ?(
115 fiber: Object,
@@ -358,9 +358,7 @@ export type RendererInterface = {
358 findHostInstancesForElementID: FindHostInstancesForElementID,
359 flushInitialOperations: () => void,
360 getBestMatchForTrackedPath: () => PathMatch | null,
361 - getNearestMountedHostInstance: (
362 - component: HostInstance,
363 - ) => HostInstance | null,
361 + getNearestMountedDOMNode: (component: Element) => Element | null,
362 getElementIDForHostInstance: GetElementIDForHostInstance,
363 getDisplayNameForElementID: GetDisplayNameForElementID,
364 getInstanceAndStyle(id: number): InstanceAndStyle,