diff --git a/src/backend/agent.js b/src/backend/agent.js
index c96b05b0aa..419bb9b760 100644
--- a/src/backend/agent.js
+++ b/src/backend/agent.js
@@ -75,6 +75,7 @@ export default class Agent extends EventEmitter {
bridge.addListener('getProfilingSummary', this.getProfilingSummary);
bridge.addListener('highlightElementInDOM', this.highlightElementInDOM);
bridge.addListener('inspectElement', this.inspectElement);
+ bridge.addListener('logElementToConsole', this.logElementToConsole);
bridge.addListener('overrideContext', this.overrideContext);
bridge.addListener('overrideHookState', this.overrideHookState);
bridge.addListener('overrideProps', this.overrideProps);
@@ -268,6 +269,15 @@ export default class Agent extends EventEmitter {
}
};
+ logElementToConsole = ({ id, rendererID }: InspectSelectParams) => {
+ const renderer = this._rendererInterfaces[rendererID];
+ if (renderer == null) {
+ console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
+ } else {
+ renderer.logElementToConsole(id);
+ }
+ };
+
reloadAndProfile = () => {
localStorage.setItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
diff --git a/src/backend/renderer.js b/src/backend/renderer.js
index 3a4cb9f1c9..0607b7c2ad 100644
--- a/src/backend/renderer.js
+++ b/src/backend/renderer.js
@@ -1410,7 +1410,7 @@ export function attach(
}
}
- function inspectElement(id: number): InspectedElement | null {
+ function inspectElementRaw(id: number): InspectedElement | null {
let fiber = idToFiberMap.get(id);
if (fiber == null) {
@@ -1491,7 +1491,7 @@ export function attach(
if (context !== null) {
// To simplify hydration and display logic for context, wrap in a value object.
// Otherwise simple values (e.g. strings, booleans) become harder to handle.
- context = cleanForBridge({ value: context });
+ context = { value: context };
}
let owners = null;
@@ -1530,16 +1530,16 @@ export function attach(
// Can view component source location.
canViewSource,
+ displayName: getDataForFiber(fiber).displayName,
+
// Inspectable properties.
// TODO Review sanitization approach for the below inspectable values.
context,
hooks: usesHooks
- ? cleanForBridge(
- inspectHooksOfFiber(fiber, (renderer.currentDispatcherRef: any))
- )
+ ? inspectHooksOfFiber(fiber, (renderer.currentDispatcherRef: any))
: null,
- props: cleanForBridge(memoizedProps),
- state: usesHooks ? null : cleanForBridge(memoizedState),
+ props: memoizedProps,
+ state: usesHooks ? null : memoizedState,
// List of owners
owners,
@@ -1549,6 +1549,56 @@ export function attach(
};
}
+ function inspectElement(id: number): InspectedElement | null {
+ let result = inspectElementRaw(id);
+ if (result === null) {
+ return null;
+ }
+ // TODO Review sanitization approach for the below inspectable values.
+ result.context = cleanForBridge(result.context);
+ result.hooks = cleanForBridge(result.hooks);
+ result.props = cleanForBridge(result.props);
+ result.state = cleanForBridge(result.state);
+ return result;
+ }
+
+ function logElementToConsole(id) {
+ const result = inspectElementRaw(id);
+ if (result === null) {
+ console.warn(`Could not find Fiber with id "${id}"`);
+ return;
+ }
+
+ const supportsGroup = typeof console.groupCollapsed === 'function';
+ const label =
+ '[Click to expand] <' + (result.displayName || 'Component') + ' />';
+
+ if (supportsGroup) {
+ console.groupCollapsed(label);
+ }
+ if (result.props !== null) {
+ console.log('Props:', result.props);
+ }
+ if (result.state !== null) {
+ console.log('State:', result.state);
+ }
+ if (result.hooks !== null) {
+ console.log('Hooks:', result.hooks);
+ }
+ const nativeNode = findNativeByFiberID(id);
+ if (nativeNode !== null) {
+ console.log('Node:', nativeNode);
+ }
+ if (window.chrome || /firefox/i.test(navigator.userAgent)) {
+ console.log(
+ 'Right-click any value to save it as a global variable for further inspection.'
+ );
+ }
+ if (supportsGroup) {
+ console.groupEnd();
+ }
+ }
+
function setInHook(
id: number,
index: number,
@@ -1859,6 +1909,7 @@ export function attach(
handleCommitFiberRoot,
handleCommitFiberUnmount,
inspectElement,
+ logElementToConsole,
prepareViewElementSource,
overrideSuspense,
renderer,
diff --git a/src/backend/types.js b/src/backend/types.js
index 620119d790..bd4562eef7 100644
--- a/src/backend/types.js
+++ b/src/backend/types.js
@@ -106,6 +106,7 @@ export type RendererInterface = {
handleCommitFiberRoot: (fiber: Object) => void,
handleCommitFiberUnmount: (fiber: Object) => void,
inspectElement: (id: number) => InspectedElement | null,
+ logElementToConsole: (id: number) => void,
overrideSuspense: (id: number, forceFallback: boolean) => void,
prepareViewElementSource: (id: number) => void,
renderer: ReactRenderer | null,
diff --git a/src/devtools/views/ButtonIcon.js b/src/devtools/views/ButtonIcon.js
index 82a48d6415..caf53a7549 100644
--- a/src/devtools/views/ButtonIcon.js
+++ b/src/devtools/views/ButtonIcon.js
@@ -12,6 +12,7 @@ export type IconType =
| 'export'
| 'filter'
| 'import'
+ | 'log-data'
| 'more'
| 'next'
| 'previous'
@@ -54,6 +55,9 @@ export default function ButtonIcon({ type }: Props) {
case 'import':
pathData = PATH_IMPORT;
break;
+ case 'log-data':
+ pathData = PATH_EXPORT; // TODO: real icon
+ break;
case 'more':
pathData = PATH_MORE;
break;
diff --git a/src/devtools/views/Components/SelectedElement.js b/src/devtools/views/Components/SelectedElement.js
index bba8d803da..d17e756161 100644
--- a/src/devtools/views/Components/SelectedElement.js
+++ b/src/devtools/views/Components/SelectedElement.js
@@ -39,14 +39,14 @@ export default function SelectedElement(_: Props) {
const inspectedElement = useInspectedElement(selectedElementID);
const highlightElement = useCallback(() => {
- if (element !== null && selectedElementID !== null) {
- const rendererID =
- store.getRendererIDForElement(selectedElementID) || null;
+ const id = selectedElementID;
+ if (element !== null && id !== null) {
+ const rendererID = store.getRendererIDForElement(id);
if (rendererID !== null) {
bridge.send('highlightElementInDOM', {
displayName: element.displayName,
hideAfterTimeout: true,
- id: selectedElementID,
+ id,
rendererID,
scrollIntoView: true,
});
@@ -54,6 +54,19 @@ export default function SelectedElement(_: Props) {
}
}, [bridge, element, selectedElementID, store]);
+ const logElement = useCallback(() => {
+ const id = selectedElementID;
+ if (id !== null) {
+ const rendererID = store.getRendererIDForElement(id);
+ if (rendererID !== null) {
+ bridge.send('logElementToConsole', {
+ id,
+ rendererID,
+ });
+ }
+ }
+ }, [bridge, selectedElementID, store]);
+
const viewSource = useCallback(() => {
if (viewElementSource != null && selectedElementID !== null) {
viewElementSource(selectedElementID);
@@ -89,6 +102,13 @@ export default function SelectedElement(_: Props) {
>
+