From 5d801243459cc18a768c9b291355943366730539 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:21:28 +0000 Subject: [PATCH 1/2] fix[devtools]: still show overlay, if getClientRects is not implemented (#35294) Follow-up to https://github.com/facebook/react/pull/34653. React Native doesn't implement `getClientRect`, since this is applicable to CSS box, which is not a concept for Native (maybe yet). I am loosening the condition that gates `showOverlay()` call to pass if `getClientRect` is not implemented. Conceptually, everything that is inside `react-devtools-shared/backend` should be Host-agnostic, because both on Web and Native it is installed inside the Host JavaScript runtime, be it main frame of the page, or RN instance. Since overlay & highlighting logic also lives there, it should also follow these principles. --- .../src/backend/views/Highlighter/index.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js index 34a81f36c6e..f1711e02c3f 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js @@ -202,13 +202,12 @@ export default function setupHighlighter( typeof node.getClientRects === 'function' ? node.getClientRects() : []; - // If this is currently display: none, then try another node. - // This can happen when one of the host instances is a hoistable. if ( - nodeRects.length > 0 && - (nodeRects.length > 2 || - nodeRects[0].width > 0 || - nodeRects[0].height > 0) + typeof node.getClientRects === 'undefined' || // If Host doesn't implement getClientRects, try to show the overlay. + (nodeRects.length > 0 && // If this is currently display: none, then try another node. + (nodeRects.length > 2 || // This can happen when one of the host instances is a hoistable. + nodeRects[0].width > 0 || + nodeRects[0].height > 0)) ) { // $FlowFixMe[method-unbinding] if (scrollIntoView && typeof node.scrollIntoView === 'function') { From 5a970933c0aa5c5cfb9793cce49f3a282b191716 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:21:54 +0000 Subject: [PATCH 2/2] 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. --- .../src/backend/utils/parseStackTrace.js | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js b/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js index 300bfbebcb3..e18c2d7baa1 100644 --- a/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js +++ b/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js @@ -154,41 +154,73 @@ function collectStackTrace( // We mirror how V8 serializes stack frames and how we later parse them. for (let i = framesToSkip; i < structuredStackTrace.length; i++) { const callSite = structuredStackTrace[i]; - let name = callSite.getFunctionName() || ''; + let name = + // $FlowFixMe[method-unbinding] + typeof callSite.getFunctionName === 'function' + ? callSite.getFunctionName() || '' + : ''; if ( name.includes('react_stack_bottom_frame') || name.includes('react-stack-bottom-frame') ) { // Skip everything after the bottom frame since it'll be internals. break; - } else if (callSite.isNative()) { - // $FlowFixMe[prop-missing] - const isAsync = callSite.isAsync(); + // $FlowFixMe[method-unbinding] + } else if (typeof callSite.isNative === 'function' && callSite.isNative()) { + const isAsync = + // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-use] + typeof callSite.isAsync === 'function' && callSite.isAsync(); result.push([name, '', 0, 0, 0, 0, isAsync]); } else { // We encode complex function calls as if they're part of the function // name since we cannot simulate the complex ones and they look the same // as function names in UIs on the client as well as stacks. - if (callSite.isConstructor()) { + if ( + // $FlowFixMe[method-unbinding] + typeof callSite.isConstructor === 'function' && + callSite.isConstructor() + ) { name = 'new ' + name; - } else if (!callSite.isToplevel()) { + } else if ( + // $FlowFixMe[method-unbinding] + typeof callSite.isToplevel === 'function' && + !callSite.isToplevel() + ) { name = getMethodCallName(callSite); } if (name === '') { name = ''; } - let filename = callSite.getScriptNameOrSourceURL() || ''; + let filename = + // $FlowFixMe[method-unbinding] + typeof callSite.getScriptNameOrSourceURL === 'function' + ? callSite.getScriptNameOrSourceURL() || '' + : ''; if (filename === '') { filename = ''; - if (callSite.isEval()) { - const origin = callSite.getEvalOrigin(); + // $FlowFixMe[method-unbinding] + if (typeof callSite.isEval === 'function' && callSite.isEval()) { + const origin = + // $FlowFixMe[method-unbinding] + typeof callSite.getEvalOrigin === 'function' + ? callSite.getEvalOrigin() + : null; if (origin) { filename = origin.toString() + ', '; } } } - const line = callSite.getLineNumber() || 0; - const col = callSite.getColumnNumber() || 0; + const line = + // $FlowFixMe[method-unbinding] + (typeof callSite.getLineNumber === 'function' && + callSite.getLineNumber()) || + 0; + const col = + // $FlowFixMe[method-unbinding] + (typeof callSite.getColumnNumber === 'function' && + callSite.getColumnNumber()) || + 0; const enclosingLine: number = // $FlowFixMe[prop-missing] typeof callSite.getEnclosingLineNumber === 'function' @@ -199,8 +231,10 @@ function collectStackTrace( typeof callSite.getEnclosingColumnNumber === 'function' ? (callSite: any).getEnclosingColumnNumber() || 0 : 0; - // $FlowFixMe[prop-missing] - const isAsync = callSite.isAsync(); + const isAsync = + // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-use] + typeof callSite.isAsync === 'function' && callSite.isAsync(); result.push([ name, filename,