@@ -11,6 +11,7 @@ import Agent from 'react-devtools-shared/src/backend/agent';
1111import { hideOverlay , showOverlay } from './Highlighter' ;
1212
1313import type { BackendBridge } from 'react-devtools-shared/src/bridge' ;
14+ import type { RendererInterface } from '../../types' ;
1415
1516// This plug-in provides in-page highlighting of the selected element.
1617// It is used by the browser extension and the standalone DevTools shell (when connected to a browser).
@@ -133,6 +134,10 @@ export default function setupHighlighter(
133134 ) {
134135 // $FlowFixMe[method-unbinding]
135136 if ( scrollIntoView && typeof node . scrollIntoView === 'function' ) {
137+ if ( scrollDelayTimer ) {
138+ clearTimeout ( scrollDelayTimer ) ;
139+ scrollDelayTimer = null ;
140+ }
136141 // If the node isn't visible show it before highlighting it.
137142 // We may want to reconsider this; it might be a little disruptive.
138143 node . scrollIntoView ( { block : 'nearest' , inline : 'nearest' } ) ;
@@ -152,29 +157,10 @@ export default function setupHighlighter(
152157 hideOverlay ( agent ) ;
153158 }
154159
155- function scrollToHostInstance ( {
156- id ,
157- rendererID ,
158- } : {
160+ function attemptScrollToHostInstance (
161+ renderer : RendererInterface ,
159162 id : number ,
160- rendererID : number ,
161- } ) {
162- // Always hide the existing overlay so it doesn't obscure the element.
163- // If you wanted to show the overlay, highlightHostInstance should be used instead
164- // with the scrollIntoView option.
165- hideOverlay ( agent ) ;
166-
167- const renderer = agent . rendererInterfaces [ rendererID ] ;
168- if ( renderer == null ) {
169- console . warn ( `Invalid renderer id "${ rendererID } " for element "${ id } "` ) ;
170- return ;
171- }
172-
173- // In some cases fiber may already be unmounted
174- if ( ! renderer . hasElementWithId ( id ) ) {
175- return ;
176- }
177-
163+ ) {
178164 const nodes = renderer . findHostInstancesForElementID ( id ) ;
179165 if ( nodes != null ) {
180166 for ( let i = 0 ; i < nodes . length ; i ++ ) {
@@ -202,11 +188,47 @@ export default function setupHighlighter(
202188 inline : 'nearest' ,
203189 behavior : 'smooth' ,
204190 } ) ;
205- return ;
191+ return true ;
206192 }
207193 }
208194 }
209195 }
196+ return false ;
197+ }
198+
199+ let scrollDelayTimer = null ;
200+ function scrollToHostInstance ( {
201+ id,
202+ rendererID,
203+ } : {
204+ id : number ,
205+ rendererID : number ,
206+ } ) {
207+ // Always hide the existing overlay so it doesn't obscure the element.
208+ // If you wanted to show the overlay, highlightHostInstance should be used instead
209+ // with the scrollIntoView option.
210+ hideOverlay ( agent ) ;
211+
212+ if ( scrollDelayTimer ) {
213+ clearTimeout ( scrollDelayTimer ) ;
214+ scrollDelayTimer = null ;
215+ }
216+
217+ const renderer = agent . rendererInterfaces [ rendererID ] ;
218+ if ( renderer == null ) {
219+ console . warn ( `Invalid renderer id "${ rendererID } " for element "${ id } "` ) ;
220+ return ;
221+ }
222+
223+ // In some cases fiber may already be unmounted
224+ if ( ! renderer . hasElementWithId ( id ) ) {
225+ return ;
226+ }
227+
228+ if ( attemptScrollToHostInstance ( renderer , id ) ) {
229+ return ;
230+ }
231+
210232 // It's possible that the current state of a Suspense boundary doesn't have a position
211233 // in the tree. E.g. because it's not yet mounted in the state we're moving to.
212234 // Such as if it's in a null tree or inside another boundary's hidden state.
@@ -229,6 +251,12 @@ export default function setupHighlighter(
229251 left : x ,
230252 behavior : 'smooth' ,
231253 } ) ;
254+ // It's possible that after mount, we're able to scroll deeper once the new nodes
255+ // have mounted. Let's try again after mount. Ideally we'd know which commit this
256+ // is going to be but for now we just try after 100ms.
257+ scrollDelayTimer = setTimeout ( ( ) => {
258+ attemptScrollToHostInstance ( renderer , id ) ;
259+ } , 100 ) ;
232260 }
233261 }
234262
0 commit comments