Skip to content

Commit

Permalink
Refactored context menu to support dynamic options
Browse files Browse the repository at this point in the history
Used this mechanism to add a conditional menu option for inspecting the current value (if it's a function)
  • Loading branch information
Brian Vaughn committed Dec 18, 2019
1 parent be9046a commit 7589f9e
Show file tree
Hide file tree
Showing 18 changed files with 250 additions and 111 deletions.
62 changes: 52 additions & 10 deletions packages/react-devtools-extensions/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import {createElement} from 'react';
import {createRoot, flushSync} from 'react-dom';
import Bridge from 'react-devtools-shared/src/bridge';
import Store from 'react-devtools-shared/src/devtools/store';
import {
createViewElementSource,
getBrowserName,
getBrowserTheme,
} from './utils';
import {getBrowserName, getBrowserTheme} from './utils';
import {LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY} from 'react-devtools-shared/src/constants';
import {
getSavedComponentFilters,
Expand Down Expand Up @@ -155,10 +151,54 @@ function createPanelIfReactLoaded() {
},
);

const viewElementSourceFunction = createViewElementSource(
bridge,
store,
);
const viewAttributeSourceFunction = (id, path) => {
const rendererID = store.getRendererIDForElement(id);
if (rendererID != null) {
// Ask the renderer interface to find the specified attribute,
// and store it as a global variable on the window.
bridge.send('viewAttributeSource', {id, path, rendererID});

setTimeout(() => {
// Ask Chrome to display the location of the attribute,
// assuming the renderer found a match.
chrome.devtools.inspectedWindow.eval(`
if (window.$attribute != null) {
inspect(window.$attribute);
}
`);
}, 100);
}
};

const viewElementSourceFunction = id => {
const rendererID = store.getRendererIDForElement(id);
if (rendererID != null) {
// Ask the renderer interface to determine the component function,
// and store it as a global variable on the window
bridge.send('viewElementSource', {id, rendererID});

setTimeout(() => {
// Ask Chrome to display the location of the component function,
// or a render method if it is a Class (ideally Class instance, not type)
// assuming the renderer found one.
chrome.devtools.inspectedWindow.eval(`
if (window.$type != null) {
if (
window.$type &&
window.$type.prototype &&
window.$type.prototype.isReactComponent
) {
// inspect Component.render, not constructor
inspect(window.$type.prototype.render);
} else {
// inspect Functional Component
inspect(window.$type);
}
}
`);
}, 100);
}
};

root = createRoot(document.createElement('div'));

Expand All @@ -170,11 +210,13 @@ function createPanelIfReactLoaded() {
bridge,
browserTheme: getBrowserTheme(),
componentsPortalContainer,
enabledInspectedElementContextMenu: true,
overrideTab,
profilerPortalContainer,
showTabBar: false,
warnIfUnsupportedVersionDetected: true,
store,
warnIfUnsupportedVersionDetected: true,
viewAttributeSourceFunction,
viewElementSourceFunction,
}),
);
Expand Down
32 changes: 0 additions & 32 deletions packages/react-devtools-extensions/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,6 @@

const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0;

export function createViewElementSource(bridge: Bridge, store: Store) {
return function viewElementSource(id) {
const rendererID = store.getRendererIDForElement(id);
if (rendererID != null) {
// Ask the renderer interface to determine the component function,
// and store it as a global variable on the window
bridge.send('viewElementSource', {id, rendererID});

setTimeout(() => {
// Ask Chrome to display the location of the component function,
// or a render method if it is a Class (ideally Class instance, not type)
// assuming the renderer found one.
chrome.devtools.inspectedWindow.eval(`
if (window.$type != null) {
if (
window.$type &&
window.$type.prototype &&
window.$type.prototype.isReactComponent
) {
// inspect Component.render, not constructor
inspect(window.$type.prototype.render);
} else {
// inspect Functional Component
inspect(window.$type);
}
}
`);
}, 100);
}
};
}

export type BrowserName = 'Chrome' | 'Firefox';

export function getBrowserName(): BrowserName {
Expand Down
10 changes: 10 additions & 0 deletions packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export default class Agent extends EventEmitter<{|
this.updateAppendComponentStack,
);
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
bridge.addListener('viewAttributeSource', this.viewAttributeSource);
bridge.addListener('viewElementSource', this.viewElementSource);

if (this._isProfiling) {
Expand Down Expand Up @@ -463,6 +464,15 @@ export default class Agent extends EventEmitter<{|
}
};

viewAttributeSource = ({id, path, rendererID}: CopyElementParams) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.prepareViewAttributeSource(id, path);
}
};

viewElementSource = ({id, rendererID}: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
Expand Down
11 changes: 11 additions & 0 deletions packages/react-devtools-shared/src/backend/legacy/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,16 @@ export function attach(
}
}

function prepareViewAttributeSource(
id: number,
path: Array<string | number>,
): void {
const inspectedElement = inspectElementRaw(id);
if (inspectedElement !== null) {
window.$attribute = getInObject(inspectedElement, path);
}
}

function prepareViewElementSource(id: number): void {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance == null) {
Expand Down Expand Up @@ -968,6 +978,7 @@ export function attach(
inspectElement,
logElementToConsole,
overrideSuspense,
prepareViewAttributeSource,
prepareViewElementSource,
renderer,
setInContext,
Expand Down
14 changes: 14 additions & 0 deletions packages/react-devtools-shared/src/backend/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,19 @@ export function attach(
}
// END copied code

function prepareViewAttributeSource(
id: number,
path: Array<string | number>,
): void {
const isCurrent = isMostRecentlyInspectedElementCurrent(id);
if (isCurrent) {
window.$attribute = getInObject(
((mostRecentlyInspectedElement: any): InspectedElement),
path,
);
}
}

function prepareViewElementSource(id: number): void {
let fiber = idToFiberMap.get(id);
if (fiber == null) {
Expand Down Expand Up @@ -3176,6 +3189,7 @@ export function attach(
handleCommitFiberUnmount,
inspectElement,
logElementToConsole,
prepareViewAttributeSource,
prepareViewElementSource,
overrideSuspense,
renderer,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ export type RendererInterface = {
) => InspectedElementPayload,
logElementToConsole: (id: number) => void,
overrideSuspense: (id: number, forceFallback: boolean) => void,
prepareViewAttributeSource: (
id: number,
path: Array<string | number>,
) => void,
prepareViewElementSource: (id: number) => void,
renderer: ReactRenderer | null,
setInContext: (id: number, path: Array<string | number>, value: any) => void,
Expand Down
6 changes: 6 additions & 0 deletions packages/react-devtools-shared/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type CopyElementPathParams = {|
path: Array<string | number>,
|};

type ViewAttributeSourceParams = {|
...ElementAndRendererID,
path: Array<string | number>,
|};

type InspectElementParams = {|
...ElementAndRendererID,
path?: Array<string | number>,
Expand Down Expand Up @@ -130,6 +135,7 @@ type FrontendEvents = {|
storeAsGlobal: [StoreAsGlobalParams],
updateAppendComponentStack: [boolean],
updateComponentFilters: [Array<ComponentFilter>],
viewAttributeSource: [ViewAttributeSourceParams],
viewElementSource: [ElementAndRendererID],

// React Native style editor plug-in.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, {
useState,
} from 'react';
import {createPortal} from 'react-dom';
import {DataContext, RegistryContext} from './Contexts';
import {RegistryContext} from './Contexts';

import styles from './ContextMenu.css';

Expand Down Expand Up @@ -120,9 +120,7 @@ export default function ContextMenu({children, id}: Props) {
} else {
return createPortal(
<div ref={menuRef} className={styles.ContextMenu}>
<DataContext.Provider value={state.data}>
{children}
</DataContext.Provider>
{children(state.data)}
</div>,
containerRef.current,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useContext} from 'react';
import {DataContext, RegistryContext} from './Contexts';
import {RegistryContext} from './Contexts';

import styles from './ContextMenuItem.css';

Expand All @@ -10,11 +10,10 @@ type Props = {|
|};

export default function ContextMenuItem({children, onClick, title}: Props) {
const data = useContext(DataContext);
const {hideMenu} = useContext(RegistryContext);

const handleClick = event => {
onClick(data);
onClick();
hideMenu();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,3 @@ export const RegistryContext = createContext({
showMenu,
registerMenu,
});

export const DataContext = createContext(null);
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ function HookView({canEditHooks, hook, id, inspectPath, path}: HookViewProps) {
const contextMenuTriggerRef = useRef(null);
useContextMenu({
data: ['hooks', ...path],
data: {
path: ['hooks', ...path],
type:
hook !== null &&
typeof hook === 'object' &&
hook.hasOwnProperty(meta.type)
? hook[meta.type]
: typeof value,
},
id: 'SelectedElement',
ref: contextMenuTriggerRef,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,15 @@ export default function KeyValue({
const toggleIsOpen = () => setIsOpen(prevIsOpen => !prevIsOpen);

useContextMenu({
data: [pathRoot, ...path],
data: {
path: [pathRoot, ...path],
type:
value !== null &&
typeof value === 'object' &&
value.hasOwnProperty(meta.type)
? value[meta.type]
: typeof value,
},
id: 'SelectedElement',
ref: contextMenuTriggerRef,
});
Expand Down
Loading

0 comments on commit 7589f9e

Please sign in to comment.