Skip to content

Commit ea6e059

Browse files
authored
[Fiber] Enable Native console.createTask Stacks When Available (#29223)
Stacked on #29206 and #29221. This disables appending owner stacks to console when `console.createTask` is available in the environment. Instead we rely on native "async" stacks that end up looking like this with source maps and ignore list enabled. <img width="673" alt="Screenshot 2024-05-22 at 4 00 27 PM" src="https://github.com/facebook/react/assets/63648/5313ed53-b298-4386-8f76-8eb85bdfbbc7"> Unfortunately Chrome requires a string name for each async stack and, worse, a suffix of `(async)` is automatically added which is very confusing since it seems like it might be an async component or something which it is not. In this case it's not so bad because it's nice to refer to the host component which otherwise doesn't have a stack frame since it's internal. However, if there were more owners here there would also be a `<Counter> (async)` which ends up being kind of duplicative. If the Chrome DevTools is not open from the start of the app, then `console.createTask` is disabled and so you lose the stack for those errors (or those parents if the devtools is opened later). Unlike our appended ones that are always added. That's unfortunate and likely to be a bit of a DX issue but it's also nice that it saves on perf in DEV mode for those cases. Framework dialogs can still surface the stack since we also track it in user space in parallel. This currently doesn't track Server Components yet. We need a more clever hack for that part in a follow up. I think I probably need to also add something to React DevTools to disable its stacks for this case too. Since it looks for stacks in the console.error and adds a stack otherwise. Since we don't add them anymore from the runtime, the DevTools adds them instead.
1 parent b078c81 commit ea6e059

File tree

4 files changed

+30
-3
lines changed

4 files changed

+30
-3
lines changed

packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,10 @@ export function getStackByFiberInDevAndProd(
9898
return '\nError generating stack: ' + x.message + '\n' + x.stack;
9999
}
100100
}
101+
102+
export function supportsNativeConsoleTasks(fiber: Fiber): boolean {
103+
// If this Fiber supports native console.createTask then we are already running
104+
// inside a native async stack trace if it's active - meaning the DevTools is open.
105+
// Ideally we'd detect if this task was created while the DevTools was open or not.
106+
return !!fiber._debugTask;
107+
}

packages/react-devtools-shared/src/backend/console.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import type {
1818
import {format, formatWithStyles} from './utils';
1919

2020
import {getInternalReactConstants, getDispatcherRef} from './renderer';
21-
import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
21+
import {
22+
getStackByFiberInDevAndProd,
23+
supportsNativeConsoleTasks,
24+
} from './DevToolsFiberComponentStack';
2225
import {consoleManagedByDevToolsDuringStrictMode} from 'react-devtools-feature-flags';
2326
import {castBool, castBrowserTheme} from '../utils';
2427

@@ -235,7 +238,10 @@ export function patch({
235238
}
236239
}
237240

238-
if (shouldAppendWarningStack) {
241+
if (
242+
shouldAppendWarningStack &&
243+
!supportsNativeConsoleTasks(current)
244+
) {
239245
const componentStack = getStackByFiberInDevAndProd(
240246
workTagMap,
241247
current,

packages/react-reconciler/src/ReactCurrentFiber.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ export function runWithFiberInDEV<A0, A1, A2, A3, A4, T>(
7474
const previousFiber = current;
7575
setCurrentFiber(fiber);
7676
try {
77+
if (enableOwnerStacks) {
78+
if (fiber !== null && fiber._debugTask) {
79+
return fiber._debugTask.run(
80+
callback.bind(null, arg0, arg1, arg2, arg3, arg4),
81+
);
82+
}
83+
}
7784
return callback(arg0, arg1, arg2, arg3, arg4);
7885
} finally {
7986
current = previousFiber;

packages/shared/consoleWithStackDev.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import ReactSharedInternals from 'shared/ReactSharedInternals';
9+
import {enableOwnerStacks} from 'shared/ReactFeatureFlags';
910

1011
let suppressWarning = false;
1112
export function setSuppressWarning(newSuppressWarning) {
@@ -36,14 +37,20 @@ export function error(format, ...args) {
3637
}
3738
}
3839

40+
// eslint-disable-next-line react-internal/no-production-logging
41+
const supportsCreateTask = __DEV__ && enableOwnerStacks && !!console.createTask;
42+
3943
function printWarning(level, format, args) {
4044
// When changing this logic, you might want to also
4145
// update consoleWithStackDev.www.js as well.
4246
if (__DEV__) {
4347
const isErrorLogger =
4448
format === '%s\n\n%s\n' || format === '%o\n\n%s\n\n%s\n';
4549

46-
if (ReactSharedInternals.getCurrentStack) {
50+
if (!supportsCreateTask && ReactSharedInternals.getCurrentStack) {
51+
// We only add the current stack to the console when createTask is not supported.
52+
// Since createTask requires DevTools to be open to work, this means that stacks
53+
// can be lost while DevTools isn't open but we can't detect this.
4754
const stack = ReactSharedInternals.getCurrentStack();
4855
if (stack !== '') {
4956
format += '%s';

0 commit comments

Comments
 (0)