[DevTools] Only show Suspense rects matching "unique-suspenders-only" filter (#34607)

This commit is contained in:
Sebastian "Sebbie" Silbermann 2025-09-26 17:29:15 +02:00 committed by GitHub
parent 6a51a9fea6
commit 8d557a638e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 10 deletions

View File

@ -19,6 +19,10 @@
overflow: hidden;
}
.SuspenseRectsScaledRect[data-visible='false'] > .SuspenseRectsBoundaryChildren {
overflow: initial;
}
.SuspenseRectsRect {
box-shadow: var(--elevation-4);
pointer-events: all;
@ -31,6 +35,11 @@
outline-color: var(--color-background-selected);
}
.SuspenseRectsScaledRect[data-visible='false'] {
pointer-events: none;
outline-width: 0;
}
/* highlight this boundary */
.SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect, .SuspenseRectsBoundary[data-highlighted='true'] > .SuspenseRectsRect {
background-color: var(--color-background-hover);

View File

@ -34,10 +34,12 @@ import {
function ScaledRect({
className,
rect,
visible,
...props
}: {
className: string,
rect: Rect,
visible: boolean,
...
}): React$Node {
const viewBox = useContext(ViewBox);
@ -50,6 +52,7 @@ function ScaledRect({
<div
{...props}
className={styles.SuspenseRectsScaledRect + ' ' + className}
data-visible={visible}
style={{
width,
height,
@ -68,6 +71,7 @@ function SuspenseRects({
const store = useContext(StoreContext);
const treeDispatch = useContext(TreeDispatcherContext);
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
const {uniqueSuspendersOnly} = useContext(SuspenseTreeStateContext);
const {inspectedElementID} = useContext(TreeStateContext);
@ -79,6 +83,7 @@ function SuspenseRects({
// getSuspenseByID will have already warned
return null;
}
const visible = suspense.hasUniqueSuspenders || !uniqueSuspendersOnly;
function handleClick(event: SyntheticMouseEvent) {
if (event.defaultPrevented) {
@ -117,9 +122,13 @@ function SuspenseRects({
const boundingBox = getBoundingBox(suspense.rects);
return (
<ScaledRect rect={boundingBox} className={styles.SuspenseRectsBoundary}>
<ScaledRect
rect={boundingBox}
className={styles.SuspenseRectsBoundary}
visible={visible}>
<ViewBox.Provider value={boundingBox}>
{suspense.rects !== null &&
{visible &&
suspense.rects !== null &&
suspense.rects.map((rect, index) => {
return (
<ScaledRect
@ -245,6 +254,7 @@ function SuspenseRectsContainer(): React$Node {
// TODO: This relies on a full re-render of all children when the Suspense tree changes.
const {roots} = useContext(SuspenseTreeStateContext);
// TODO: bbox does not consider uniqueSuspendersOnly filter
const boundingBox = getDocumentBoundingRect(store, roots);
const boundingBoxWidth = boundingBox.width;

View File

@ -184,8 +184,12 @@ function EmptySuspense() {
// $FlowFixMe[missing-local-annot]
function PrimaryFallbackTest({initialSuspend}) {
const [suspend, setSuspend] = useState(initialSuspend);
const fallbackStep = useTestSequence('fallback', Fallback1, Fallback2);
const primaryStep = useTestSequence('primary', Primary1, Primary2);
const [fallbackStepIndex, fallbackStep] = useTestSequence(
'fallback',
Fallback1,
Fallback2,
);
const [, primaryStep] = useTestSequence('primary', Primary1, Primary2);
return (
<Fragment>
<label>
@ -198,7 +202,11 @@ function PrimaryFallbackTest({initialSuspend}) {
</label>
<br />
<Suspense fallback={fallbackStep}>
{suspend ? <Never /> : primaryStep}
{suspend ? (
<Never id={`primary-fallback-test-${fallbackStepIndex}`} />
) : (
primaryStep
)}
</Suspense>
</Fragment>
);
@ -227,7 +235,7 @@ function useTestSequence(label: string, T1: any => any, T2: any => any) {
{next} <T2 prop={step}>goodbye</T2>
</Fragment>,
];
return allSteps[step];
return [step, allSteps[step]];
}
function NestedSuspenseTest() {
@ -252,7 +260,7 @@ function Parent() {
</Suspense>
<br />
<Suspense fallback={<Fallback1>This will never load</Fallback1>}>
<Never />
<Never id="parent-never" />
</Suspense>
<br />
<b>
@ -298,14 +306,48 @@ function LoadLater() {
Loaded! Click to suspend again.
</Primary1>
) : (
<Never />
<Never id="load-later" />
)}
</Suspense>
);
}
function Never() {
throw new Promise(resolve => {});
function readRecord(promise: any): any {
if (typeof React.use === 'function') {
return React.use(promise);
}
switch (promise.status) {
case 'pending':
throw promise;
case 'rejected':
throw promise.reason;
case 'fulfilled':
return promise.value;
default:
promise.status = 'pending';
promise.then(
value => {
promise.status = 'fulfilled';
promise.value = value;
},
reason => {
promise.status = 'rejected';
promise.reason = reason;
},
);
throw promise;
}
}
const nevers = new Map<string, Promise<empty>>();
function Never({id}: {id: string}) {
let promise = nevers.get(id);
if (!promise) {
promise = new Promise(() => {});
(promise as any).displayName = id;
nevers.set(id, promise);
}
readRecord(promise);
}
function Fallback1({prop, ...rest}: any) {