mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[DevTools] Add ignore-listed stack frame disclosure (#36828)
This commit is contained in:
+5
@@ -143,3 +143,8 @@
|
||||
padding-left: 1.25rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.SuspendedBySkeleton {
|
||||
padding: 0.25rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
Vendored
+198
-97
@@ -9,7 +9,7 @@
|
||||
|
||||
import {copy} from 'clipboard-js';
|
||||
import * as React from 'react';
|
||||
import {useState, useTransition} from 'react';
|
||||
import {use, useContext, useState, useTransition} from 'react';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import KeyValue from './KeyValue';
|
||||
@@ -17,10 +17,13 @@ import {serializeDataForCopy, pluralize} from '../utils';
|
||||
import Store from '../../store';
|
||||
import styles from './InspectedElementSharedStyles.css';
|
||||
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
|
||||
import StackTraceView from './StackTraceView';
|
||||
import FetchFileWithCachingContext from './FetchFileWithCachingContext';
|
||||
import StackTraceView, {IgnoreListToggleButton} from './StackTraceView';
|
||||
import OwnerView from './OwnerView';
|
||||
import {meta} from '../../../hydration';
|
||||
import Skeleton from './Skeleton';
|
||||
import useInferredName from '../useInferredName';
|
||||
import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import {getClassNameForEnvironment} from '../SuspenseTab/SuspenseEnvironmentColors.js';
|
||||
|
||||
@@ -29,6 +32,8 @@ import type {
|
||||
SerializedAsyncInfo,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {ReactStackTrace} from 'shared/ReactTypes';
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import {
|
||||
UNKNOWN_SUSPENDERS_NONE,
|
||||
@@ -94,6 +99,78 @@ function formatBytes(bytes: number) {
|
||||
return (bytes / 1_000_000_000).toFixed(1) + ' gB';
|
||||
}
|
||||
|
||||
type StackTraceGroupProps = {
|
||||
children: (showIgnoreList: boolean) => React.Node,
|
||||
ioStack: null | ReactStackTrace,
|
||||
asyncInfoStack: null | ReactStackTrace,
|
||||
};
|
||||
|
||||
function StackTraceGroup({
|
||||
children,
|
||||
ioStack,
|
||||
asyncInfoStack,
|
||||
}: StackTraceGroupProps): React.Node {
|
||||
const [showIgnoreList, setShowIgnoreList] = useState(false);
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
const ioStackHasIgnoredFrames =
|
||||
ioStack !== null &&
|
||||
ioStack.some(callSite => {
|
||||
const [, virtualURL, virtualLine, virtualColumn] = callSite;
|
||||
|
||||
// symbolicated output is cached
|
||||
const symbolicatedCallSite: null | SourceMappedLocation =
|
||||
fetchFileWithCaching !== null
|
||||
? use(
|
||||
symbolicateSourceWithCache(
|
||||
fetchFileWithCaching,
|
||||
virtualURL,
|
||||
virtualLine,
|
||||
virtualColumn,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return symbolicatedCallSite !== null && symbolicatedCallSite.ignored;
|
||||
});
|
||||
|
||||
const asyncInfoStackHasIgnoredFrames =
|
||||
asyncInfoStack !== null &&
|
||||
asyncInfoStack.some(callSite => {
|
||||
const [, virtualURL, virtualLine, virtualColumn] = callSite;
|
||||
|
||||
// symbolicated output is cached
|
||||
const symbolicatedCallSite: null | SourceMappedLocation =
|
||||
fetchFileWithCaching !== null
|
||||
? use(
|
||||
symbolicateSourceWithCache(
|
||||
fetchFileWithCaching,
|
||||
virtualURL,
|
||||
virtualLine,
|
||||
virtualColumn,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return symbolicatedCallSite !== null && symbolicatedCallSite.ignored;
|
||||
});
|
||||
|
||||
const hasIgnoredFrames =
|
||||
ioStackHasIgnoredFrames || asyncInfoStackHasIgnoredFrames;
|
||||
|
||||
return (
|
||||
<>
|
||||
{children(showIgnoreList)}
|
||||
{hasIgnoredFrames && (
|
||||
<IgnoreListToggleButton
|
||||
onClick={() => setShowIgnoreList(prev => !prev)}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SuspendedByRow({
|
||||
bridge,
|
||||
element,
|
||||
@@ -203,102 +280,126 @@ function SuspendedByRow({
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<div className={styles.CollapsableContent}>
|
||||
{showIOStack && (
|
||||
<StackTraceView
|
||||
stack={ioInfo.stack}
|
||||
environmentName={
|
||||
ioOwner !== null && ioOwner.env === ioInfo.env
|
||||
? null
|
||||
: ioInfo.env
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{ioOwner !== null &&
|
||||
ioOwner.id !== inspectedElement.id &&
|
||||
(showIOStack ||
|
||||
!showAwaitStack ||
|
||||
asyncOwner === null ||
|
||||
ioOwner.id !== asyncOwner.id) ? (
|
||||
<OwnerView
|
||||
key={ioOwner.id}
|
||||
displayName={ioOwner.displayName || 'Anonymous'}
|
||||
environmentName={
|
||||
ioOwner.env === inspectedElement.env &&
|
||||
ioOwner.env === ioInfo.env
|
||||
? null
|
||||
: ioOwner.env
|
||||
}
|
||||
hocDisplayNames={ioOwner.hocDisplayNames}
|
||||
compiledWithForget={ioOwner.compiledWithForget}
|
||||
id={ioOwner.id}
|
||||
isInStore={store.containsElement(ioOwner.id)}
|
||||
type={ioOwner.type}
|
||||
/>
|
||||
) : null}
|
||||
{showAwaitStack ? (
|
||||
<>
|
||||
<div className={styles.SmallHeader}>awaited at:</div>
|
||||
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
|
||||
<StackTraceView
|
||||
stack={asyncInfo.stack}
|
||||
environmentName={
|
||||
asyncOwner !== null && asyncOwner.env === asyncInfo.env
|
||||
? null
|
||||
: asyncInfo.env
|
||||
}
|
||||
/>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<div className={styles.SuspendedBySkeleton}>
|
||||
<Skeleton height={16} width="40%" />
|
||||
</div>
|
||||
}>
|
||||
<StackTraceGroup
|
||||
ioStack={showIOStack ? ioInfo.stack : null}
|
||||
asyncInfoStack={showAwaitStack ? asyncInfo.stack : null}>
|
||||
{(showIgnoreList: boolean) => (
|
||||
<>
|
||||
{showIOStack && (
|
||||
<StackTraceView
|
||||
stack={ioInfo.stack}
|
||||
environmentName={
|
||||
ioOwner !== null && ioOwner.env === ioInfo.env
|
||||
? null
|
||||
: ioInfo.env
|
||||
}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
)}
|
||||
{ioOwner !== null &&
|
||||
ioOwner.id !== inspectedElement.id &&
|
||||
(showIOStack ||
|
||||
!showAwaitStack ||
|
||||
asyncOwner === null ||
|
||||
ioOwner.id !== asyncOwner.id) ? (
|
||||
<OwnerView
|
||||
key={ioOwner.id}
|
||||
displayName={ioOwner.displayName || 'Anonymous'}
|
||||
environmentName={
|
||||
ioOwner.env === inspectedElement.env &&
|
||||
ioOwner.env === ioInfo.env
|
||||
? null
|
||||
: ioOwner.env
|
||||
}
|
||||
hocDisplayNames={ioOwner.hocDisplayNames}
|
||||
compiledWithForget={ioOwner.compiledWithForget}
|
||||
id={ioOwner.id}
|
||||
isInStore={store.containsElement(ioOwner.id)}
|
||||
type={ioOwner.type}
|
||||
/>
|
||||
) : null}
|
||||
{showAwaitStack ? (
|
||||
<>
|
||||
<div className={styles.SmallHeader}>awaited at:</div>
|
||||
{asyncInfo.stack !== null &&
|
||||
asyncInfo.stack.length > 0 && (
|
||||
<StackTraceView
|
||||
stack={asyncInfo.stack}
|
||||
environmentName={
|
||||
asyncOwner !== null &&
|
||||
asyncOwner.env === asyncInfo.env
|
||||
? null
|
||||
: asyncInfo.env
|
||||
}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
)}
|
||||
{asyncOwner !== null &&
|
||||
asyncOwner.id !== inspectedElement.id ? (
|
||||
<OwnerView
|
||||
key={asyncOwner.id}
|
||||
displayName={asyncOwner.displayName || 'Anonymous'}
|
||||
environmentName={
|
||||
asyncOwner.env === inspectedElement.env &&
|
||||
asyncOwner.env === asyncInfo.env
|
||||
? null
|
||||
: asyncOwner.env
|
||||
}
|
||||
hocDisplayNames={asyncOwner.hocDisplayNames}
|
||||
compiledWithForget={asyncOwner.compiledWithForget}
|
||||
id={asyncOwner.id}
|
||||
isInStore={store.containsElement(asyncOwner.id)}
|
||||
type={asyncOwner.type}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
<div className={styles.PreviewContainer}>
|
||||
<KeyValue
|
||||
alphaSort={true}
|
||||
bridge={bridge}
|
||||
canDeletePaths={false}
|
||||
canEditValues={false}
|
||||
canRenamePaths={false}
|
||||
depth={1}
|
||||
element={element}
|
||||
hidden={false}
|
||||
inspectedElement={inspectedElement}
|
||||
name={
|
||||
isFulfilled
|
||||
? 'awaited value'
|
||||
: isRejected
|
||||
? 'rejected with'
|
||||
: 'pending value'
|
||||
}
|
||||
path={
|
||||
isFulfilled
|
||||
? [index, 'awaited', 'value', 'value']
|
||||
: isRejected
|
||||
? [index, 'awaited', 'value', 'reason']
|
||||
: [index, 'awaited', 'value']
|
||||
}
|
||||
pathRoot="suspendedBy"
|
||||
store={store}
|
||||
value={
|
||||
isFulfilled
|
||||
? value.value
|
||||
: isRejected
|
||||
? value.reason
|
||||
: value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? (
|
||||
<OwnerView
|
||||
key={asyncOwner.id}
|
||||
displayName={asyncOwner.displayName || 'Anonymous'}
|
||||
environmentName={
|
||||
asyncOwner.env === inspectedElement.env &&
|
||||
asyncOwner.env === asyncInfo.env
|
||||
? null
|
||||
: asyncOwner.env
|
||||
}
|
||||
hocDisplayNames={asyncOwner.hocDisplayNames}
|
||||
compiledWithForget={asyncOwner.compiledWithForget}
|
||||
id={asyncOwner.id}
|
||||
isInStore={store.containsElement(asyncOwner.id)}
|
||||
type={asyncOwner.type}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
<div className={styles.PreviewContainer}>
|
||||
<KeyValue
|
||||
alphaSort={true}
|
||||
bridge={bridge}
|
||||
canDeletePaths={false}
|
||||
canEditValues={false}
|
||||
canRenamePaths={false}
|
||||
depth={1}
|
||||
element={element}
|
||||
hidden={false}
|
||||
inspectedElement={inspectedElement}
|
||||
name={
|
||||
isFulfilled
|
||||
? 'awaited value'
|
||||
: isRejected
|
||||
? 'rejected with'
|
||||
: 'pending value'
|
||||
}
|
||||
path={
|
||||
isFulfilled
|
||||
? [index, 'awaited', 'value', 'value']
|
||||
: isRejected
|
||||
? [index, 'awaited', 'value', 'reason']
|
||||
: [index, 'awaited', 'value']
|
||||
}
|
||||
pathRoot="suspendedBy"
|
||||
store={store}
|
||||
value={
|
||||
isFulfilled ? value.value : isRejected ? value.reason : value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StackTraceGroup>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+124
-27
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Fragment, useContext} from 'react';
|
||||
import {Fragment, useContext, useState, use} from 'react';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import InspectedElementBadges from './InspectedElementBadges';
|
||||
import InspectedElementContextTree from './InspectedElementContextTree';
|
||||
@@ -19,15 +19,17 @@ import InspectedElementStateTree from './InspectedElementStateTree';
|
||||
import InspectedElementStyleXPlugin from './InspectedElementStyleXPlugin';
|
||||
import InspectedElementSuspendedBy from './InspectedElementSuspendedBy';
|
||||
import NativeStyleEditor from './NativeStyleEditor';
|
||||
import FetchFileWithCachingContext from './FetchFileWithCachingContext';
|
||||
import {enableStyleXFeatures} from 'react-devtools-feature-flags';
|
||||
import InspectedElementSourcePanel from './InspectedElementSourcePanel';
|
||||
import StackTraceView from './StackTraceView';
|
||||
import StackTraceView, {IgnoreListToggleButton} from './StackTraceView';
|
||||
import OwnerView from './OwnerView';
|
||||
import Skeleton from './Skeleton';
|
||||
import {
|
||||
ElementTypeSuspense,
|
||||
ElementTypeActivity,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import styles from './InspectedElementView.css';
|
||||
|
||||
@@ -39,6 +41,83 @@ import type {HookNames} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
type StackTraceGroupProps = {
|
||||
children: (showIgnoreList: boolean) => React.Node,
|
||||
componentStack: InspectedElement['stack'],
|
||||
owners: InspectedElement['owners'],
|
||||
};
|
||||
|
||||
function StackTraceGroup({
|
||||
children,
|
||||
componentStack,
|
||||
owners,
|
||||
}: StackTraceGroupProps): React.Node {
|
||||
const [showIgnoreList, setShowIgnoreList] = useState(false);
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
const componentStackHasIgnoredFrames =
|
||||
componentStack !== null &&
|
||||
componentStack.some(callSite => {
|
||||
const [, virtualURL, virtualLine, virtualColumn] = callSite;
|
||||
|
||||
// symbolicated output is cached
|
||||
const symbolicatedCallSite: null | SourceMappedLocation =
|
||||
fetchFileWithCaching !== null
|
||||
? use(
|
||||
symbolicateSourceWithCache(
|
||||
fetchFileWithCaching,
|
||||
virtualURL,
|
||||
virtualLine,
|
||||
virtualColumn,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return symbolicatedCallSite !== null && symbolicatedCallSite.ignored;
|
||||
});
|
||||
|
||||
const ownerStacksHaveIgnoredFrames =
|
||||
owners !== null &&
|
||||
owners.some(owner => {
|
||||
return (
|
||||
owner.stack !== null &&
|
||||
owner.stack.some(callSite => {
|
||||
const [, virtualURL, virtualLine, virtualColumn] = callSite;
|
||||
|
||||
// symbolicated output is cached
|
||||
const symbolicatedCallSite: null | SourceMappedLocation =
|
||||
fetchFileWithCaching !== null
|
||||
? use(
|
||||
symbolicateSourceWithCache(
|
||||
fetchFileWithCaching,
|
||||
virtualURL,
|
||||
virtualLine,
|
||||
virtualColumn,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return symbolicatedCallSite !== null && symbolicatedCallSite.ignored;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const hasIgnoredFrames =
|
||||
componentStackHasIgnoredFrames || ownerStacksHaveIgnoredFrames;
|
||||
|
||||
return (
|
||||
<>
|
||||
{children(showIgnoreList)}
|
||||
{hasIgnoredFrames && (
|
||||
<IgnoreListToggleButton
|
||||
onClick={() => setShowIgnoreList(prev => !prev)}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
@@ -189,33 +268,51 @@ export default function InspectedElementView({
|
||||
<Skeleton height={16} width="40%" />
|
||||
</div>
|
||||
}>
|
||||
{showStack ? <StackTraceView stack={stack} /> : null}
|
||||
{showOwnersList &&
|
||||
owners?.map(owner => (
|
||||
<Fragment key={owner.id}>
|
||||
<OwnerView
|
||||
displayName={owner.displayName || 'Anonymous'}
|
||||
hocDisplayNames={owner.hocDisplayNames}
|
||||
environmentName={
|
||||
inspectedElement.env === owner.env ? null : owner.env
|
||||
}
|
||||
compiledWithForget={owner.compiledWithForget}
|
||||
id={owner.id}
|
||||
isInStore={store.containsElement(owner.id)}
|
||||
type={owner.type}
|
||||
/>
|
||||
{owner.stack != null && owner.stack.length > 0 ? (
|
||||
<StackTraceView stack={owner.stack} />
|
||||
<StackTraceGroup componentStack={stack} owners={owners}>
|
||||
{(showIgnoreList: boolean) => (
|
||||
<>
|
||||
{showStack ? (
|
||||
<StackTraceView
|
||||
stack={stack}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
{showOwnersList &&
|
||||
owners?.map(owner => (
|
||||
<Fragment key={owner.id}>
|
||||
<OwnerView
|
||||
displayName={owner.displayName || 'Anonymous'}
|
||||
hocDisplayNames={owner.hocDisplayNames}
|
||||
environmentName={
|
||||
inspectedElement.env === owner.env
|
||||
? null
|
||||
: owner.env
|
||||
}
|
||||
compiledWithForget={owner.compiledWithForget}
|
||||
id={owner.id}
|
||||
isInStore={store.containsElement(owner.id)}
|
||||
type={owner.type}
|
||||
/>
|
||||
{owner.stack != null && owner.stack.length > 0 ? (
|
||||
<StackTraceView
|
||||
stack={owner.stack}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{rootType !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rootType}</div>
|
||||
)}
|
||||
{rendererLabel !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rendererLabel}</div>
|
||||
)}
|
||||
{rootType !== null && (
|
||||
<div className={styles.OwnersMetaField}>{rootType}</div>
|
||||
)}
|
||||
{rendererLabel !== null && (
|
||||
<div className={styles.OwnersMetaField}>
|
||||
{rendererLabel}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StackTraceGroup>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -8,10 +8,18 @@
|
||||
white-space-collapse: preserve;
|
||||
}
|
||||
|
||||
.IgnoredCallSite {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.IgnoredCallSite, .BuiltInCallSite {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.IgnoredCallSite.CallSite {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.CallSite + .BuiltInCallSite {
|
||||
display: block;
|
||||
}
|
||||
@@ -33,3 +41,14 @@
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.IgnoreListToggleContainer {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.IgnoreListToggleButton {
|
||||
background: none;
|
||||
color: var(--color-link);
|
||||
font: inherit;
|
||||
font-style: italic;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
+68
-8
@@ -10,6 +10,7 @@
|
||||
import * as React from 'react';
|
||||
import {use, useContext} from 'react';
|
||||
|
||||
import Button from '../Button';
|
||||
import useOpenResource from '../useOpenResource';
|
||||
|
||||
import ElementBadges from './ElementBadges';
|
||||
@@ -29,11 +30,13 @@ import formatLocationForDisplay from './formatLocationForDisplay';
|
||||
type CallSiteViewProps = {
|
||||
callSite: ReactCallSite,
|
||||
environmentName: null | string,
|
||||
showIgnoreList: boolean,
|
||||
};
|
||||
|
||||
export function CallSiteView({
|
||||
callSite,
|
||||
environmentName,
|
||||
showIgnoreList,
|
||||
}: CallSiteViewProps): React.Node {
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
@@ -60,16 +63,14 @@ export function CallSiteView({
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
|
||||
const ignored =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
|
||||
// TODO: Make an option to be able to toggle the display of ignore listed rows.
|
||||
// Ideally this UI should be higher than a single Stack Trace so that there's not
|
||||
// multiple buttons in a single inspection taking up space.
|
||||
|
||||
const isBuiltIn = url === '' || url.startsWith('<anonymous>'); // This looks like a fake anonymous through eval.
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
ignored
|
||||
? styles.IgnoredCallSite
|
||||
? styles.IgnoredCallSite +
|
||||
(showIgnoreList ? ' ' + styles.CallSite : '')
|
||||
: isBuiltIn
|
||||
? styles.BuiltInCallSite
|
||||
: styles.CallSite
|
||||
@@ -94,15 +95,75 @@ export function CallSiteView({
|
||||
);
|
||||
}
|
||||
|
||||
type IgnoreListToggleButtonProps = {
|
||||
onClick: () => void,
|
||||
showIgnoreList: boolean,
|
||||
};
|
||||
|
||||
export function IgnoreListToggleButton({
|
||||
onClick,
|
||||
showIgnoreList,
|
||||
}: IgnoreListToggleButtonProps): React.Node {
|
||||
const label = showIgnoreList
|
||||
? 'Hide ignore-listed frames'
|
||||
: 'Show ignore-listed frames';
|
||||
|
||||
return (
|
||||
<div className={styles.IgnoreListToggleContainer}>
|
||||
<Button
|
||||
className={styles.IgnoreListToggleButton}
|
||||
onClick={onClick}
|
||||
title={label}>
|
||||
{label}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
stack: ReactStackTrace,
|
||||
environmentName: null | string,
|
||||
showIgnoreList: boolean,
|
||||
};
|
||||
|
||||
export default function StackTraceView({
|
||||
stack,
|
||||
environmentName,
|
||||
showIgnoreList,
|
||||
}: Props): React.Node {
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
let lastMeaningfulFrameIndex = -1;
|
||||
// Reverse loop to find the last non-ignored, non-built-in index
|
||||
for (let index = stack.length - 1; index >= 0; index--) {
|
||||
const callSite = stack[index];
|
||||
const [, virtualURL, virtualLine, virtualColumn] = callSite;
|
||||
|
||||
// symbolicated output is cached
|
||||
const symbolicatedCallSite: null | SourceMappedLocation =
|
||||
fetchFileWithCaching !== null
|
||||
? use(
|
||||
symbolicateSourceWithCache(
|
||||
fetchFileWithCaching,
|
||||
virtualURL,
|
||||
virtualLine,
|
||||
virtualColumn,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
const [, url] =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
|
||||
const ignored =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
|
||||
// This looks like a fake anonymous through eval.
|
||||
const isBuiltIn = url === '' || url.startsWith('<anonymous>');
|
||||
|
||||
if (!ignored && !isBuiltIn) {
|
||||
lastMeaningfulFrameIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.StackTraceView}>
|
||||
{stack.map((callSite, index) => (
|
||||
@@ -110,11 +171,10 @@ export default function StackTraceView({
|
||||
key={index}
|
||||
callSite={callSite}
|
||||
environmentName={
|
||||
// Badge last row
|
||||
// TODO: If we start ignore listing the last row, we should badge the last
|
||||
// non-ignored row.
|
||||
index === stack.length - 1 ? environmentName : null
|
||||
// Badge last meaningful frame (non-ignored, non-built-in)
|
||||
index === lastMeaningfulFrameIndex ? environmentName : null
|
||||
}
|
||||
showIgnoreList={showIgnoreList}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user