689
value: null,
690
});
691
if (enableOwnerStacks) {
692
+ let normalizedStackTrace: null | Error = null;
693
+ if (stack !== null) {
694
+ // We create a fake stack and then create an Error object inside of it.
695
+ // This means that the stack trace is now normalized into the native format
696
+ // of the browser and the stack frames will have been registered with
697
+ // source mapping information.
698
+ // This can unfortunately happen within a user space callstack which will
699
+ // remain on the stack.
700
+ normalizedStackTrace = createFakeJSXCallStackInDEV(response, stack);
701
+ }
702
Object.defineProperty(element, '_debugStack', {
703
configurable: false,
704
enumerable: false,
705
writable: true,
696
- value: stack,
706
+ value: normalizedStackTrace,
707
});
708
709
let task: null | ConsoleTask = null;
734
writable: true,
735
value: task,
736
});
737
+
738
+ // This owner should ideally have already been initialized to avoid getting
739
+ // user stack frames on the stack.
740
+ if (owner !== null) {
741
+ initializeFakeStack(response, owner);
742
+ }
743
}
744
}
745
768
};
769
if (enableOwnerStacks) {
770
// $FlowFixMe[cannot-write]
755
- erroredComponent.stack = element._debugStack;
771
+ erroredComponent.debugStack = element._debugStack;
772
// $FlowFixMe[cannot-write]
757
- erroredComponent.task = element._debugTask;
773
+ erroredComponent.debugTask = element._debugTask;
774
}
775
erroredChunk._debugInfo = [erroredComponent];
776
}
931
};
932
if (enableOwnerStacks) {
933
// $FlowFixMe[cannot-write]
918
- erroredComponent.stack = element._debugStack;
934
+ erroredComponent.debugStack = element._debugStack;
935
// $FlowFixMe[cannot-write]
920
- erroredComponent.task = element._debugTask;
936
+ erroredComponent.debugTask = element._debugTask;
937
}
938
const chunkDebugInfo: ReactDebugInfo =
939
chunk._debugInfo || (chunk._debugInfo = []);
2017
response: Response,
2018
debugInfo: ReactComponentInfo | ReactAsyncInfo,
2019
): null | ConsoleTask {
2004
- if (!supportsCreateTask || typeof debugInfo.stack !== 'string') {
2020
+ if (!supportsCreateTask) {
2021
return null;
2022
}
2023
const componentInfo: ReactComponentInfo = (debugInfo: any); // Refined
2008
- const stack: string = debugInfo.stack;
2009
- const cachedEntry = componentInfo.task;
2024
+ const cachedEntry = componentInfo.debugTask;
2025
if (cachedEntry !== undefined) {
2026
return cachedEntry;
2027
}
2028
2029
+ if (typeof debugInfo.stack !== 'string') {
2030
+ // If this is an error, we should've really already initialized the task.
2031
+ // If it's null, we can't initialize a task.
2032
+ return null;
2033
+ }
2034
+
2035
+ const stack = debugInfo.stack;
2036
+
2037
const ownerTask =
2038
componentInfo.owner == null
2039
? null
2057
componentTask = ownerTask.run(callStack);
2058
}
2059
// $FlowFixMe[cannot-write]: We consider this part of initialization.
2037
- componentInfo.task = componentTask;
2060
+ componentInfo.debugTask = componentTask;
2061
return componentTask;
2062
}
2063
2064
+const createFakeJSXCallStack = {
2065
+ 'react-stack-bottom-frame': function (
2066
+ response: Response,
2067
+ stack: string,
2068
+ ): Error {
2069
+ const callStackForError = buildFakeCallStack(
2070
+ response,
2071
+ stack,
2072
+ fakeJSXCallSite,
2073
+ );
2074
+ return callStackForError();
2075
+ },
2076
+};
2077
+
2078
+const createFakeJSXCallStackInDEV: (
2079
+ response: Response,
2080
+ stack: string,
2081
+) => Error = __DEV__
2082
+ ? // We use this technique to trick minifiers to preserve the function name.
2083
+ (createFakeJSXCallStack['react-stack-bottom-frame'].bind(
2084
+ createFakeJSXCallStack,
2085
+ ): any)
2086
+ : (null: any);
2087
+
2088
+/** @noinline */
2089
+function fakeJSXCallSite() {
2090
+ // This extra call frame represents the JSX creation function. We always pop this frame
2091
+ // off before presenting so it needs to be part of the stack.
2092
+ return new Error('react-stack-top-frame');
2093
+}
2094
+
2095
+function initializeFakeStack(
2096
+ response: Response,
2097
+ debugInfo: ReactComponentInfo | ReactAsyncInfo,
2098
+): void {
2099
+ const cachedEntry = debugInfo.debugStack;
2100
+ if (cachedEntry !== undefined) {
2101
+ return;
2102
+ }
2103
+ if (typeof debugInfo.stack === 'string') {
2104
+ // $FlowFixMe[cannot-write]
2105
+ // $FlowFixMe[prop-missing]
2106
+ debugInfo.debugStack = createFakeJSXCallStackInDEV(
2107
+ response,
2108
+ debugInfo.stack,
2109
+ );
2110
+ }
2111
+ if (debugInfo.owner != null) {
2112
+ // Initialize any owners not yet initialized.
2113
+ initializeFakeStack(response, debugInfo.owner);
2114
+ }
2115
+}
2116
+
2117
function resolveDebugInfo(
2118
response: Response,
2119
id: number,
2130
// render phase so we're not inside a user space stack at this point. If we waited
2131
// to initialize it when we need it, we might be inside user code.
2132
initializeFakeTask(response, debugInfo);
2133
+ initializeFakeStack(response, debugInfo);
2134
+
2135
const chunk = getChunk(response, id);
2136
const chunkDebugInfo: ReactDebugInfo =
2137
chunk._debugInfo || (chunk._debugInfo = []);
2174
);
2175
if (owner != null) {
2176
const task = initializeFakeTask(response, owner);
2177
+ initializeFakeStack(response, owner);
2178
if (task !== null) {
2179
task.run(callStack);
2180
return;