diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index 369dc43a242..f8286783a9b 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -26,7 +26,7 @@ import { import {localStorageSetItem} from 'react-devtools-shared/src/storage'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; -import type {ReactFunctionLocation} from 'shared/ReactTypes'; +import type {ReactFunctionLocation, ReactCallSite} from 'shared/ReactTypes'; export type StatusTypes = 'server-connected' | 'devtools-connected' | 'error'; export type StatusListener = (message: string, status: StatusTypes) => void; @@ -144,8 +144,8 @@ async function fetchFileWithCaching(url: string) { } function canViewElementSourceFunction( - _source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + _source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ): boolean { if (symbolicatedSource == null) { return false; @@ -156,8 +156,8 @@ function canViewElementSourceFunction( } function viewElementSourceFunction( - _source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + _source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ): void { if (symbolicatedSource == null) { return; diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index d7756ca991b..5bb94ea1ccd 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -326,7 +326,7 @@ function createSourcesEditorPanel() { editorPane = createdPane; createdPane.setPage('panel.html'); - createdPane.setHeight('42px'); + createdPane.setHeight('75px'); createdPane.onShown.addListener(portal => { editorPortalContainer = portal.container; diff --git a/packages/react-devtools-fusebox/src/frontend.d.ts b/packages/react-devtools-fusebox/src/frontend.d.ts index 5a06aec7e49..a1142178f47 100644 --- a/packages/react-devtools-fusebox/src/frontend.d.ts +++ b/packages/react-devtools-fusebox/src/frontend.d.ts @@ -34,17 +34,26 @@ export type ReactFunctionLocation = [ number, // enclosing line number number, // enclosing column number ]; +export type ReactCallSite = [ + string, // function name + string, // file name TODO: model nested eval locations as nested arrays + number, // line number + number, // column number + number, // enclosing line number + number, // enclosing column number + boolean, // async resume +]; export type ViewElementSource = ( - source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ) => void; export type ViewAttributeSource = ( id: number, path: Array, ) => void; export type CanViewElementSource = ( - source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ) => boolean; export type InitializationOptions = { diff --git a/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js index 870ad35e4b0..55029252843 100644 --- a/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js +++ b/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js @@ -15,8 +15,7 @@ export function test(maybeInspectedElement) { hasOwnProperty('canEditFunctionProps') && hasOwnProperty('canEditHooks') && hasOwnProperty('canToggleSuspense') && - hasOwnProperty('canToggleError') && - hasOwnProperty('canViewSource') + hasOwnProperty('canToggleError') ); } diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 937fe8d352f..96c7a38863b 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -4374,8 +4374,6 @@ export function attach( (fiber.alternate !== null && forceFallbackForFibers.has(fiber.alternate))), - // Can view component source location. - canViewSource, source, // Does the component have legacy context attached to it. @@ -4416,7 +4414,6 @@ export function attach( function inspectVirtualInstanceRaw( virtualInstance: VirtualInstance, ): InspectedElement | null { - const canViewSource = true; const source = getSourceForInstance(virtualInstance); const componentInfo = virtualInstance.data; @@ -4470,8 +4467,6 @@ export function attach( canToggleSuspense: supportsTogglingSuspense && hasSuspenseBoundary, - // Can view component source location. - canViewSource, source, // Does the component have legacy context attached to it. diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index cbcc37e319e..cc097c83790 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -830,8 +830,6 @@ export function attach( // Suspense did not exist in legacy versions canToggleSuspense: false, - // Can view component source location. - canViewSource: type === ElementTypeClass || type === ElementTypeFunction, source: null, // Only legacy context exists in legacy versions. diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index c8629885097..c9d6284b2f4 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -264,9 +264,6 @@ export type InspectedElement = { // Is this Suspense, and can its value be overridden now? canToggleSuspense: boolean, - // Can view component source location. - canViewSource: boolean, - // Does the component have legacy context attached to it. hasLegacyContext: boolean, diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index bbb171bce0d..20b4e99a101 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -222,7 +222,6 @@ export function convertInspectedElementBackendToFrontend( canToggleError, isErrored, canToggleSuspense, - canViewSource, hasLegacyContext, id, type, @@ -252,7 +251,6 @@ export function convertInspectedElementBackendToFrontend( canToggleError, isErrored, canToggleSuspense, - canViewSource, hasLegacyContext, id, key, diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js index b0873816590..fa32ead1e93 100644 --- a/packages/react-devtools-shared/src/constants.js +++ b/packages/react-devtools-shared/src/constants.js @@ -37,6 +37,8 @@ export const LOCAL_STORAGE_OPEN_IN_EDITOR_URL = 'React::DevTools::openInEditorUrl'; export const LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET = 'React::DevTools::openInEditorUrlPreset'; +export const LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR = + 'React::DevTools::alwaysOpenInEditor'; export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY = 'React::DevTools::parseHookNames'; export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY = diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index 8210e12331b..0c980f0db48 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -18,8 +18,11 @@ import Toggle from '../Toggle'; import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types'; import InspectedElementView from './InspectedElementView'; import {InspectedElementContext} from './InspectedElementContext'; -import {getOpenInEditorURL} from '../../../utils'; -import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants'; +import {getOpenInEditorURL, getAlwaysOpenInEditor} from '../../../utils'; +import { + LOCAL_STORAGE_OPEN_IN_EDITOR_URL, + LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, +} from '../../../constants'; import FetchFileWithCachingContext from './FetchFileWithCachingContext'; import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource'; import OpenInEditorButton from './OpenInEditorButton'; @@ -118,18 +121,26 @@ export default function InspectedElementWrapper(_: Props): React.Node { inspectedElement != null && inspectedElement.canToggleSuspense; - const editorURL = useSyncExternalStore( - function subscribe(callback) { - window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + const alwaysOpenInEditor = useSyncExternalStore( + useCallback(function subscribe(callback) { + window.addEventListener(LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, callback); return function unsubscribe() { - window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + window.removeEventListener( + LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, + callback, + ); }; - }, - function getState() { - return getOpenInEditorURL(); - }, + }, []), + getAlwaysOpenInEditor, ); + const editorURL = useSyncExternalStore(function subscribe(callback) { + window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + return function unsubscribe() { + window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + }; + }, getOpenInEditorURL); + const toggleErrored = useCallback(() => { if (inspectedElement == null) { return; @@ -217,7 +228,8 @@ export default function InspectedElementWrapper(_: Props): React.Node { - {!!editorURL && + {!alwaysOpenInEditor && + !!editorURL && inspectedElement != null && inspectedElement.source != null && symbolicatedSourcePromise != null && ( @@ -271,8 +283,7 @@ export default function InspectedElementWrapper(_: Props): React.Node { {!hideViewSourceAction && ( )} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js index 91780cdc13d..585361cedcf 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js @@ -8,7 +8,6 @@ */ import * as React from 'react'; -import {useCallback, useContext} from 'react'; import {copy} from 'clipboard-js'; import {toNormalUrl} from 'jsc-safe-url'; @@ -17,7 +16,7 @@ import ButtonIcon from '../ButtonIcon'; import Skeleton from './Skeleton'; import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck'; -import ViewElementSourceContext from './ViewElementSourceContext'; +import useOpenResource from '../useOpenResource'; import type {ReactFunctionLocation} from 'shared/ReactTypes'; import styles from './InspectedElementSourcePanel.css'; @@ -91,24 +90,11 @@ function CopySourceButton({source, symbolicatedSourcePromise}: Props) { function FormattedSourceString({source, symbolicatedSourcePromise}: Props) { const symbolicatedSource = React.use(symbolicatedSourcePromise); - const {canViewElementSourceFunction, viewElementSourceFunction} = useContext( - ViewElementSourceContext, + const [linkIsEnabled, viewSource] = useOpenResource( + source, + symbolicatedSource, ); - // In some cases (e.g. FB internal usage) the standalone shell might not be able to view the source. - // To detect this case, we defer to an injected helper function (if present). - const linkIsEnabled = - viewElementSourceFunction != null && - source != null && - (canViewElementSourceFunction == null || - canViewElementSourceFunction(source, symbolicatedSource)); - - const viewSource = useCallback(() => { - if (viewElementSourceFunction != null && source != null) { - viewElementSourceFunction(source, symbolicatedSource); - } - }, [source, symbolicatedSource]); - const [, sourceURL, line] = symbolicatedSource == null ? source : symbolicatedSource; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js index 6043bb61df9..23d4cf96c82 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js @@ -11,79 +11,48 @@ import * as React from 'react'; import ButtonIcon from '../ButtonIcon'; import Button from '../Button'; -import ViewElementSourceContext from './ViewElementSourceContext'; import Skeleton from './Skeleton'; import type {ReactFunctionLocation} from 'shared/ReactTypes'; -import type { - CanViewElementSource, - ViewElementSource, -} from 'react-devtools-shared/src/devtools/views/DevTools'; -const {useCallback, useContext} = React; +import useOpenResource from '../useOpenResource'; type Props = { - canViewSource: ?boolean, - source: ?ReactFunctionLocation, + source: null | ReactFunctionLocation, symbolicatedSourcePromise: Promise | null, }; function InspectedElementViewSourceButton({ - canViewSource, source, symbolicatedSourcePromise, }: Props): React.Node { - const {canViewElementSourceFunction, viewElementSourceFunction} = useContext( - ViewElementSourceContext, - ); - return ( }> ); } type ActualSourceButtonProps = { - canViewSource: ?boolean, - source: ?ReactFunctionLocation, + source: null | ReactFunctionLocation, symbolicatedSourcePromise: Promise | null, - canViewElementSourceFunction: CanViewElementSource | null, - viewElementSourceFunction: ViewElementSource | null, }; function ActualSourceButton({ - canViewSource, source, symbolicatedSourcePromise, - canViewElementSourceFunction, - viewElementSourceFunction, }: ActualSourceButtonProps): React.Node { const symbolicatedSource = symbolicatedSourcePromise == null ? null : React.use(symbolicatedSourcePromise); - // In some cases (e.g. FB internal usage) the standalone shell might not be able to view the source. - // To detect this case, we defer to an injected helper function (if present). - const buttonIsEnabled = - !!canViewSource && - viewElementSourceFunction != null && - source != null && - (canViewElementSourceFunction == null || - canViewElementSourceFunction(source, symbolicatedSource)); - - const viewSource = useCallback(() => { - if (viewElementSourceFunction != null && source != null) { - viewElementSourceFunction(source, symbolicatedSource); - } - }, [source, symbolicatedSource]); - + const [buttonIsEnabled, viewSource] = useOpenResource( + source, + symbolicatedSource, + ); return ( ); + } else { + editorToolbar = ( +
+ +
+ +
+ ); } return (
- -
- + {editorToolbar} +
+ {editorURL ? ( + + ) : ( + 'Configure an external editor to open local files.' + )} +
); } diff --git a/packages/react-devtools-shared/src/devtools/views/Editor/utils.js b/packages/react-devtools-shared/src/devtools/views/Editor/utils.js index 89e697bd60c..b239c9d213a 100644 --- a/packages/react-devtools-shared/src/devtools/views/Editor/utils.js +++ b/packages/react-devtools-shared/src/devtools/views/Editor/utils.js @@ -7,11 +7,11 @@ * @flow */ -import type {ReactFunctionLocation} from 'shared/ReactTypes'; +import type {ReactFunctionLocation, ReactCallSite} from 'shared/ReactTypes'; export function checkConditions( editorURL: string, - source: ReactFunctionLocation, + source: ReactFunctionLocation | ReactCallSite, ): {url: URL | null, shouldDisableButton: boolean} { try { const url = new URL(editorURL); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js index b7b031a990c..77f0d13feb7 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js @@ -12,7 +12,6 @@ import type {SchedulingEvent} from 'react-devtools-timeline/src/types'; import * as React from 'react'; import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; -import ViewElementSourceContext from '../Components/ViewElementSourceContext'; import {useContext} from 'react'; import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext'; import { @@ -22,6 +21,7 @@ import { import {stackToComponentLocations} from 'react-devtools-shared/src/devtools/utils'; import {copy} from 'clipboard-js'; import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck'; +import useOpenResource from '../useOpenResource'; import styles from './SidebarEventInfo.css'; @@ -32,9 +32,6 @@ type SchedulingEventProps = { }; function SchedulingEventInfo({eventInfo}: SchedulingEventProps) { - const {canViewElementSourceFunction, viewElementSourceFunction} = useContext( - ViewElementSourceContext, - ); const {componentName, timestamp} = eventInfo; const componentStack = eventInfo.componentStack || null; @@ -79,15 +76,10 @@ function SchedulingEventInfo({eventInfo}: SchedulingEventProps) { // TODO: We should support symbolication here as well, but // symbolicating the whole stack can be expensive - const canViewSource = - canViewElementSourceFunction == null || - canViewElementSourceFunction(location, null); - - const viewSource = - !canViewSource || viewElementSourceFunction == null - ? () => null - : () => viewElementSourceFunction(location, null); - + const [canViewSource, viewSource] = useOpenResource( + location, + null, + ); return (
  • +
    + +
    + +
    +
    Display density
    + +
    + {supportsTraceUpdates && (