From b176cd44144393439817b0026bd13302139e5550 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 8 Apr 2019 16:20:17 +0100 Subject: [PATCH 1/2] Refactor: split inspectElementRaw --- src/backend/renderer.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/backend/renderer.js b/src/backend/renderer.js index 3a4cb9f1c9..3529612f31 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; @@ -1534,12 +1534,10 @@ export function attach( // 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 +1547,19 @@ 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 setInHook( id: number, index: number, From 5b120d6b24e29972ec8bc8b00638051c55f08a43 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 8 Apr 2019 18:37:00 +0100 Subject: [PATCH 2/2] Add a separate button for logging values --- src/backend/agent.js | 10 +++++ src/backend/renderer.js | 40 +++++++++++++++++++ src/backend/types.js | 1 + src/devtools/views/ButtonIcon.js | 4 ++ .../views/Components/SelectedElement.js | 28 +++++++++++-- src/devtools/views/Components/types.js | 2 + 6 files changed, 81 insertions(+), 4 deletions(-) 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 3529612f31..0607b7c2ad 100644 --- a/src/backend/renderer.js +++ b/src/backend/renderer.js @@ -1530,6 +1530,8 @@ 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, @@ -1560,6 +1562,43 @@ export function attach( 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, @@ -1870,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) { > +