24
ViewTransitionProps,
25
ActivityProps,
26
SuspenseProps,
27
+ SuspenseListProps,
28
+ SuspenseListRevealOrder,
29
} from 'shared/ReactTypes';
30
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
31
import type {
233
[key: string]: any,
234
};
235
236
+type SuspenseListRow = {
237
+ pendingTasks: number, // The number of tasks, previous rows and inner suspense boundaries blocking this row.
238
+ boundaries: null | Array<SuspenseBoundary>, // The boundaries in this row waiting to be unblocked by the previous row. (null means this row is not blocked)
239
+ next: null | SuspenseListRow, // The next row blocked by this one.
240
+};
241
+
242
const CLIENT_RENDERED = 4; // if it errors or infinitely suspends
243
244
type SuspenseBoundary = {
246
rootSegmentID: number,
247
parentFlushed: boolean,
248
pendingTasks: number, // when it reaches zero we can show this boundary's content
249
+ row: null | SuspenseListRow, // the row that this boundary blocks from completing.
250
completedSegments: Array<Segment>, // completed but not yet flushed segments.
251
byteSize: number, // used to determine whether to inline children boundaries.
252
fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.
277
formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)
278
context: ContextSnapshot, // the current new context that this task is executing in
279
treeContext: TreeContext, // the current tree context that this task is executing in
280
+ row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside
281
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
282
thenableState: null | ThenableState,
283
legacyContext: LegacyContext, // the current legacy context that this task is executing in
284
debugTask: null | ConsoleTask, // DEV only
275
- // DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
285
+ // DON'T ANY MORE FIELDS. We at 16 in prod already which otherwise requires converting to a constructor.
286
// Consider splitting into multiple objects or consolidating some fields.
287
};
288
308
formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)
309
context: ContextSnapshot, // the current new context that this task is executing in
310
treeContext: TreeContext, // the current tree context that this task is executing in
311
+ row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside
312
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
313
thenableState: null | ThenableState,
314
legacyContext: LegacyContext, // the current legacy context that this task is executing in
315
debugTask: null | ConsoleTask, // DEV only
305
- // DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
306
- // Consider splitting into multiple objects or consolidating some fields.
316
};
317
318
export type Task = RenderTask | ReplayTask;
551
rootContextSnapshot,
552
emptyTreeContext,
553
null,
554
+ null,
555
emptyContextObject,
556
null,
557
);
657
rootContextSnapshot,
658
emptyTreeContext,
659
null,
660
+ null,
661
emptyContextObject,
662
null,
663
);
685
rootContextSnapshot,
686
emptyTreeContext,
687
null,
688
+ null,
689
emptyContextObject,
690
null,
691
);
751
752
function createSuspenseBoundary(
753
request: Request,
754
+ row: null | SuspenseListRow,
755
fallbackAbortableTasks: Set<Task>,
756
contentPreamble: null | Preamble,
757
fallbackPreamble: null | Preamble,
761
rootSegmentID: -1,
762
parentFlushed: false,
763
pendingTasks: 0,
764
+ row: row,
765
completedSegments: [],
766
byteSize: 0,
767
fallbackAbortableTasks,
779
boundary.errorStack = null;
780
boundary.errorComponentStack = null;
781
}
782
+ if (row !== null) {
783
+ // This boundary will block this row from completing.
784
+ row.pendingTasks++;
785
+ const blockedBoundaries = row.boundaries;
786
+ if (blockedBoundaries !== null) {
787
+ // Previous rows will block this boundary itself from completing.
788
+ request.allPendingTasks++;
789
+ boundary.pendingTasks++;
790
+ blockedBoundaries.push(boundary);
791
+ }
792
+ }
793
return boundary;
794
}
795
807
formatContext: FormatContext,
808
context: ContextSnapshot,
809
treeContext: TreeContext,
810
+ row: null | SuspenseListRow,
811
componentStack: null | ComponentStackNode,
812
legacyContext: LegacyContext,
813
debugTask: null | ConsoleTask,
818
} else {
819
blockedBoundary.pendingTasks++;
820
}
821
+ if (row !== null) {
822
+ row.pendingTasks++;
823
+ }
824
const task: RenderTask = ({
825
replay: null,
826
node,
835
formatContext,
836
context,
837
treeContext,
838
+ row,
839
componentStack,
840
thenableState,
841
}: any);
862
formatContext: FormatContext,
863
context: ContextSnapshot,
864
treeContext: TreeContext,
865
+ row: null | SuspenseListRow,
866
componentStack: null | ComponentStackNode,
867
legacyContext: LegacyContext,
868
debugTask: null | ConsoleTask,
873
} else {
874
blockedBoundary.pendingTasks++;
875
}
876
+ if (row !== null) {
877
+ row.pendingTasks++;
878
+ }
879
replay.pendingTasks++;
880
const task: ReplayTask = ({
881
replay,
891
formatContext,
892
context,
893
treeContext,
894
+ row,
895
componentStack,
896
thenableState,
897
}: any);
1180
// so we can just render through it.
1181
const prevKeyPath = someTask.keyPath;
1182
const prevContext = someTask.formatContext;
1183
+ const prevRow = someTask.row;
1184
someTask.keyPath = keyPath;
1185
someTask.formatContext = getSuspenseContentFormatContext(
1186
request.resumableState,
1187
prevContext,
1188
);
1189
+ someTask.row = null;
1190
const content: ReactNodeList = props.children;
1191
try {
1192
renderNode(request, someTask, content, -1);
1193
} finally {
1194
someTask.keyPath = prevKeyPath;
1195
someTask.formatContext = prevContext;
1196
+ someTask.row = prevRow;
1197
}
1198
return;
1199
}
1202
1203
const prevKeyPath = task.keyPath;
1204
const prevContext = task.formatContext;
1205
+ const prevRow = task.row;
1206
const parentBoundary = task.blockedBoundary;
1207
const parentPreamble = task.blockedPreamble;
1208
const parentHoistableState = task.hoistableState;
1220
if (canHavePreamble(task.formatContext)) {
1221
newBoundary = createSuspenseBoundary(
1222
request,
1223
+ task.row,
1224
fallbackAbortSet,
1225
createPreambleState(),
1226
createPreambleState(),
1227
);
1228
} else {
1189
- newBoundary = createSuspenseBoundary(request, fallbackAbortSet, null, null);
1229
+ newBoundary = createSuspenseBoundary(
1230
+ request,
1231
+ task.row,
1232
+ fallbackAbortSet,
1233
+ null,
1234
+ null,
1235
+ );
1236
}
1237
if (request.trackedPostpones !== null) {
1238
newBoundary.trackedContentKeyPath = keyPath;
1336
),
1337
task.context,
1338
task.treeContext,
1339
+ null, // The row gets reset inside the Suspense boundary.
1340
task.componentStack,
1341
!disableLegacyContext ? task.legacyContext : emptyContextObject,
1342
__DEV__ ? task.debugTask : null,
1365
request.resumableState,
1366
prevContext,
1367
);
1368
+ task.row = null;
1369
contentRootSegment.status = RENDERING;
1370
1371
try {
1387
// the fallback. However, if this boundary ended up big enough to be eligible for outlining
1388
// we can't do that because we might still need the fallback if we outline it.
1389
if (!isEligibleForOutlining(request, newBoundary)) {
1390
+ if (prevRow !== null) {
1391
+ // If we have synchronously completed the boundary and it's not eligible for outlining
1392
+ // then we don't have to wait for it to be flushed before we unblock future rows.
1393
+ // This lets us inline small rows in order.
1394
+ if (--prevRow.pendingTasks === 0) {
1395
+ finishSuspenseListRow(request, prevRow);
1396
+ }
1397
+ }
1398
if (request.pendingRootTasks === 0 && task.blockedPreamble) {
1399
// The root is complete and this boundary may contribute part of the preamble.
1400
// We eagerly attempt to prepare the preamble here because we expect most requests
1461
task.blockedSegment = parentSegment;
1462
task.keyPath = prevKeyPath;
1463
task.formatContext = prevContext;
1464
+ task.row = prevRow;
1465
}
1466
1467
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
1484
),
1485
task.context,
1486
task.treeContext,
1487
+ task.row,
1488
task.componentStack,
1489
!disableLegacyContext ? task.legacyContext : emptyContextObject,
1490
__DEV__ ? task.debugTask : null,
1509
): void {
1510
const prevKeyPath = task.keyPath;
1511
const prevContext = task.formatContext;
1512
+ const prevRow = task.row;
1513
const previousReplaySet: ReplaySet = task.replay;
1514
1515
const parentBoundary = task.blockedBoundary;
1523
if (canHavePreamble(task.formatContext)) {
1524
resumedBoundary = createSuspenseBoundary(
1525
request,
1526
+ task.row,
1527
fallbackAbortSet,
1528
createPreambleState(),
1529
createPreambleState(),
1531
} else {
1532
resumedBoundary = createSuspenseBoundary(
1533
request,
1534
+ task.row,
1535
fallbackAbortSet,
1536
null,
1537
null,
1551
request.resumableState,
1552
prevContext,
1553
);
1554
+ task.row = null;
1555
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
1556
1557
try {
1628
task.replay = previousReplaySet;
1629
task.keyPath = prevKeyPath;
1630
task.formatContext = prevContext;
1631
+ task.row = prevRow;
1632
}
1633
1634
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
1656
),
1657
task.context,
1658
task.treeContext,
1659
+ task.row,
1660
task.componentStack,
1661
!disableLegacyContext ? task.legacyContext : emptyContextObject,
1662
__DEV__ ? task.debugTask : null,
1668
request.pingedTasks.push(suspendedFallbackTask);
1669
}
1670
1671
+function finishSuspenseListRow(request: Request, row: SuspenseListRow): void {
1672
+ // This row finished. Now we have to unblock all the next rows that were blocked on this.
1673
+ // We do this in a loop to avoid stack overflow for very long lists that get unblocked.
1674
+ let unblockedRow = row.next;
1675
+ while (unblockedRow !== null) {
1676
+ // Unblocking the boundaries will decrement the count of this row but we keep it above
1677
+ // zero so they never finish this row recursively.
1678
+ const unblockedBoundaries = unblockedRow.boundaries;
1679
+ if (unblockedBoundaries !== null) {
1680
+ unblockedRow.boundaries = null;
1681
+ for (let i = 0; i < unblockedBoundaries.length; i++) {
1682
+ finishedTask(request, unblockedBoundaries[i], null, null);
1683
+ }
1684
+ }
1685
+ // Instead we decrement at the end to keep it all in this loop.
1686
+ unblockedRow.pendingTasks--;
1687
+ if (unblockedRow.pendingTasks > 0) {
1688
+ // Still blocked.
1689
+ break;
1690
+ }
1691
+ unblockedRow = unblockedRow.next;
1692
+ }
1693
+}
1694
+
1695
+function createSuspenseListRow(
1696
+ previousRow: null | SuspenseListRow,
1697
+): SuspenseListRow {
1698
+ const newRow: SuspenseListRow = {
1699
+ pendingTasks: 1, // At first the row is blocked on attempting rendering itself.
1700
+ boundaries: null,
1701
+ next: null,
1702
+ };
1703
+ if (previousRow !== null && previousRow.pendingTasks > 0) {
1704
+ // If the previous row is not done yet, we add ourselves to be blocked on it.
1705
+ // When it finishes, we'll decrement our pending tasks.
1706
+ newRow.pendingTasks++;
1707
+ newRow.boundaries = [];
1708
+ previousRow.next = newRow;
1709
+ }
1710
+ return newRow;
1711
+}
1712
+
1713
+function renderSuspenseListRows(
1714
+ request: Request,
1715
+ task: Task,
1716
+ keyPath: KeyNode,
1717
+ rows: Array<ReactNodeList>,
1718
+ revealOrder: 'forwards' | 'backwards',
1719
+): void {
1720
+ // This is a fork of renderChildrenArray that's aware of tracking rows.
1721
+ const prevKeyPath = task.keyPath;
1722
+ const previousComponentStack = task.componentStack;
1723
+ let previousDebugTask = null;
1724
+ if (__DEV__) {
1725
+ previousDebugTask = task.debugTask;
1726
+ // We read debugInfo from task.node.props.children instead of rows because it
1727
+ // might have been an unwrapped iterable so we read from the original node.
1728
+ pushServerComponentStack(task, (task.node: any).props.children._debugInfo);
1729
+ }
1730
+
1731
+ const prevTreeContext = task.treeContext;
1732
+ const prevRow = task.row;
1733
+ const totalChildren = rows.length;
1734
+
1735
+ if (task.replay !== null) {
1736
+ // Replay
1737
+ // First we need to check if we have any resume slots at this level.
1738
+ const resumeSlots = task.replay.slots;
1739
+ if (resumeSlots !== null && typeof resumeSlots === 'object') {
1740
+ let previousSuspenseListRow: null | SuspenseListRow = null;
1741
+ for (let n = 0; n < totalChildren; n++) {
1742
+ // Since we are going to resume into a slot whose order was already
1743
+ // determined by the prerender, we can safely resume it even in reverse
1744
+ // render order.
1745
+ const i = revealOrder !== 'backwards' ? n : totalChildren - 1 - n;
1746
+ const node = rows[i];
1747
+ task.row = previousSuspenseListRow = createSuspenseListRow(
1748
+ previousSuspenseListRow,
1749
+ );
1750
+ task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
1751
+ const resumeSegmentID = resumeSlots[i];
1752
+ // TODO: If this errors we should still continue with the next sibling.
1753
+ if (typeof resumeSegmentID === 'number') {
1754
+ resumeNode(request, task, resumeSegmentID, node, i);
1755
+ // We finished rendering this node, so now we can consume this
1756
+ // slot. This must happen after in case we rerender this task.
1757
+ delete resumeSlots[i];
1758
+ } else {
1759
+ renderNode(request, task, node, i);
1760
+ }
1761
+ if (--previousSuspenseListRow.pendingTasks === 0) {
1762
+ finishSuspenseListRow(request, previousSuspenseListRow);
1763
+ }
1764
+ }
1765
+ } else {
1766
+ let previousSuspenseListRow: null | SuspenseListRow = null;
1767
+ for (let n = 0; n < totalChildren; n++) {
1768
+ // Since we are going to resume into a slot whose order was already
1769
+ // determined by the prerender, we can safely resume it even in reverse
1770
+ // render order.
1771
+ const i = revealOrder !== 'backwards' ? n : totalChildren - 1 - n;
1772
+ const node = rows[i];
1773
+ if (__DEV__) {
1774
+ warnForMissingKey(request, task, node);
1775
+ }
1776
+ task.row = previousSuspenseListRow = createSuspenseListRow(
1777
+ previousSuspenseListRow,
1778
+ );
1779
+ task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
1780
+ renderNode(request, task, node, i);
1781
+ if (--previousSuspenseListRow.pendingTasks === 0) {
1782
+ finishSuspenseListRow(request, previousSuspenseListRow);
1783
+ }
1784
+ }
1785
+ }
1786
+ } else {
1787
+ task = ((task: any): RenderTask); // Refined
1788
+ if (revealOrder !== 'backwards') {
1789
+ // Forwards direction
1790
+ let previousSuspenseListRow: null | SuspenseListRow = null;
1791
+ for (let i = 0; i < totalChildren; i++) {
1792
+ const node = rows[i];
1793
+ if (__DEV__) {
1794
+ warnForMissingKey(request, task, node);
1795
+ }
1796
+ task.row = previousSuspenseListRow = createSuspenseListRow(
1797
+ previousSuspenseListRow,
1798
+ );
1799
+ task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
1800
+ renderNode(request, task, node, i);
1801
+ if (--previousSuspenseListRow.pendingTasks === 0) {
1802
+ finishSuspenseListRow(request, previousSuspenseListRow);
1803
+ }
1804
+ }
1805
+ } else {
1806
+ // For backwards direction we need to do things a bit differently.
1807
+ // We give each row its own segment so that we can render the content in
1808
+ // reverse order but still emit it in the right order when we flush.
1809
+ const parentSegment = task.blockedSegment;
1810
+ const childIndex = parentSegment.children.length;
1811
+ const insertionIndex = parentSegment.chunks.length;
1812
+ let previousSuspenseListRow: null | SuspenseListRow = null;
1813
+ for (let i = totalChildren - 1; i >= 0; i--) {
1814
+ const node = rows[i];
1815
+ task.row = previousSuspenseListRow = createSuspenseListRow(
1816
+ previousSuspenseListRow,
1817
+ );
1818
+ task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
1819
+ const newSegment = createPendingSegment(
1820
+ request,
1821
+ insertionIndex,
1822
+ null,
1823
+ task.formatContext,
1824
+ // Assume we are text embedded at the trailing edges
1825
+ i === 0 ? parentSegment.lastPushedText : true,
1826
+ true,
1827
+ );
1828
+ // Insert in the beginning of the sequence, which will insert before any previous rows.
1829
+ parentSegment.children.splice(childIndex, 0, newSegment);
1830
+ task.blockedSegment = newSegment;
1831
+ if (__DEV__) {
1832
+ warnForMissingKey(request, task, node);
1833
+ }
1834
+ try {
1835
+ renderNode(request, task, node, i);
1836
+ pushSegmentFinale(
1837
+ newSegment.chunks,
1838
+ request.renderState,
1839
+ newSegment.lastPushedText,
1840
+ newSegment.textEmbedded,
1841
+ );
1842
+ newSegment.status = COMPLETED;
1843
+ finishedSegment(request, task.blockedBoundary, newSegment);
1844
+ if (--previousSuspenseListRow.pendingTasks === 0) {
1845
+ finishSuspenseListRow(request, previousSuspenseListRow);
1846
+ }
1847
+ } catch (thrownValue: mixed) {
1848
+ if (request.status === ABORTING) {
1849
+ newSegment.status = ABORTED;
1850
+ } else {
1851
+ newSegment.status = ERRORED;
1852
+ }
1853
+ throw thrownValue;
1854
+ }
1855
+ }
1856
+ task.blockedSegment = parentSegment;
1857
+ // Reset lastPushedText for current Segment since the new Segments "consumed" it
1858
+ parentSegment.lastPushedText = false;
1859
+ }
1860
+ }
1861
+
1862
+ // Because this context is always set right before rendering every child, we
1863
+ // only need to reset it to the previous value at the very end.
1864
+ task.treeContext = prevTreeContext;
1865
+ task.row = prevRow;
1866
+ task.keyPath = prevKeyPath;
1867
+ if (__DEV__) {
1868
+ task.componentStack = previousComponentStack;
1869
+ task.debugTask = previousDebugTask;
1870
+ }
1871
+}
1872
+
1873
+function renderSuspenseList(
1874
+ request: Request,
1875
+ task: Task,
1876
+ keyPath: KeyNode,
1877
+ props: SuspenseListProps,
1878
+): void {
1879
+ const children: any = props.children;
1880
+ const revealOrder: SuspenseListRevealOrder = props.revealOrder;
1881
+ // TODO: Support tail hidden/collapsed modes.
1882
+ // const tailMode: SuspenseListTailMode = props.tail;
1883
+ if (revealOrder === 'forwards' || revealOrder === 'backwards') {
1884
+ // For ordered reveal, we need to produce rows from the children.
1885
+ if (isArray(children)) {
1886
+ renderSuspenseListRows(request, task, keyPath, children, revealOrder);
1887
+ return;
1888
+ }
1889
+ const iteratorFn = getIteratorFn(children);
1890
+ if (iteratorFn) {
1891
+ const iterator = iteratorFn.call(children);
1892
+ if (iterator) {
1893
+ if (__DEV__) {
1894
+ validateIterable(task, children, -1, iterator, iteratorFn);
1895
+ }
1896
+ // TODO: We currently use the same id algorithm as regular nodes
1897
+ // but we need a new algorithm for SuspenseList that doesn't require
1898
+ // a full set to be loaded up front to support Async Iterable.
1899
+ // When we have that, we shouldn't buffer anymore.
1900
+ let step = iterator.next();
1901
+ if (!step.done) {
1902
+ const rows = [];
1903
+ do {
1904
+ rows.push(step.value);
1905
+ step = iterator.next();
1906
+ } while (!step.done);
1907
+ renderSuspenseListRows(request, task, keyPath, children, revealOrder);
1908
+ }
1909
+ return;
1910
+ }
1911
+ }
1912
+ if (
1913
+ enableAsyncIterableChildren &&
1914
+ typeof (children: any)[ASYNC_ITERATOR] === 'function'
1915
+ ) {
1916
+ const iterator: AsyncIterator<ReactNodeList> = (children: any)[
1917
+ ASYNC_ITERATOR
1918
+ ]();
1919
+ if (iterator) {
1920
+ if (__DEV__) {
1921
+ validateAsyncIterable(task, (children: any), -1, iterator);
1922
+ }
1923
+ // TODO: Update the task.children to be the iterator to avoid asking
1924
+ // for new iterators, but we currently warn for rendering these
1925
+ // so needs some refactoring to deal with the warning.
1926
+
1927
+ // Restore the thenable state before resuming.
1928
+ const prevThenableState = task.thenableState;
1929
+ task.thenableState = null;
1930
+ prepareToUseThenableState(prevThenableState);
1931
+
1932
+ // We need to know how many total rows are in this set, so that we
1933
+ // can allocate enough id slots to acommodate them. So we must exhaust
1934
+ // the iterator before we start recursively rendering the rows.
1935
+ // TODO: This is not great but I think it's inherent to the id
1936
+ // generation algorithm.
1937
+
1938
+ const rows = [];
1939
+
1940
+ let done = false;
1941
+
1942
+ if (iterator === children) {
1943
+ // If it's an iterator we need to continue reading where we left
1944
+ // off. We can do that by reading the first few rows from the previous
1945
+ // thenable state.
1946
+ // $FlowFixMe
1947
+ let step = readPreviousThenableFromState();
1948
+ while (step !== undefined) {
1949
+ if (step.done) {
1950
+ done = true;
1951
+ break;
1952
+ }
1953
+ rows.push(step.value);
1954
+ step = readPreviousThenableFromState();
1955
+ }
1956
+ }
1957
+
1958
+ if (!done) {
1959
+ let step = unwrapThenable(iterator.next());
1960
+ while (!step.done) {
1961
+ rows.push(step.value);
1962
+ step = unwrapThenable(iterator.next());
1963
+ }
1964
+ }
1965
+ renderSuspenseListRows(request, task, keyPath, rows, revealOrder);
1966
+ return;
1967
+ }
1968
+ }
1969
+ // This case will warn on the client. It's the same as independent revealOrder.
1970
+ }
1971
+
1972
+ if (revealOrder === 'together') {
1973
+ // TODO
1974
+ }
1975
+ // For other reveal order modes, we just render it as a fragment.
1976
+ const prevKeyPath = task.keyPath;
1977
+ task.keyPath = keyPath;
1978
+ renderNodeDestructive(request, task, children, -1);
1979
+ task.keyPath = prevKeyPath;
1980
+}
1981
+
1982
function renderPreamble(
1983
request: Request,
1984
task: Task,
2009
task.formatContext,
2010
task.context,
2011
task.treeContext,
2012
+ task.row,
2013
task.componentStack,
2014
!disableLegacyContext ? task.legacyContext : emptyContextObject,
2015
__DEV__ ? task.debugTask : null,
2759
return;
2760
}
2761
case REACT_SUSPENSE_LIST_TYPE: {
2386
- // TODO: SuspenseList should control the boundaries.
2387
- const prevKeyPath = task.keyPath;
2388
- task.keyPath = keyPath;
2389
- renderNodeDestructive(request, task, props.children, -1);
2390
- task.keyPath = prevKeyPath;
2762
+ renderSuspenseList(request, task, keyPath, props);
2763
return;
2764
}
2765
case REACT_VIEW_TRANSITION_TYPE: {
3909
task.formatContext,
3910
task.context,
3911
task.treeContext,
3912
+ task.row,
3913
task.componentStack,
3914
!disableLegacyContext ? task.legacyContext : emptyContextObject,
3915
__DEV__ ? task.debugTask : null,
3951
task.formatContext,
3952
task.context,
3953
task.treeContext,
3954
+ task.row,
3955
task.componentStack,
3956
!disableLegacyContext ? task.legacyContext : emptyContextObject,
3957
__DEV__ ? task.debugTask : null,
4260
function erroredTask(
4261
request: Request,
4262
boundary: Root | SuspenseBoundary,
4263
+ row: null | SuspenseListRow,
4264
error: mixed,
4265
errorInfo: ThrownInfo,
4266
debugTask: null | ConsoleTask,
4267
) {
4268
+ if (row !== null) {
4269
+ if (--row.pendingTasks === 0) {
4270
+ finishSuspenseListRow(request, row);
4271
+ }
4272
+ }
4273
+
4274
+ request.allPendingTasks--;
4275
+
4276
// Report the error to a global handler.
4277
let errorDigest;
4278
// We don't handle halts here because we only halt when prerendering and
4324
}
4325
}
4326
3944
- request.allPendingTasks--;
4327
if (request.allPendingTasks === 0) {
4328
completeAll(request);
4329
}
4338
const segment = task.blockedSegment;
4339
if (segment !== null) {
4340
segment.status = ABORTED;
3959
- finishedTask(request, boundary, segment);
4341
+ finishedTask(request, boundary, task.row, segment);
4342
}
4343
}
4344
4352
): void {
4353
const resumedBoundary = createSuspenseBoundary(
4354
request,
4355
+ null,
4356
new Set(),
4357
null,
4358
null,
4452
segment.status = ABORTED;
4453
}
4454
4455
+ const row = task.row;
4456
+ if (row !== null) {
4457
+ if (--row.pendingTasks === 0) {
4458
+ finishSuspenseListRow(request, row);
4459
+ }
4460
+ }
4461
+
4462
const errorInfo = getThrownInfo(task.componentStack);
4463
4464
if (boundary === null) {
4481
// we just need to mark it as postponed.
4482
logPostpone(request, postponeInstance.message, errorInfo, null);
4483
trackPostpone(request, trackedPostpones, task, segment);
4094
- finishedTask(request, null, segment);
4484
+ finishedTask(request, null, row, segment);
4485
} else {
4486
const fatal = new Error(
4487
'The render was aborted with postpone when the shell is incomplete. Reason: ' +
4500
// We log the error but we still resolve the prerender
4501
logRecoverableError(request, error, errorInfo, null);
4502
trackPostpone(request, trackedPostpones, task, segment);
4113
- finishedTask(request, null, segment);
4503
+ finishedTask(request, null, row, segment);
4504
} else {
4505
logRecoverableError(request, error, errorInfo, null);
4506
fatalError(request, error, errorInfo, null);
4572
abortTask(fallbackTask, request, error),
4573
);
4574
boundary.fallbackAbortableTasks.clear();
4185
- return finishedTask(request, boundary, segment);
4575
+ return finishedTask(request, boundary, row, segment);
4576
}
4577
}
4578
boundary.status = CLIENT_RENDERED;
4589
logPostpone(request, postponeInstance.message, errorInfo, null);
4590
if (request.trackedPostpones !== null && segment !== null) {
4591
trackPostpone(request, request.trackedPostpones, task, segment);
4202
- finishedTask(request, task.blockedBoundary, segment);
4592
+ finishedTask(request, task.blockedBoundary, row, segment);
4593
4594
// If this boundary was still pending then we haven't already cancelled its fallbacks.
4595
// We'll need to abort the fallbacks, which will also error that parent boundary.
4745
function finishedTask(
4746
request: Request,
4747
boundary: Root | SuspenseBoundary,
4748
+ row: null | SuspenseListRow,
4749
segment: null | Segment,
4750
) {
4751
+ if (row !== null) {
4752
+ if (--row.pendingTasks === 0) {
4753
+ finishSuspenseListRow(request, row);
4754
+ }
4755
+ }
4756
request.allPendingTasks--;
4757
if (boundary === null) {
4758
if (segment !== null && segment.parentFlushed) {
4801
if (!isEligibleForOutlining(request, boundary)) {
4802
boundary.fallbackAbortableTasks.forEach(abortTaskSoft, request);
4803
boundary.fallbackAbortableTasks.clear();
4804
+ const boundaryRow = boundary.row;
4805
+ if (boundaryRow !== null) {
4806
+ // If we aren't eligible for outlining, we don't have to wait until we flush it.
4807
+ if (--boundaryRow.pendingTasks === 0) {
4808
+ finishSuspenseListRow(request, boundaryRow);
4809
+ }
4810
+ }
4811
}
4812
4813
if (
4906
task.abortSet.delete(task);
4907
segment.status = COMPLETED;
4908
finishedSegment(request, task.blockedBoundary, segment);
4506
- finishedTask(request, task.blockedBoundary, segment);
4909
+ finishedTask(request, task.blockedBoundary, task.row, segment);
4910
} catch (thrownValue: mixed) {
4911
resetHooksState();
4912
4959
}
4960
4961
trackPostpone(request, trackedPostpones, task, segment);
4559
- finishedTask(request, task.blockedBoundary, segment);
4962
+ finishedTask(request, task.blockedBoundary, task.row, segment);
4963
return;
4964
}
4965
4993
__DEV__ ? task.debugTask : null,
4994
);
4995
trackPostpone(request, trackedPostpones, task, segment);
4593
- finishedTask(request, task.blockedBoundary, segment);
4996
+ finishedTask(request, task.blockedBoundary, task.row, segment);
4997
return;
4998
}
4999
}
5005
erroredTask(
5006
request,
5007
task.blockedBoundary,
5008
+ task.row,
5009
x,
5010
errorInfo,
5011
__DEV__ ? task.debugTask : null,
5053
task.replay.pendingTasks--;
5054
5055
task.abortSet.delete(task);
4652
- finishedTask(request, task.blockedBoundary, null);
5056
+ finishedTask(request, task.blockedBoundary, task.row, null);
5057
} catch (thrownValue) {
5058
resetHooksState();
5059
5365
// Emit a client rendered suspense boundary wrapper.
5366
// We never queue the inner boundary so we'll never emit its content or partial segments.
5367
5368
+ const row = boundary.row;
5369
+ if (row !== null) {
5370
+ // Since this boundary end up client rendered, we can unblock future suspense list rows.
5371
+ // This means that they may appear out of order if the future rows succeed but this is
5372
+ // a client rendered row.
5373
+ if (--row.pendingTasks === 0) {
5374
+ finishSuspenseListRow(request, row);
5375
+ }
5376
+ }
5377
+
5378
if (__DEV__) {
5379
writeStartClientRenderedSuspenseBoundary(
5380
destination,
5463
if (hoistableState) {
5464
hoistHoistables(hoistableState, boundary.contentState);
5465
}
5466
+
5467
+ const row = boundary.row;
5468
+ if (row !== null && isEligibleForOutlining(request, boundary)) {
5469
+ // Once we have written the boundary, we can unblock the row and let future
5470
+ // rows be written. This may schedule new completed boundaries.
5471
+ if (--row.pendingTasks === 0) {
5472
+ finishSuspenseListRow(request, row);
5473
+ }
5474
+ }
5475
+
5476
// We can inline this boundary's content as a complete boundary.
5477
writeStartCompletedSuspenseBoundary(destination, request.renderState);
5478
5551
}
5552
completedSegments.length = 0;
5553
5554
+ const row = boundary.row;
5555
+ if (row !== null && isEligibleForOutlining(request, boundary)) {
5556
+ // Once we have written the boundary, we can unblock the row and let future
5557
+ // rows be written. This may schedule new completed boundaries.
5558
+ if (--row.pendingTasks === 0) {
5559
+ finishSuspenseListRow(request, row);
5560
+ }
5561
+ }
5562
+
5563
writeHoistablesForBoundary(
5564
destination,
5565
boundary.contentState,
5752
5753
// Next we check the completed boundaries again. This may have had
5754
// boundaries added to it in case they were too larged to be inlined.
5755
+ // SuspenseListRows might have been unblocked as well.
5756
// New ones might be added in this loop.
5757
const largeBoundaries = request.completedBoundaries;
5758
for (i = 0; i < largeBoundaries.length; i++) {