Files
react/packages/react-devtools-shared/src/devtools/views/Components/OpenInEditorButton.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

91 lines
2.4 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.
*
*/
import * as React from 'react';
import Button from 'react-devtools-shared/src/devtools/views/Button';
import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
import type {Source} from 'react-devtools-shared/src/shared/types';
type Props = {
editorURL: string,
source: Source,
symbolicatedSourcePromise: Promise<Source | null>,
};
function checkConditions(
editorURL: string,
source: Source,
): {url: URL | null, shouldDisableButton: boolean} {
try {
const url = new URL(editorURL);
let sourceURL = source.sourceURL;
// Check if sourceURL is a correct URL, which has a protocol specified
if (sourceURL.includes('://')) {
if (!__IS_INTERNAL_VERSION__) {
// In this case, we can't really determine the path to a file, disable a button
return {url: null, shouldDisableButton: true};
} else {
const endOfSourceMapURLPattern = '.js/';
const endOfSourceMapURLIndex = sourceURL.lastIndexOf(
endOfSourceMapURLPattern,
);
if (endOfSourceMapURLIndex === -1) {
return {url: null, shouldDisableButton: true};
} else {
sourceURL = sourceURL.slice(
endOfSourceMapURLIndex + endOfSourceMapURLPattern.length,
sourceURL.length,
);
}
}
}
const lineNumberAsString = String(source.line);
url.href = url.href
.replace('{path}', sourceURL)
.replace('{line}', lineNumberAsString)
.replace('%7Bpath%7D', sourceURL)
.replace('%7Bline%7D', lineNumberAsString);
return {url, shouldDisableButton: false};
} catch (e) {
// User has provided incorrect editor url
return {url: null, shouldDisableButton: true};
}
}
function OpenInEditorButton({
editorURL,
source,
symbolicatedSourcePromise,
}: Props): React.Node {
const symbolicatedSource = React.use(symbolicatedSourcePromise);
const {url, shouldDisableButton} = checkConditions(
editorURL,
symbolicatedSource ? symbolicatedSource : source,
);
return (
<Button
disabled={shouldDisableButton}
onClick={() => window.open(url)}
title="Open in editor">
<ButtonIcon type="editor" />
</Button>
);
}
export default OpenInEditorButton;