diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index c1172bfbe1267..a45f510ff230e 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -1647,102 +1647,100 @@ export function attach( } function mountFiberRecursively( - fiber: Fiber, + firstChild: Fiber, parentFiber: Fiber | null, traverseSiblings: boolean, traceNearestHostComponentUpdate: boolean, ) { - if (__DEBUG__) { - debug('mountFiberRecursively()', fiber, parentFiber); - } + // Iterate over siblings rather than recursing. + // This reduces the chance of stack overflow for wide trees (e.g. lists with many items). + let fiber: Fiber | null = firstChild; + while (fiber !== null) { + if (__DEBUG__) { + debug('mountFiberRecursively()', fiber, parentFiber); + } - // If we have the tree selection from previous reload, try to match this Fiber. - // Also remember whether to do the same for siblings. - const mightSiblingsBeOnTrackedPath = updateTrackedPathStateBeforeMount( - fiber, - ); + // If we have the tree selection from previous reload, try to match this Fiber. + // Also remember whether to do the same for siblings. + const mightSiblingsBeOnTrackedPath = updateTrackedPathStateBeforeMount( + fiber, + ); - const shouldIncludeInTree = !shouldFilterFiber(fiber); - if (shouldIncludeInTree) { - recordMount(fiber, parentFiber); - } + const shouldIncludeInTree = !shouldFilterFiber(fiber); + if (shouldIncludeInTree) { + recordMount(fiber, parentFiber); + } - if (traceUpdatesEnabled) { - if (traceNearestHostComponentUpdate) { - const elementType = getElementTypeForFiber(fiber); - // If an ancestor updated, we should mark the nearest host nodes for highlighting. - if (elementType === ElementTypeHostComponent) { - traceUpdatesForNodes.add(fiber.stateNode); - traceNearestHostComponentUpdate = false; + if (traceUpdatesEnabled) { + if (traceNearestHostComponentUpdate) { + const elementType = getElementTypeForFiber(fiber); + // If an ancestor updated, we should mark the nearest host nodes for highlighting. + if (elementType === ElementTypeHostComponent) { + traceUpdatesForNodes.add(fiber.stateNode); + traceNearestHostComponentUpdate = false; + } } - } - // We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch, - // because we don't want to highlight every host node inside of a newly mounted subtree. - } + // We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch, + // because we don't want to highlight every host node inside of a newly mounted subtree. + } - const isSuspense = fiber.tag === ReactTypeOfWork.SuspenseComponent; - if (isSuspense) { - const isTimedOut = fiber.memoizedState !== null; - if (isTimedOut) { - // Special case: if Suspense mounts in a timed-out state, - // get the fallback child from the inner fragment and mount - // it as if it was our own child. Updates handle this too. - const primaryChildFragment = fiber.child; - const fallbackChildFragment = primaryChildFragment - ? primaryChildFragment.sibling - : null; - const fallbackChild = fallbackChildFragment - ? fallbackChildFragment.child - : null; - if (fallbackChild !== null) { - mountFiberRecursively( - fallbackChild, - shouldIncludeInTree ? fiber : parentFiber, - true, - traceNearestHostComponentUpdate, - ); + const isSuspense = fiber.tag === ReactTypeOfWork.SuspenseComponent; + if (isSuspense) { + const isTimedOut = fiber.memoizedState !== null; + if (isTimedOut) { + // Special case: if Suspense mounts in a timed-out state, + // get the fallback child from the inner fragment and mount + // it as if it was our own child. Updates handle this too. + const primaryChildFragment = fiber.child; + const fallbackChildFragment = primaryChildFragment + ? primaryChildFragment.sibling + : null; + const fallbackChild = fallbackChildFragment + ? fallbackChildFragment.child + : null; + if (fallbackChild !== null) { + mountFiberRecursively( + fallbackChild, + shouldIncludeInTree ? fiber : parentFiber, + true, + traceNearestHostComponentUpdate, + ); + } + } else { + let primaryChild: Fiber | null = null; + const areSuspenseChildrenConditionallyWrapped = + OffscreenComponent === -1; + if (areSuspenseChildrenConditionallyWrapped) { + primaryChild = fiber.child; + } else if (fiber.child !== null) { + primaryChild = fiber.child.child; + } + if (primaryChild !== null) { + mountFiberRecursively( + primaryChild, + shouldIncludeInTree ? fiber : parentFiber, + true, + traceNearestHostComponentUpdate, + ); + } } } else { - let primaryChild: Fiber | null = null; - const areSuspenseChildrenConditionallyWrapped = - OffscreenComponent === -1; - if (areSuspenseChildrenConditionallyWrapped) { - primaryChild = fiber.child; - } else if (fiber.child !== null) { - primaryChild = fiber.child.child; - } - if (primaryChild !== null) { + if (fiber.child !== null) { mountFiberRecursively( - primaryChild, + fiber.child, shouldIncludeInTree ? fiber : parentFiber, true, traceNearestHostComponentUpdate, ); } } - } else { - if (fiber.child !== null) { - mountFiberRecursively( - fiber.child, - shouldIncludeInTree ? fiber : parentFiber, - true, - traceNearestHostComponentUpdate, - ); - } - } - // We're exiting this Fiber now, and entering its siblings. - // If we have selection to restore, we might need to re-activate tracking. - updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath); + // We're exiting this Fiber now, and entering its siblings. + // If we have selection to restore, we might need to re-activate tracking. + updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath); - if (traverseSiblings && fiber.sibling !== null) { - mountFiberRecursively( - fiber.sibling, - parentFiber, - true, - traceNearestHostComponentUpdate, - ); + fiber = traverseSiblings ? fiber.sibling : null; } }