@samitouri / QOS-React-1 / commits / 5a970933c0

fix[devtools]: feature-check structure stack trace methods (#35293)

`Error.prepareStackTrace` is non-standard feature and not all JavaScript runtimes implement the methods that we are using in React DevTools backend. This PR adds additional checks for the presence of the methods that we are using.

Ruslan Lesiutin committed Dec 10, 2025 at 19:21 UTC 5a970933c0aa5c5cfb9793cce49f3a282b191716
1 file changed +47 -13
packages/react-devtools-shared/src/backend/utils/parseStackTrace.js
+47 -13
@@ -154,41 +154,73 @@ function collectStackTrace(
154 // We mirror how V8 serializes stack frames and how we later parse them.
155 for (let i = framesToSkip; i < structuredStackTrace.length; i++) {
156 const callSite = structuredStackTrace[i];
157 - let name = callSite.getFunctionName() || '<anonymous>';
157 + let name =
158 + // $FlowFixMe[method-unbinding]
159 + typeof callSite.getFunctionName === 'function'
160 + ? callSite.getFunctionName() || '<anonymous>'
161 + : '';
162 if (
163 name.includes('react_stack_bottom_frame') ||
164 name.includes('react-stack-bottom-frame')
165 ) {
166 // Skip everything after the bottom frame since it'll be internals.
167 break;
164 - } else if (callSite.isNative()) {
165 - // $FlowFixMe[prop-missing]
166 - const isAsync = callSite.isAsync();
168 + // $FlowFixMe[method-unbinding]
169 + } else if (typeof callSite.isNative === 'function' && callSite.isNative()) {
170 + const isAsync =
171 + // $FlowFixMe[prop-missing]
172 + // $FlowFixMe[incompatible-use]
173 + typeof callSite.isAsync === 'function' && callSite.isAsync();
174 result.push([name, '', 0, 0, 0, 0, isAsync]);
175 } else {
176 // We encode complex function calls as if they're part of the function
177 // name since we cannot simulate the complex ones and they look the same
178 // as function names in UIs on the client as well as stacks.
172 - if (callSite.isConstructor()) {
179 + if (
180 + // $FlowFixMe[method-unbinding]
181 + typeof callSite.isConstructor === 'function' &&
182 + callSite.isConstructor()
183 + ) {
184 name = 'new ' + name;
174 - } else if (!callSite.isToplevel()) {
185 + } else if (
186 + // $FlowFixMe[method-unbinding]
187 + typeof callSite.isToplevel === 'function' &&
188 + !callSite.isToplevel()
189 + ) {
190 name = getMethodCallName(callSite);
191 }
192 if (name === '<anonymous>') {
193 name = '';
194 }
180 - let filename = callSite.getScriptNameOrSourceURL() || '<anonymous>';
195 + let filename =
196 + // $FlowFixMe[method-unbinding]
197 + typeof callSite.getScriptNameOrSourceURL === 'function'
198 + ? callSite.getScriptNameOrSourceURL() || '<anonymous>'
199 + : '';
200 if (filename === '<anonymous>') {
201 filename = '';
183 - if (callSite.isEval()) {
184 - const origin = callSite.getEvalOrigin();
202 + // $FlowFixMe[method-unbinding]
203 + if (typeof callSite.isEval === 'function' && callSite.isEval()) {
204 + const origin =
205 + // $FlowFixMe[method-unbinding]
206 + typeof callSite.getEvalOrigin === 'function'
207 + ? callSite.getEvalOrigin()
208 + : null;
209 if (origin) {
210 filename = origin.toString() + ', <anonymous>';
211 }
212 }
213 }
190 - const line = callSite.getLineNumber() || 0;
191 - const col = callSite.getColumnNumber() || 0;
214 + const line =
215 + // $FlowFixMe[method-unbinding]
216 + (typeof callSite.getLineNumber === 'function' &&
217 + callSite.getLineNumber()) ||
218 + 0;
219 + const col =
220 + // $FlowFixMe[method-unbinding]
221 + (typeof callSite.getColumnNumber === 'function' &&
222 + callSite.getColumnNumber()) ||
223 + 0;
224 const enclosingLine: number =
225 // $FlowFixMe[prop-missing]
226 typeof callSite.getEnclosingLineNumber === 'function'
@@ -199,8 +231,10 @@ function collectStackTrace(
231 typeof callSite.getEnclosingColumnNumber === 'function'
232 ? (callSite: any).getEnclosingColumnNumber() || 0
233 : 0;
202 - // $FlowFixMe[prop-missing]
203 - const isAsync = callSite.isAsync();
234 + const isAsync =
235 + // $FlowFixMe[prop-missing]
236 + // $FlowFixMe[incompatible-use]
237 + typeof callSite.isAsync === 'function' && callSite.isAsync();
238 result.push([
239 name,
240 filename,