mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
[DevTools] Implement Owner Stacks (#30417)
Stacked on #30410. Use "owner stacks" as the appended component stack if it is available on the Fiber. This will only be available if the enableOwnerStacks flag is on. Otherwise it fallback to parent stacks. In prod, there's no owner so it's never added there. I was going back and forth on whether to inject essentially `captureOwnerStack` as part of the DevTools hooks or replicate the implementation but decided to replicate the implementation. The DevTools needs all the same information from internals to implement owner views elsewhere in the UI anyway so we're not saving anything in terms of the scope of internals. Additionally, we really need this information for non-current components as well like "rendered by" views of the currently selected component. It can also be useful if we need to change the format after the fact like we did for parent stacks in: https://github.com/facebook/react/pull/30289 Injecting the implementation would lock us into specifics both in terms of what the core needs to provide and what the DevTools can use. The implementation depends on the technique used in #30369 which tags frames to strip out with `react-stack-bottom-frame`. That's how the implementation knows how to materialize the error if it hasn't already. Firefox: <img width="487" alt="Screenshot 2024-07-21 at 11 33 37 PM" src="https://github.com/user-attachments/assets/d3539b53-4578-4fdd-af25-25698b2bcc7d"> Follow up: One thing about this view is that it doesn't include the current actual synchronous stack. When I used to append these I would include both the real current stack and the owner stack. That's because the owner stack doesn't include the name of the currently executing component. I'll probably inject the current stack too in addition to the owner stack. This is similar to how native Async Stacks are basically just appended onto the current stack rather than its own.
This commit is contained in:
committed by
GitHub
parent
b7e7f1a3fa
commit
43dac1ee8d
@@ -67,6 +67,6 @@
|
||||
"workerize-loader": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"web-ext": "^4"
|
||||
"web-ext": "^8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('component stack', () => {
|
||||
let act;
|
||||
let mockError;
|
||||
let mockWarn;
|
||||
let supportsOwnerStacks;
|
||||
|
||||
beforeEach(() => {
|
||||
// Intercept native console methods before DevTools bootstraps.
|
||||
@@ -31,6 +32,12 @@ describe('component stack', () => {
|
||||
act = utils.act;
|
||||
|
||||
React = require('react');
|
||||
if (
|
||||
React.version.startsWith('19') &&
|
||||
React.version.includes('experimental')
|
||||
) {
|
||||
supportsOwnerStacks = true;
|
||||
}
|
||||
});
|
||||
|
||||
const {render} = getVersionedRenderImplementation();
|
||||
@@ -49,13 +56,13 @@ describe('component stack', () => {
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
'Test error.',
|
||||
'\n in Child (at **)' +
|
||||
(supportsOwnerStacks ? '' : '\n in Child (at **)') +
|
||||
'\n in Parent (at **)' +
|
||||
'\n in Grandparent (at **)',
|
||||
);
|
||||
expect(mockWarn).toHaveBeenCalledWith(
|
||||
'Test warning.',
|
||||
'\n in Child (at **)' +
|
||||
(supportsOwnerStacks ? '' : '\n in Child (at **)') +
|
||||
'\n in Parent (at **)' +
|
||||
'\n in Grandparent (at **)',
|
||||
);
|
||||
@@ -112,17 +119,21 @@ describe('component stack', () => {
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
'Test error.',
|
||||
'\n in Child (at **)' +
|
||||
'\n in ServerComponent (at **)' +
|
||||
'\n in Parent (at **)' +
|
||||
'\n in Grandparent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Child (at **)'
|
||||
: '\n in Child (at **)' +
|
||||
'\n in ServerComponent (at **)' +
|
||||
'\n in Parent (at **)' +
|
||||
'\n in Grandparent (at **)',
|
||||
);
|
||||
expect(mockWarn).toHaveBeenCalledWith(
|
||||
'Test warning.',
|
||||
'\n in Child (at **)' +
|
||||
'\n in ServerComponent (at **)' +
|
||||
'\n in Parent (at **)' +
|
||||
'\n in Grandparent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Child (at **)'
|
||||
: '\n in Child (at **)' +
|
||||
'\n in ServerComponent (at **)' +
|
||||
'\n in Parent (at **)' +
|
||||
'\n in Grandparent (at **)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ let mockWarn;
|
||||
let patchConsole;
|
||||
let unpatchConsole;
|
||||
let rendererID;
|
||||
let supportsOwnerStacks = false;
|
||||
|
||||
describe('console', () => {
|
||||
beforeEach(() => {
|
||||
@@ -62,6 +63,12 @@ describe('console', () => {
|
||||
};
|
||||
|
||||
React = require('react');
|
||||
if (
|
||||
React.version.startsWith('19') &&
|
||||
React.version.includes('experimental')
|
||||
) {
|
||||
supportsOwnerStacks = true;
|
||||
}
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
|
||||
const utils = require('./utils');
|
||||
@@ -224,13 +231,17 @@ describe('console', () => {
|
||||
expect(mockWarn.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[0][0]).toBe('warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledTimes(1);
|
||||
expect(mockError.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[0][0]).toBe('error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -267,23 +278,31 @@ describe('console', () => {
|
||||
expect(mockWarn.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[0][0]).toBe('active warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockWarn.mock.calls[1]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[1][0]).toBe('passive warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledTimes(2);
|
||||
expect(mockError.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[0][0]).toBe('active error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError.mock.calls[1]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[1][0]).toBe('passive error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -325,23 +344,31 @@ describe('console', () => {
|
||||
expect(mockWarn.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[0][0]).toBe('didMount warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockWarn.mock.calls[1]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[1][0]).toBe('didUpdate warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledTimes(2);
|
||||
expect(mockError.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[0][0]).toBe('didMount error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError.mock.calls[1]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[1][0]).toBe('didUpdate error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -375,13 +402,17 @@ describe('console', () => {
|
||||
expect(mockWarn.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[0][0]).toBe('warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledTimes(1);
|
||||
expect(mockError.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[0][0]).toBe('error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -465,13 +496,17 @@ describe('console', () => {
|
||||
expect(mockWarn.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockWarn.mock.calls[0][0]).toBe('warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledTimes(1);
|
||||
expect(mockError.mock.calls[0]).toHaveLength(2);
|
||||
expect(mockError.mock.calls[0][0]).toBe('error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -996,7 +1031,9 @@ describe('console', () => {
|
||||
expect(mockWarn).toHaveBeenCalledTimes(2);
|
||||
expect(mockWarn.mock.calls[0]).toHaveLength(2);
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockWarn.mock.calls[1]).toHaveLength(3);
|
||||
expect(mockWarn.mock.calls[1][0]).toEqual(
|
||||
@@ -1004,13 +1041,17 @@ describe('console', () => {
|
||||
);
|
||||
expect(mockWarn.mock.calls[1][1]).toMatch('warn');
|
||||
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][2]).trim()).toEqual(
|
||||
'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? 'in Parent (at **)'
|
||||
: 'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
|
||||
expect(mockError).toHaveBeenCalledTimes(2);
|
||||
expect(mockError.mock.calls[0]).toHaveLength(2);
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toEqual(
|
||||
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? '\n in Parent (at **)'
|
||||
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
expect(mockError.mock.calls[1]).toHaveLength(3);
|
||||
expect(mockError.mock.calls[1][0]).toEqual(
|
||||
@@ -1018,7 +1059,9 @@ describe('console', () => {
|
||||
);
|
||||
expect(mockError.mock.calls[1][1]).toEqual('error');
|
||||
expect(normalizeCodeLocInfo(mockError.mock.calls[1][2]).trim()).toEqual(
|
||||
'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
supportsOwnerStacks
|
||||
? 'in Parent (at **)'
|
||||
: 'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {CurrentDispatcherRef, WorkTagMap} from './types';
|
||||
|
||||
import type {ReactComponentInfo} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
describeBuiltInComponentFrame,
|
||||
describeFunctionComponentFrame,
|
||||
@@ -22,6 +24,8 @@ import {
|
||||
describeDebugInfoFrame,
|
||||
} from './DevToolsComponentStackFrame';
|
||||
|
||||
import {formatOwnerStack} from './DevToolsOwnerStack';
|
||||
|
||||
export function describeFiber(
|
||||
workTagMap: WorkTagMap,
|
||||
workInProgress: Fiber,
|
||||
@@ -110,3 +114,119 @@ export function supportsNativeConsoleTasks(fiber: Fiber): boolean {
|
||||
// Ideally we'd detect if this task was created while the DevTools was open or not.
|
||||
return !!fiber._debugTask;
|
||||
}
|
||||
|
||||
export function supportsOwnerStacks(fiber: Fiber): boolean {
|
||||
// If this Fiber supports owner stacks then it'll have the _debugStack field.
|
||||
// It might be null but that still means we should use the owner stack logic.
|
||||
return fiber._debugStack !== undefined;
|
||||
}
|
||||
|
||||
function describeFunctionComponentFrameWithoutLineNumber(fn: Function): string {
|
||||
// We use this because we don't actually want to describe the line of the component
|
||||
// but just the component name.
|
||||
const name = fn ? fn.displayName || fn.name : '';
|
||||
return name ? describeBuiltInComponentFrame(name) : '';
|
||||
}
|
||||
|
||||
export function getOwnerStackByFiberInDev(
|
||||
workTagMap: WorkTagMap,
|
||||
workInProgress: Fiber,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
const {
|
||||
HostHoistable,
|
||||
HostSingleton,
|
||||
HostText,
|
||||
HostComponent,
|
||||
SuspenseComponent,
|
||||
SuspenseListComponent,
|
||||
FunctionComponent,
|
||||
SimpleMemoComponent,
|
||||
ForwardRef,
|
||||
ClassComponent,
|
||||
} = workTagMap;
|
||||
try {
|
||||
let info = '';
|
||||
|
||||
if (workInProgress.tag === HostText) {
|
||||
// Text nodes never have an owner/stack because they're not created through JSX.
|
||||
// We use the parent since text nodes are always created through a host parent.
|
||||
workInProgress = (workInProgress.return: any);
|
||||
}
|
||||
|
||||
// The owner stack of the current fiber will be where it was created, i.e. inside its owner.
|
||||
// There's no actual name of the currently executing component. Instead, that is available
|
||||
// on the regular stack that's currently executing. However, for built-ins there is no such
|
||||
// named stack frame and it would be ignored as being internal anyway. Therefore we add
|
||||
// add one extra frame just to describe the "current" built-in component by name.
|
||||
// Similarly, if there is no owner at all, then there's no stack frame so we add the name
|
||||
// of the root component to the stack to know which component is currently executing.
|
||||
switch (workInProgress.tag) {
|
||||
case HostHoistable:
|
||||
case HostSingleton:
|
||||
case HostComponent:
|
||||
info += describeBuiltInComponentFrame(workInProgress.type);
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
info += describeBuiltInComponentFrame('Suspense');
|
||||
break;
|
||||
case SuspenseListComponent:
|
||||
info += describeBuiltInComponentFrame('SuspenseList');
|
||||
break;
|
||||
case FunctionComponent:
|
||||
case SimpleMemoComponent:
|
||||
case ClassComponent:
|
||||
if (!workInProgress._debugOwner && info === '') {
|
||||
// Only if we have no other data about the callsite do we add
|
||||
// the component name as the single stack frame.
|
||||
info += describeFunctionComponentFrameWithoutLineNumber(
|
||||
workInProgress.type,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ForwardRef:
|
||||
if (!workInProgress._debugOwner && info === '') {
|
||||
info += describeFunctionComponentFrameWithoutLineNumber(
|
||||
workInProgress.type.render,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
|
||||
|
||||
while (owner) {
|
||||
if (typeof owner.tag === 'number') {
|
||||
const fiber: Fiber = (owner: any);
|
||||
owner = fiber._debugOwner;
|
||||
let debugStack = fiber._debugStack;
|
||||
// If we don't actually print the stack if there is no owner of this JSX element.
|
||||
// In a real app it's typically not useful since the root app is always controlled
|
||||
// by the framework. These also tend to have noisy stacks because they're not rooted
|
||||
// in a React render but in some imperative bootstrapping code. It could be useful
|
||||
// if the element was created in module scope. E.g. hoisted. We could add a a single
|
||||
// stack frame for context for example but it doesn't say much if that's a wrapper.
|
||||
if (owner && debugStack) {
|
||||
if (typeof debugStack !== 'string') {
|
||||
debugStack = formatOwnerStack(debugStack);
|
||||
}
|
||||
if (debugStack !== '') {
|
||||
info += '\n' + debugStack;
|
||||
}
|
||||
}
|
||||
} else if (owner.debugStack != null) {
|
||||
// Server Component
|
||||
const ownerStack: Error = owner.debugStack;
|
||||
owner = owner.owner;
|
||||
if (owner && ownerStack) {
|
||||
info += '\n' + formatOwnerStack(ownerStack);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
} catch (x) {
|
||||
return '\nError generating stack: ' + x.message + '\n' + x.stack;
|
||||
}
|
||||
}
|
||||
|
||||
53
packages/react-devtools-shared/src/backend/DevToolsOwnerStack.js
vendored
Normal file
53
packages/react-devtools-shared/src/backend/DevToolsOwnerStack.js
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// This is a DevTools fork of ReactFiberOwnerStack.
|
||||
|
||||
// TODO: Make this configurable?
|
||||
const externalRegExp = /\/node\_modules\/|\(\<anonymous\>/;
|
||||
|
||||
function isNotExternal(stackFrame: string): boolean {
|
||||
return !externalRegExp.test(stackFrame);
|
||||
}
|
||||
|
||||
function filterDebugStack(error: Error): string {
|
||||
// Since stacks can be quite large and we pass a lot of them, we filter them out eagerly
|
||||
// to save bandwidth even in DEV. We'll also replay these stacks on the client so by
|
||||
// stripping them early we avoid that overhead. Otherwise we'd normally just rely on
|
||||
// the DevTools or framework's ignore lists to filter them out.
|
||||
const prevPrepareStackTrace = Error.prepareStackTrace;
|
||||
// $FlowFixMe[incompatible-type] It does accept undefined.
|
||||
Error.prepareStackTrace = undefined;
|
||||
let stack = error.stack;
|
||||
Error.prepareStackTrace = prevPrepareStackTrace;
|
||||
if (stack.startsWith('Error: react-stack-top-frame\n')) {
|
||||
// V8's default formatting prefixes with the error message which we
|
||||
// don't want/need.
|
||||
stack = stack.slice(29);
|
||||
}
|
||||
let idx = stack.indexOf('react-stack-bottom-frame');
|
||||
if (idx !== -1) {
|
||||
idx = stack.lastIndexOf('\n', idx);
|
||||
}
|
||||
if (idx !== -1) {
|
||||
// Cut off everything after the bottom frame since it'll be internals.
|
||||
stack = stack.slice(0, idx);
|
||||
} else {
|
||||
// We didn't find any internal callsite out to user space.
|
||||
// This means that this was called outside an owner or the owner is fully internal.
|
||||
// To keep things light we exclude the entire trace in this case.
|
||||
return '';
|
||||
}
|
||||
const frames = stack.split('\n').slice(1); // Pop the JSX frame.
|
||||
return frames.filter(isNotExternal).join('\n');
|
||||
}
|
||||
|
||||
export function formatOwnerStack(ownerStackTrace: Error): string {
|
||||
return filterDebugStack(ownerStackTrace);
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
import {getInternalReactConstants, getDispatcherRef} from './renderer';
|
||||
import {
|
||||
getStackByFiberInDevAndProd,
|
||||
getOwnerStackByFiberInDev,
|
||||
supportsOwnerStacks,
|
||||
supportsNativeConsoleTasks,
|
||||
} from './DevToolsFiberComponentStack';
|
||||
import {castBool, castBrowserTheme} from '../utils';
|
||||
@@ -250,11 +252,17 @@ export function patch({
|
||||
consoleSettingsRef.appendComponentStack &&
|
||||
!supportsNativeConsoleTasks(current)
|
||||
) {
|
||||
const componentStack = getStackByFiberInDevAndProd(
|
||||
workTagMap,
|
||||
current,
|
||||
(currentDispatcherRef: any),
|
||||
);
|
||||
const componentStack = supportsOwnerStacks(current)
|
||||
? getOwnerStackByFiberInDev(
|
||||
workTagMap,
|
||||
current,
|
||||
(currentDispatcherRef: any),
|
||||
)
|
||||
: getStackByFiberInDevAndProd(
|
||||
workTagMap,
|
||||
current,
|
||||
(currentDispatcherRef: any),
|
||||
);
|
||||
if (componentStack !== '') {
|
||||
// Create a fake Error so that when we print it we get native source maps. Every
|
||||
// browser will print the .stack property of the error and then parse it back for source
|
||||
|
||||
@@ -70,9 +70,14 @@ function lazyRequireFunctionExports(moduleName) {
|
||||
// If this export is a function, return a wrapper function that lazily
|
||||
// requires the implementation from the current module cache.
|
||||
if (typeof originalModule[prop] === 'function') {
|
||||
return function () {
|
||||
return jest.requireActual(moduleName)[prop].apply(this, arguments);
|
||||
};
|
||||
// eslint-disable-next-line no-eval
|
||||
const wrapper = eval(`
|
||||
(function () {
|
||||
return jest.requireActual(moduleName)[prop].apply(this, arguments);
|
||||
})
|
||||
// We use this to trick the filtering of Flight to exclude this frame.
|
||||
//# sourceURL=<anonymous>`);
|
||||
return wrapper;
|
||||
} else {
|
||||
return originalModule[prop];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user