@@ -88,6 +88,7 @@ import {
8888 SUSPENSE_TREE_OPERATION_REMOVE ,
8989 SUSPENSE_TREE_OPERATION_REORDER_CHILDREN ,
9090 SUSPENSE_TREE_OPERATION_RESIZE ,
91+ SUSPENSE_TREE_OPERATION_SUSPENDERS ,
9192 UNKNOWN_SUSPENDERS_NONE ,
9293 UNKNOWN_SUSPENDERS_REASON_PRODUCTION ,
9394 UNKNOWN_SUSPENDERS_REASON_OLD_VERSION ,
@@ -2016,6 +2017,7 @@ export function attach(
20162017 const pendingOperations: OperationsArray = [];
20172018 const pendingRealUnmountedIDs: Array<FiberInstance['id']> = [];
20182019 const pendingRealUnmountedSuspenseIDs: Array<FiberInstance['id']> = [];
2020+ const pendingSuspenderChanges: Set<FiberInstance['id']> = new Set();
20192021 let pendingOperationsQueue: Array<OperationsArray> | null = [];
20202022 const pendingStringTable: Map<string, StringTableEntry> = new Map();
20212023 let pendingStringTableLength: number = 0;
@@ -2047,6 +2049,7 @@ export function attach(
20472049 pendingOperations.length === 0 &&
20482050 pendingRealUnmountedIDs.length === 0 &&
20492051 pendingRealUnmountedSuspenseIDs.length === 0 &&
2052+ pendingSuspenderChanges.size === 0 &&
20502053 pendingUnmountedRootID === null
20512054 );
20522055 }
@@ -2113,6 +2116,7 @@ export function attach(
21132116 pendingRealUnmountedIDs.length +
21142117 (pendingUnmountedRootID === null ? 0 : 1);
21152118 const numUnmountSuspenseIDs = pendingRealUnmountedSuspenseIDs.length;
2119+ const numSuspenderChanges = pendingSuspenderChanges.size;
21162120
21172121 const operations = new Array<number>(
21182122 // Identify which renderer this update is coming from.
@@ -2128,7 +2132,10 @@ export function attach(
21282132 // [TREE_OPERATION_REMOVE, removedIDLength, ...ids]
21292133 (numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +
21302134 // Regular operations
2131- pendingOperations.length,
2135+ pendingOperations.length +
2136+ // All suspender changes are batched in a single message.
2137+ // [SUSPENSE_TREE_OPERATION_SUSPENDERS, suspenderChangesLength, ...[id, hasUniqueSuspenders]]
2138+ (numSuspenderChanges > 0 ? 2 + numSuspenderChanges * 2 : 0),
21322139 );
21332140
21342141 // Identify which renderer this update is coming from.
@@ -2191,19 +2198,39 @@ export function attach(
21912198 i++;
21922199 }
21932200 }
2194- // Fill in the rest of the operations.
2201+
2202+ // Fill in pending operations.
21952203 for (let j = 0; j < pendingOperations.length; j++) {
21962204 operations[i + j] = pendingOperations[j];
21972205 }
21982206 i += pendingOperations.length;
21992207
2208+ // Suspender changes might affect newly mounted nodes that we already recorded
2209+ // in pending operations.
2210+ if (numSuspenderChanges > 0) {
2211+ operations[i++] = SUSPENSE_TREE_OPERATION_SUSPENDERS;
2212+ operations[i++] = numSuspenderChanges;
2213+ pendingSuspenderChanges.forEach(fiberIdWithChanges => {
2214+ const suspense = idToSuspenseNodeMap.get(fiberIdWithChanges);
2215+ if (suspense === undefined) {
2216+ // Probably forgot to cleanup pendingSuspenderChanges when this node was removed.
2217+ throw new Error(
2218+ ` Could send suspender changes for "${fiberIdWithChanges } " since the Fiber no longer exists . `,
2219+ );
2220+ }
2221+ operations[i++] = fiberIdWithChanges;
2222+ operations[i++] = suspense.hasUniqueSuspenders ? 1 : 0;
2223+ });
2224+ }
2225+
22002226 // Let the frontend know about tree operations.
22012227 flushOrQueueOperations(operations);
22022228
22032229 // Reset all of the pending state now that we've told the frontend about it.
22042230 pendingOperations.length = 0;
22052231 pendingRealUnmountedIDs.length = 0;
22062232 pendingRealUnmountedSuspenseIDs.length = 0;
2233+ pendingSuspenderChanges.clear();
22072234 pendingUnmountedRootID = null;
22082235 pendingStringTable.clear();
22092236 pendingStringTableLength = 0;
@@ -2688,6 +2715,19 @@ export function attach(
26882715 }
26892716 }
26902717
2718+ function recordSuspenseSuspenders(suspenseNode: SuspenseNode): void {
2719+ if (__DEBUG__) {
2720+ console.log('recordSuspenseSuspenders()', suspenseNode);
2721+ }
2722+ const fiberInstance = suspenseNode.instance;
2723+ if (fiberInstance.kind !== FIBER_INSTANCE) {
2724+ // TODO: Suspender updates of filtered Suspense nodes are currently dropped.
2725+ return;
2726+ }
2727+
2728+ pendingSuspenderChanges.add(fiberInstance.id);
2729+ }
2730+
26912731 function recordSuspenseUnmount(suspenseInstance: SuspenseNode): void {
26922732 if (__DEBUG__) {
26932733 console.log(
@@ -2709,6 +2749,7 @@ export function attach(
27092749 // and later arrange them in the correct order.
27102750 pendingRealUnmountedSuspenseIDs.push(id);
27112751
2752+ pendingSuspenderChanges.delete(id);
27122753 idToSuspenseNodeMap.delete(id);
27132754 }
27142755
@@ -2779,6 +2820,7 @@ export function attach(
27792820 ) {
27802821 // This didn't exist in the parent before, so let's mark this boundary as having a unique suspender.
27812822 parentSuspenseNode.hasUniqueSuspenders = true;
2823+ recordSuspenseSuspenders(parentSuspenseNode);
27822824 }
27832825 }
27842826 // We have observed at least one known reason this might have been suspended.
@@ -2820,6 +2862,9 @@ export function attach(
28202862 // We have found a child boundary that depended on the unblocked I/O.
28212863 // It can now be marked as having unique suspenders. We can skip its children
28222864 // since they'll still be blocked by this one.
2865+ if (!node.hasUniqueSuspenders) {
2866+ recordSuspenseSuspenders(node);
2867+ }
28232868 node.hasUniqueSuspenders = true;
28242869 node.hasUnknownSuspenders = false;
28252870 } else if (node.firstChild !== null) {
@@ -3522,6 +3567,9 @@ export function attach(
35223567 // Unfortunately if we don't have any DEV time debug info or debug thenables then
35233568 // we have no meta data to show. However, we still mark this Suspense boundary as
35243569 // participating in the loading sequence since apparently it can suspend.
3570+ if (!suspenseNode.hasUniqueSuspenders) {
3571+ recordSuspenseSuspenders(suspenseNode);
3572+ }
35253573 suspenseNode.hasUniqueSuspenders = true;
35263574 // We have not seen any reason yet for why this suspense node might have been
35273575 // suspended but it clearly has been at some point. If we later discover a reason
0 commit comments