Files
react/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js
T
Ruslan Lesiutin e5287287aa feat[devtools]: symbolicate source for inspected element (#28471)
Stacked on https://github.com/facebook/react/pull/28351, please review
only the last commit.

Top-level description of the approach:
1. Once user selects an element from the tree, frontend asks backend to
return the inspected element, this is where we simulate an error
happening in `render` function of the component and then we parse the
error stack. As an improvement, we should probably migrate from custom
implementation of error stack parser to `error-stack-parser` from npm.
2. When frontend receives the inspected element and this object is being
propagated, we create a Promise for symbolicated source, which is then
passed down to all components, which are using `source`.
3. These components use `use` hook for this promise and are wrapped in
Suspense.

Caching:
1. For browser extension, we cache Promises based on requested resource
+ key + column, also added use of
`chrome.devtools.inspectedWindow.getResource` API.
2. For standalone case (RN), we cache based on requested resource url,
we cache the content of it.
2024-03-05 12:32:11 +00:00

98 lines
2.9 KiB
JavaScript

/**
* 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 ButtonIcon from '../ButtonIcon';
import Button from '../Button';
import ViewElementSourceContext from './ViewElementSourceContext';
import Skeleton from './Skeleton';
import type {Source as InspectedElementSource} from 'react-devtools-shared/src/shared/types';
import type {
CanViewElementSource,
ViewElementSource,
} from 'react-devtools-shared/src/devtools/views/DevTools';
const {useCallback, useContext} = React;
type Props = {
canViewSource: ?boolean,
source: ?InspectedElementSource,
symbolicatedSourcePromise: Promise<InspectedElementSource | null> | null,
};
function InspectedElementViewSourceButton({
canViewSource,
source,
symbolicatedSourcePromise,
}: Props): React.Node {
const {canViewElementSourceFunction, viewElementSourceFunction} = useContext(
ViewElementSourceContext,
);
return (
<React.Suspense fallback={<Skeleton height={16} width={24} />}>
<ActualSourceButton
canViewSource={canViewSource}
source={source}
symbolicatedSourcePromise={symbolicatedSourcePromise}
canViewElementSourceFunction={canViewElementSourceFunction}
viewElementSourceFunction={viewElementSourceFunction}
/>
</React.Suspense>
);
}
type ActualSourceButtonProps = {
canViewSource: ?boolean,
source: ?InspectedElementSource,
symbolicatedSourcePromise: Promise<InspectedElementSource | null> | 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]);
return (
<Button
disabled={!buttonIsEnabled}
onClick={viewSource}
title="View source for this element">
<ButtonIcon type="view-source" />
</Button>
);
}
export default InspectedElementViewSourceButton;