feat[devtools]: display native tag for host components for Native (#32762)

Native only. Displays the native tag for Native Host components inside a
badge, when user inspects the component.

Only displaying will be supported for now, because in order to get
native tags indexable, they should be part of the bridge operations,
which is technically a breaking change that requires significantly more
time investment.

The text will only be shown when user hovers over the badge.
![Screenshot 2025-03-26 at 19 46
40](https://github.com/user-attachments/assets/787530cf-c5e5-4b85-8e2a-15b006a3d783)
This commit is contained in:
Ruslan Lesiutin 2025-04-02 22:44:38 +01:00 committed by GitHub
parent b2f6365745
commit f0c767e2a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 97 additions and 3 deletions

View File

@ -808,6 +808,27 @@ function getPublicInstance(instance: HostInstance): HostInstance {
return instance;
}
function getNativeTag(instance: HostInstance): number | null {
if (typeof instance !== 'object' || instance === null) {
return null;
}
// Modern. Fabric.
if (
instance.canonical != null &&
typeof instance.canonical.nativeTag === 'number'
) {
return instance.canonical.nativeTag;
}
// Legacy. Paper.
if (typeof instance._nativeTag === 'number') {
return instance._nativeTag;
}
return null;
}
function aquireHostInstance(
nearestInstance: DevToolsInstance,
hostInstance: HostInstance,
@ -4298,6 +4319,11 @@ export function attach(
componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);
}
let nativeTag = null;
if (elementType === ElementTypeHostComponent) {
nativeTag = getNativeTag(fiber.stateNode);
}
return {
id: fiberInstance.id,
@ -4364,6 +4390,8 @@ export function attach(
rendererVersion: renderer.version,
plugins,
nativeTag,
};
}
@ -4457,6 +4485,8 @@ export function attach(
rendererVersion: renderer.version,
plugins,
nativeTag: null,
};
}

View File

@ -859,6 +859,8 @@ export function attach(
plugins: {
stylex: null,
},
nativeTag: null,
};
}

View File

@ -294,6 +294,9 @@ export type InspectedElement = {
// UI plugins/visualizations for the inspected element.
plugins: Plugins,
// React Native only.
nativeTag: number | null,
};
export const InspectElementErrorType = 'error';

View File

@ -239,6 +239,7 @@ export function convertInspectedElementBackendToFrontend(
key,
errors,
warnings,
nativeTag,
} = inspectedElementBackend;
const inspectedElement: InspectedElementFrontend = {
@ -273,6 +274,7 @@ export function convertInspectedElementBackendToFrontend(
state: hydrateHelper(state),
errors,
warnings,
nativeTag,
};
return inspectedElement;

View File

@ -11,21 +11,25 @@ import * as React from 'react';
import Badge from './Badge';
import ForgetBadge from './ForgetBadge';
import NativeTagBadge from './NativeTagBadge';
import styles from './InspectedElementBadges.css';
type Props = {
hocDisplayNames: null | Array<string>,
compiledWithForget: boolean,
nativeTag: number | null,
};
export default function InspectedElementBadges({
hocDisplayNames,
compiledWithForget,
nativeTag,
}: Props): React.Node {
if (
!compiledWithForget &&
(hocDisplayNames == null || hocDisplayNames.length === 0)
(hocDisplayNames == null || hocDisplayNames.length === 0) &&
nativeTag === null
) {
return null;
}
@ -33,6 +37,7 @@ export default function InspectedElementBadges({
return (
<div className={styles.Root}>
{compiledWithForget && <ForgetBadge indexable={false} />}
{nativeTag !== null && <NativeTagBadge nativeTag={nativeTag} />}
{hocDisplayNames !== null &&
hocDisplayNames.map(hocDisplayName => (

View File

@ -54,8 +54,14 @@ export default function InspectedElementView({
toggleParseHookNames,
symbolicatedSourcePromise,
}: Props): React.Node {
const {owners, rendererPackageName, rendererVersion, rootType, source} =
inspectedElement;
const {
owners,
rendererPackageName,
rendererVersion,
rootType,
source,
nativeTag,
} = inspectedElement;
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
@ -75,6 +81,7 @@ export default function InspectedElementView({
<InspectedElementBadges
hocDisplayNames={element.hocDisplayNames}
compiledWithForget={element.compiledWithForget}
nativeTag={nativeTag}
/>
</div>

View File

@ -0,0 +1,11 @@
.Toggle {
display: flex;
}
.Toggle > span { /* targets .ToggleContent */
padding: 0;
}
.Badge {
cursor: help;
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import Badge from './Badge';
import Toggle from '../Toggle';
import styles from './NativeTagBadge.css';
type Props = {
nativeTag: number,
};
const noop = () => {};
const title =
'Unique identifier for the corresponding native component. React Native only.';
export default function NativeTagBadge({nativeTag}: Props): React.Node {
return (
<Toggle onChange={noop} className={styles.Toggle} title={title}>
<Badge className={styles.Badge}>Tag {nativeTag}</Badge>
</Toggle>
);
}

View File

@ -259,6 +259,9 @@ export type InspectedElement = {
// UI plugins/visualizations for the inspected element.
plugins: Plugins,
// React Native only.
nativeTag: number | null,
};
// TODO: Add profiling type