5733
// to a specific instance will have those appear in order of when that instance was discovered.
5734
let hooksCacheKey: null | DevToolsInstance = null;
5735
let hooksCache: null | HooksTree = null;
5736
+ // Collect the stream entries with the highest byte offset and end time.
5737
+ const streamEntries: Map<
5738
+ Promise<mixed>,
5739
+ {
5740
+ asyncInfo: ReactAsyncInfo,
5741
+ instance: DevToolsInstance,
5742
+ hooks: null | HooksTree,
5743
+ },
5744
+ > = new Map();
5745
suspenseNode.suspendedBy.forEach((set, ioInfo) => {
5746
let parentNode = suspenseNode.parent;
5747
while (parentNode !== null) {
5780
}
5781
}
5782
}
5774
- result.push(serializeAsyncInfo(asyncInfo, firstInstance, hooks));
5783
+ const newIO = asyncInfo.awaited;
5784
+ if (newIO.name === 'RSC stream' && newIO.value != null) {
5785
+ const streamPromise = newIO.value;
5786
+ // Special case RSC stream entries to pick the last entry keyed by the stream.
5787
+ const existingEntry = streamEntries.get(streamPromise);
5788
+ if (existingEntry === undefined) {
5789
+ streamEntries.set(streamPromise, {
5790
+ asyncInfo,
5791
+ instance: firstInstance,
5792
+ hooks,
5793
+ });
5794
+ } else {
5795
+ const existingIO = existingEntry.asyncInfo.awaited;
5796
+ if (
5797
+ newIO !== existingIO &&
5798
+ ((newIO.byteSize !== undefined &&
5799
+ existingIO.byteSize !== undefined &&
5800
+ newIO.byteSize > existingIO.byteSize) ||
5801
+ newIO.end > existingIO.end)
5802
+ ) {
5803
+ // The new entry is later in the stream that the old entry. Replace it.
5804
+ existingEntry.asyncInfo = asyncInfo;
5805
+ existingEntry.instance = firstInstance;
5806
+ existingEntry.hooks = hooks;
5807
+ }
5808
+ }
5809
+ } else {
5810
+ result.push(serializeAsyncInfo(asyncInfo, firstInstance, hooks));
5811
+ }
5812
+ }
5813
+ }
5814
+ });
5815
+ // Add any deduped stream entries.
5816
+ streamEntries.forEach(({asyncInfo, instance, hooks}) => {
5817
+ result.push(serializeAsyncInfo(asyncInfo, instance, hooks));
5818
+ });
5819
+ return result;
5820
+ }
5821
+
5822
+ function getSuspendedByOfInstance(
5823
+ devtoolsInstance: DevToolsInstance,
5824
+ hooks: null | HooksTree,
5825
+ ): Array<SerializedAsyncInfo> {
5826
+ const suspendedBy = devtoolsInstance.suspendedBy;
5827
+ if (suspendedBy === null) {
5828
+ return [];
5829
+ }
5830
+
5831
+ const foundIOEntries: Set<ReactIOInfo> = new Set();
5832
+ const streamEntries: Map<Promise<mixed>, ReactAsyncInfo> = new Map();
5833
+ const result: Array<SerializedAsyncInfo> = [];
5834
+ for (let i = 0; i < suspendedBy.length; i++) {
5835
+ const asyncInfo = suspendedBy[i];
5836
+ const ioInfo = asyncInfo.awaited;
5837
+ if (foundIOEntries.has(ioInfo)) {
5838
+ // We have already added this I/O entry to the result. We can dedupe it.
5839
+ // This can happen when an instance depends on the same data in mutliple places.
5840
+ continue;
5841
+ }
5842
+ foundIOEntries.add(ioInfo);
5843
+ if (ioInfo.name === 'RSC stream' && ioInfo.value != null) {
5844
+ const streamPromise = ioInfo.value;
5845
+ // Special case RSC stream entries to pick the last entry keyed by the stream.
5846
+ const existingEntry = streamEntries.get(streamPromise);
5847
+ if (existingEntry === undefined) {
5848
+ streamEntries.set(streamPromise, asyncInfo);
5849
+ } else {
5850
+ const existingIO = existingEntry.awaited;
5851
+ if (
5852
+ ioInfo !== existingIO &&
5853
+ ((ioInfo.byteSize !== undefined &&
5854
+ existingIO.byteSize !== undefined &&
5855
+ ioInfo.byteSize > existingIO.byteSize) ||
5856
+ ioInfo.end > existingIO.end)
5857
+ ) {
5858
+ // The new entry is later in the stream that the old entry. Replace it.
5859
+ streamEntries.set(streamPromise, asyncInfo);
5860
+ }
5861
}
5862
+ } else {
5863
+ result.push(serializeAsyncInfo(asyncInfo, devtoolsInstance, hooks));
5864
}
5865
+ }
5866
+ // Add any deduped stream entries.
5867
+ streamEntries.forEach(asyncInfo => {
5868
+ result.push(serializeAsyncInfo(asyncInfo, devtoolsInstance, hooks));
5869
});
5870
return result;
5871
}
6389
// In this case, this becomes associated with the Client/Host Component where as normally
6390
// you'd expect these to be associated with the Server Component that awaited the data.
6391
// TODO: Prepend other suspense sources like css, images and use().
6300
- fiberInstance.suspendedBy === null
6301
- ? []
6302
- : fiberInstance.suspendedBy.map(info =>
6303
- serializeAsyncInfo(info, fiberInstance, hooks),
6304
- );
6392
+ getSuspendedByOfInstance(fiberInstance, hooks);
6393
const suspendedByRange = getSuspendedByRange(
6394
getNearestSuspenseNode(fiberInstance),
6395
);
6534
6535
const isSuspended = null;
6536
// Things that Suspended this Server Component (use(), awaits and direct child promises)
6449
- const suspendedBy = virtualInstance.suspendedBy;
6537
+ const suspendedBy = getSuspendedByOfInstance(virtualInstance, null);
6538
const suspendedByRange = getSuspendedByRange(
6539
getNearestSuspenseNode(virtualInstance),
6540
);
6585
? []
6586
: Array.from(componentLogsEntry.warnings.entries()),
6587
6500
- suspendedBy:
6501
- suspendedBy === null
6502
- ? []
6503
- : suspendedBy.map(info =>
6504
- serializeAsyncInfo(info, virtualInstance, null),
6505
- ),
6588
+ suspendedBy: suspendedBy,
6589
suspendedByRange: suspendedByRange,
6590
unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
6591