From ed94ea146a111124711910c87af44d7760bfd409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 2 Aug 2024 17:04:27 -0400 Subject: [PATCH] [DevTools] Allow Highlighting/Inspect HostSingletons/Hoistables and Resources (#30584) Basically the new Float types needs to be supported. Resources are a bit special because they're a DOM specific type but we can expect any other implementation using resources to provide and instance on this field if needed. There's a slightly related case for the reverse lookup. You can already select a singleton or hoistable (that's not a resource) in the browser elements panel and it'll select the corresponding node in the RDT Components panel. That works because it uses the same mechanism as event dispatching and those need to be able to receive events. However, you can't select a resource. Because that's conceptually one to many. We could in principle just search the tree for the first one or keep a map of currently mounted resources and just pick the first fiber that created it. So that you can select a resource and see what created it. Particularly useful when there's only one Fiber which is most of the time. --------- Co-authored-by: Ruslan Lesiutin --- .../src/backend/fiber/renderer.js | 51 ++++++++++++++----- .../src/backend/types.js | 2 +- .../backend/views/Highlighter/Highlighter.js | 16 ++++-- .../src/backend/views/Highlighter/Overlay.js | 2 +- .../src/backend/views/Highlighter/index.js | 4 +- 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 75cf2c7c03755..50ee8e74dabf7 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -2695,11 +2695,11 @@ export function attach( // If we're tracing updates and we've bailed out before reaching a host node, // we should fall back to recursively marking the nearest host descendants for highlight. if (traceNearestHostComponentUpdate) { - const hostFibers = findAllCurrentHostFibers( + const hostInstances = findAllCurrentHostInstances( getFiberInstanceThrows(nextFiber), ); - hostFibers.forEach(hostFiber => { - traceUpdatesForNodes.add(hostFiber.stateNode); + hostInstances.forEach(hostInstance => { + traceUpdatesForNodes.add(hostInstance); }); } } @@ -2943,31 +2943,54 @@ export function attach( currentRootID = -1; } - function findAllCurrentHostFibers( + function getResourceInstance(fiber: Fiber): HostInstance | null { + if (fiber.tag === HostHoistable) { + const resource = fiber.memoizedState; + // Feature Detect a DOM Specific Instance of a Resource + if ( + typeof resource === 'object' && + resource !== null && + resource.instance != null + ) { + return resource.instance; + } + } + return null; + } + + function findAllCurrentHostInstances( fiberInstance: FiberInstance, - ): $ReadOnlyArray { - const fibers = []; + ): $ReadOnlyArray { + const hostInstances = []; const fiber = findCurrentFiberUsingSlowPathByFiberInstance(fiberInstance); if (!fiber) { - return fibers; + return hostInstances; } // Next we'll drill down this component to find all HostComponent/Text. let node: Fiber = fiber; while (true) { - if (node.tag === HostComponent || node.tag === HostText) { - fibers.push(node); + if ( + node.tag === HostComponent || + node.tag === HostText || + node.tag === HostSingleton || + node.tag === HostHoistable + ) { + const hostInstance = node.stateNode || getResourceInstance(node); + if (hostInstance) { + hostInstances.push(hostInstance); + } } else if (node.child) { node.child.return = node; node = node.child; continue; } if (node === fiber) { - return fibers; + return hostInstances; } while (!node.sibling) { if (!node.return || node.return === fiber) { - return fibers; + return hostInstances; } node = node.return; } @@ -2976,7 +2999,7 @@ export function attach( } // Flow needs the return here, but ESLint complains about it. // eslint-disable-next-line no-unreachable - return fibers; + return hostInstances; } function findHostInstancesForElementID(id: number) { @@ -2996,8 +3019,8 @@ export function attach( return null; } - const hostFibers = findAllCurrentHostFibers(devtoolsInstance); - return hostFibers.map(hostFiber => hostFiber.stateNode).filter(Boolean); + const hostInstances = findAllCurrentHostInstances(devtoolsInstance); + return hostInstances; } catch (err) { // The fiber might have unmounted by now. return null; diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index fdcfddea5fb6b..2bd13a3a1294d 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -94,7 +94,7 @@ export type GetElementIDForHostInstance = ( ) => number | null; export type FindHostInstancesForElementID = ( id: number, -) => ?Array; +) => null | $ReadOnlyArray; export type ReactProviderType = { $$typeof: symbol | number, diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js b/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js index 6ed083abe4c1b..ddcb6f1ef11a9 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js @@ -38,12 +38,15 @@ export function hideOverlay(agent: Agent): void { : hideOverlayWeb(); } -function showOverlayNative(elements: Array, agent: Agent): void { +function showOverlayNative( + elements: $ReadOnlyArray, + agent: Agent, +): void { agent.emit('showNativeHighlight', elements); } function showOverlayWeb( - elements: Array, + elements: $ReadOnlyArray, componentName: string | null, agent: Agent, hideAfterTimeout: boolean, @@ -64,12 +67,17 @@ function showOverlayWeb( } export function showOverlay( - elements: Array, + elements: $ReadOnlyArray, componentName: string | null, agent: Agent, hideAfterTimeout: boolean, ): void { return isReactNativeEnvironment() ? showOverlayNative(elements, agent) - : showOverlayWeb((elements: any), componentName, agent, hideAfterTimeout); + : showOverlayWeb( + (elements: $ReadOnlyArray), + componentName, + agent, + hideAfterTimeout, + ); } diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js b/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js index fe0a40d8e9c2d..cdaf64ed8c7a3 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js @@ -187,7 +187,7 @@ export default class Overlay { } } - inspect(nodes: Array, name?: ?string) { + inspect(nodes: $ReadOnlyArray, name?: ?string) { // We can't get the size of text nodes or comment nodes. React as of v15 // heavily uses comment nodes to delimit text. const elements = nodes.filter(node => node.nodeType === Node.ELEMENT_NODE); diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js index dc711d7881bd4..7fefa837e2fc5 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js @@ -13,7 +13,6 @@ import Agent from 'react-devtools-shared/src/backend/agent'; import {hideOverlay, showOverlay} from './Highlighter'; import type {BackendBridge} from 'react-devtools-shared/src/bridge'; -import type {HostInstance} from '../../types'; // This plug-in provides in-page highlighting of the selected element. // It is used by the browser extension and the standalone DevTools shell (when connected to a browser). @@ -113,8 +112,7 @@ export default function setupHighlighter( return; } - const nodes: ?Array = - renderer.findHostInstancesForElementID(id); + const nodes = renderer.findHostInstancesForElementID(id); if (nodes != null && nodes[0] != null) { const node = nodes[0];