@samitouri / QOS-React / commits / f78b2343cc

[DevTools] Recursively compute the bounding rect of the roots (#34629)

It's possible for the children to overflow the bounding rect of the root in general when they overflow in the DOM. However even when it doesn't overflow in the DOM, the bounding rect of the root can shrink while the content is suspended. In fact, it's very likely. Originally I thought we didn't need to consider this recursively because document scrolling takes absolute positioned content into account but because we're using nested overflow scrolling, we have to manually compute this.

Sebastian Markbåge committed Sep 28, 2025 at 10:15 UTC f78b2343cc1af0647110b52fcefef8d5abffaaae
1 file changed +48 -21
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
+48 -21
@@ -184,6 +184,42 @@ function getBoundingBox(rects: $ReadOnlyArray<Rect> | null): Rect {
184 };
185 }
186
187 +function computeBoundingRectRecursively(
188 + store: Store,
189 + node: SuspenseNode,
190 + bounds: {
191 + minX: number,
192 + minY: number,
193 + maxX: number,
194 + maxY: number,
195 + },
196 +): void {
197 + const rects = node.rects;
198 + if (rects !== null) {
199 + for (let j = 0; j < rects.length; j++) {
200 + const rect = rects[j];
201 + if (rect.x < bounds.minX) {
202 + bounds.minX = rect.x;
203 + }
204 + if (rect.x + rect.width > bounds.maxX) {
205 + bounds.maxX = rect.x + rect.width;
206 + }
207 + if (rect.y < bounds.minY) {
208 + bounds.minY = rect.y;
209 + }
210 + if (rect.y + rect.height > bounds.maxY) {
211 + bounds.maxY = rect.y + rect.height;
212 + }
213 + }
214 + }
215 + for (let i = 0; i < node.children.length; i++) {
216 + const child = store.getSuspenseByID(node.children[i]);
217 + if (child !== null) {
218 + computeBoundingRectRecursively(store, child, bounds);
219 + }
220 + }
221 +}
222 +
223 function getDocumentBoundingRect(
224 store: Store,
225 roots: $ReadOnlyArray<SuspenseNode['id']>,
@@ -192,10 +228,12 @@ function getDocumentBoundingRect(
228 return {x: 0, y: 0, width: 0, height: 0};
229 }
230
195 - let minX = Number.POSITIVE_INFINITY;
196 - let minY = Number.POSITIVE_INFINITY;
197 - let maxX = Number.NEGATIVE_INFINITY;
198 - let maxY = Number.NEGATIVE_INFINITY;
231 + const bounds = {
232 + minX: Number.POSITIVE_INFINITY,
233 + minY: Number.POSITIVE_INFINITY,
234 + maxX: Number.NEGATIVE_INFINITY,
235 + maxY: Number.NEGATIVE_INFINITY,
236 + };
237
238 for (let i = 0; i < roots.length; i++) {
239 const rootID = roots[i];
@@ -203,30 +241,19 @@ function getDocumentBoundingRect(
241 if (root === null) {
242 continue;
243 }
206 -
207 - const rects = root.rects;
208 - if (rects === null) {
209 - continue;
210 - }
211 - for (let j = 0; j < rects.length; j++) {
212 - const rect = rects[j];
213 - minX = Math.min(minX, rect.x);
214 - minY = Math.min(minY, rect.y);
215 - maxX = Math.max(maxX, rect.x + rect.width);
216 - maxY = Math.max(maxY, rect.y + rect.height);
217 - }
244 + computeBoundingRectRecursively(store, root, bounds);
245 }
246
220 - if (minX === Number.POSITIVE_INFINITY) {
247 + if (bounds.minX === Number.POSITIVE_INFINITY) {
248 // No rects found, return empty rect
249 return {x: 0, y: 0, width: 0, height: 0};
250 }
251
252 return {
226 - x: minX,
227 - y: minY,
228 - width: maxX - minX,
229 - height: maxY - minY,
253 + x: bounds.minX,
254 + y: bounds.minY,
255 + width: bounds.maxX - bounds.minX,
256 + height: bounds.maxY - bounds.minY,
257 };
258 }
259