packages/react-server/src/ReactFizzServer.js JAVASCRIPT 6,492 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 6,492.
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {11  Destination,12  Chunk,13  PrecomputedChunk,14} from './ReactServerStreamConfig';15import type {16  ReactNodeList,17  ReactContext,18  ReactConsumerType,19  Wakeable,20  Thenable,21  ReactFormState,22  ReactComponentInfo,23  ReactDebugInfo,24  ReactAsyncInfo,25  ViewTransitionProps,26  ActivityProps,27  SuspenseProps,28  SuspenseListProps,29  SuspenseListRevealOrder,30  ReactKey,31} from 'shared/ReactTypes';32import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';33import type {34  RenderState,35  ResumableState,36  PreambleState,37  FormatContext,38  HoistableState,39} from './ReactFizzConfig';40import type {ContextSnapshot} from './ReactFizzNewContext';41import type {ComponentStackNode} from './ReactFizzComponentStack';42import type {TreeContext} from './ReactFizzTreeContext';43import type {ThenableState} from './ReactFizzThenable';4445import {describeObjectForErrorMessage} from 'shared/ReactSerializationErrors';4647import {48  scheduleWork,49  scheduleMicrotask,50  beginWriting,51  writeChunk,52  writeChunkAndReturn,53  completeWriting,54  flushBuffered,55  close,56  closeWithError,57  byteLengthOfChunk,58} from './ReactServerStreamConfig';59import {60  writeCompletedRoot,61  writePlaceholder,62  pushStartActivityBoundary,63  pushEndActivityBoundary,64  writeStartCompletedSuspenseBoundary,65  writeStartPendingSuspenseBoundary,66  writeStartClientRenderedSuspenseBoundary,67  writeEndCompletedSuspenseBoundary,68  writeEndPendingSuspenseBoundary,69  writeEndClientRenderedSuspenseBoundary,70  writeStartSegment,71  writeEndSegment,72  writeClientRenderBoundaryInstruction,73  writeCompletedBoundaryInstruction,74  writeCompletedSegmentInstruction,75  writeHoistablesForBoundary,76  pushTextInstance,77  pushStartInstance,78  pushEndInstance,79  pushSegmentFinale,80  getChildFormatContext,81  getSuspenseFallbackFormatContext,82  getSuspenseContentFormatContext,83  getViewTransitionFormatContext,84  writeHoistables,85  writePreambleStart,86  writePreambleEnd,87  writePostamble,88  hoistHoistables,89  createHoistableState,90  createPreambleState,91  isWorkLoopExternallyDriven,92  supportsRequestStorage,93  requestStorage,94  pushFormStateMarkerIsMatching,95  pushFormStateMarkerIsNotMatching,96  resetResumableState,97  completeResumableState,98  emitEarlyPreloads,99  bindToConsole,100  canHavePreamble,101  hoistPreambleState,102  isPreambleReady,103  isPreambleContext,104  hasSuspenseyContent,105} from './ReactFizzConfig';106import {107  constructClassInstance,108  mountClassInstance,109} from './ReactFizzClassComponent';110import {111  getMaskedContext,112  processChildContext,113  emptyContextObject,114} from './ReactFizzLegacyContext';115import {116  readContext,117  rootContextSnapshot,118  switchContext,119  getActiveContext,120  pushProvider,121  popProvider,122} from './ReactFizzNewContext';123import {124  prepareToUseHooks,125  prepareToUseThenableState,126  finishHooks,127  checkDidRenderIdHook,128  resetHooksState,129  HooksDispatcher,130  currentResumableState,131  setCurrentResumableState,132  getThenableStateAfterSuspending,133  unwrapThenable,134  readPreviousThenableFromState,135  getActionStateCount,136  getActionStateMatchingIndex,137} from './ReactFizzHooks';138import {DefaultAsyncDispatcher} from './ReactFizzAsyncDispatcher';139import {140  getStackByComponentStackNode,141  getOwnerStackByComponentStackNodeInDev,142} from './ReactFizzComponentStack';143import {emptyTreeContext, pushTreeContext} from './ReactFizzTreeContext';144import {currentTaskInDEV, setCurrentTaskInDEV} from './ReactFizzCurrentTask';145import {146  callLazyInitInDEV,147  callComponentInDEV,148  callRenderInDEV,149} from './ReactFizzCallUserSpace';150import {151  getViewTransitionClassName,152  getViewTransitionName,153} from './ReactFizzViewTransitionComponent';154155import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';156import {157  getIteratorFn,158  ASYNC_ITERATOR,159  REACT_ELEMENT_TYPE,160  REACT_PORTAL_TYPE,161  REACT_LAZY_TYPE,162  REACT_SUSPENSE_TYPE,163  REACT_LEGACY_HIDDEN_TYPE,164  REACT_STRICT_MODE_TYPE,165  REACT_PROFILER_TYPE,166  REACT_SUSPENSE_LIST_TYPE,167  REACT_FRAGMENT_TYPE,168  REACT_FORWARD_REF_TYPE,169  REACT_MEMO_TYPE,170  REACT_CONTEXT_TYPE,171  REACT_CONSUMER_TYPE,172  REACT_SCOPE_TYPE,173  REACT_VIEW_TRANSITION_TYPE,174  REACT_ACTIVITY_TYPE,175  REACT_OPTIMISTIC_KEY,176} from 'shared/ReactSymbols';177import ReactSharedInternals from 'shared/ReactSharedInternals';178import {179  disableLegacyContext,180  disableLegacyContextForFunctionComponents,181  enableScopeAPI,182  enableAsyncIterableChildren,183  enableViewTransition,184  enableFizzBlockingRender,185  enableAsyncDebugInfo,186  enableCPUSuspense,187} from 'shared/ReactFeatureFlags';188189import assign from 'shared/assign';190import noop from 'shared/noop';191import getComponentNameFromType from 'shared/getComponentNameFromType';192import isArray from 'shared/isArray';193import {194  SuspenseException,195  getSuspendedThenable,196  ensureSuspendableThenableStateDEV,197  getSuspendedCallSiteStackDEV,198  getSuspendedCallSiteDebugTaskDEV,199  setCaptureSuspendedCallSiteDEV,200} from './ReactFizzThenable';201202// Linked list representing the identity of a component given the component/tag name and key.203// The name might be minified but we assume that it's going to be the same generated name. Typically204// because it's just the same compiled output in practice.205export type KeyNode = [206  Root | KeyNode /* parent */,207  string | null /* name */,208  string | number /* key */,209];210211type ResumeSlots =212  | null // nothing to resume213  | number // resume with segment ID at the root position214  | {[index: number]: number}; // resume with segmentID at the index215216type ReplaySuspenseBoundary = [217  string | null /* name */,218  string | number /* key */,219  Array<ReplayNode> /* content keyed children */,220  ResumeSlots /* content resumable slots */,221  null | ReplayNode /* fallback content */,222  number /* rootSegmentID */,223];224225type ReplayNode =226  | [227      string | null /* name */,228      string | number /* key */,229      Array<ReplayNode> /* keyed children */,230      ResumeSlots /* resumable slots */,231    ]232  | ReplaySuspenseBoundary;233234type PostponedHoles = {235  workingMap: Map<KeyNode, ReplayNode>,236  rootNodes: Array<ReplayNode>,237  rootSlots: ResumeSlots,238};239240type LegacyContext = {241  [key: string]: any,242};243244type SuspenseListRow = {245  pendingTasks: number, // The number of tasks, previous rows and inner suspense boundaries blocking this row.246  boundaries: null | Array<SuspenseBoundary>, // The boundaries in this row waiting to be unblocked by the previous row. (null means this row is not blocked)247  hoistables: HoistableState, // Any dependencies that this row depends on. Future rows need to also depend on it.248  inheritedHoistables: null | HoistableState, // Any dependencies that previous row depend on, that new boundaries of this row needs.249  together: boolean, // All the boundaries within this row must be revealed together.250  next: null | SuspenseListRow, // The next row blocked by this one.251};252253const CLIENT_RENDERED = 4; // if it errors or infinitely suspends254255type SuspenseBoundary = {256  status: 0 | 1 | 4 | 5,257  rootSegmentID: number,258  parentFlushed: boolean,259  pendingTasks: number, // when it reaches zero we can show this boundary's content260  row: null | SuspenseListRow, // the row that this boundary blocks from completing.261  completedSegments: Array<Segment>, // completed but not yet flushed segments.262  byteSize: number, // used to determine whether to inline children boundaries.263  defer: boolean, // never inline deferred boundaries264  fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.265  contentState: HoistableState,266  fallbackState: HoistableState,267  preamble: null | Preamble,268  tracked: null | {269    contentKeyPath: null | KeyNode, // used to track the path for replay nodes270    fallbackNode: null | ReplayNode, // used to track the fallback for replay nodes271  },272  errorDigest: ?string, // the error hash if it errors273  // DEV-only fields274  errorMessage?: null | string, // the error string if it errors275  errorStack?: null | string, // the error stack if it errors276  errorComponentStack?: null | string, // the error component stack if it errors277};278279type Ping = {280  resolve: () => void,281  reject: (error: mixed) => void,282};283284type RenderTask = {285  replay: null,286  node: ReactNodeList,287  childIndex: number,288  ping: Ping,289  blockedBoundary: Root | SuspenseBoundary,290  blockedSegment: Segment, // the segment we'll write to291  blockedPreamble: null | PreambleState,292  hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary293  abortSet: Set<Task>, // the abortable set that this task belongs to294  keyPath: Root | KeyNode, // the path of all parent keys currently rendering295  formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)296  context: ContextSnapshot, // the current new context that this task is executing in297  treeContext: TreeContext, // the current tree context that this task is executing in298  row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside299  componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component300  thenableState: null | ThenableState,301  legacyContext: LegacyContext, // the current legacy context that this task is executing in302  debugTask: null | ConsoleTask, // DEV only303  // DON'T ANY MORE FIELDS. We at 16 in prod already which otherwise requires converting to a constructor.304  // Consider splitting into multiple objects or consolidating some fields.305};306307type ReplaySet = {308  nodes: Array<ReplayNode>, // the possible paths to follow down the replaying309  slots: ResumeSlots, // slots to resume310  pendingTasks: number, // tracks the number of tasks currently tracking this set of nodes311  // if pending tasks reach zero but there are still nodes left, it means we couldn't find312  // them all in the tree, so we need to abort and client render the boundary.313};314315type ReplayTask = {316  replay: ReplaySet,317  node: ReactNodeList,318  childIndex: number,319  ping: Ping,320  blockedBoundary: Root | SuspenseBoundary,321  blockedSegment: null, // we don't write to anything when we replay322  blockedPreamble: null,323  hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary324  abortSet: Set<Task>, // the abortable set that this task belongs to325  keyPath: Root | KeyNode, // the path of all parent keys currently rendering326  formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)327  context: ContextSnapshot, // the current new context that this task is executing in328  treeContext: TreeContext, // the current tree context that this task is executing in329  row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside330  componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component331  thenableState: null | ThenableState,332  legacyContext: LegacyContext, // the current legacy context that this task is executing in333  debugTask: null | ConsoleTask, // DEV only334};335336export type Task = RenderTask | ReplayTask;337338const PENDING = 0;339const COMPLETED = 1;340const FLUSHED = 2;341const ABORTED = 3;342const ERRORED = 4;343const POSTPONED = 5;344345type Root = null;346347type Segment = {348  status: 0 | 1 | 2 | 3 | 4 | 5,349  parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed350  id: number, // starts as 0 and is lazily assigned if the parent flushes early351  +index: number, // the index within the parent's chunks or 0 at the root352  +chunks: Array<Chunk | PrecomputedChunk>,353  +children: Array<Segment>,354  +preambleChildren: Array<Segment>,355  // The context that this segment was created in.356  parentFormatContext: FormatContext,357  // If this segment represents a fallback, this is the content that will replace that fallback.358  boundary: null | SuspenseBoundary,359  // used to discern when text separator boundaries are needed360  lastPushedText: boolean,361  textEmbedded: boolean,362};363364// The ordering of these statuses matters. OPENING and OPEN are the only365// statuses in which newly scheduled work may be performed. Any status greater366// than OPEN represents a request that no longer admits work.367const OPENING = 10;368const OPEN = 11;369const CLOSING = 12;370const CLOSED = 13;371const STALLED_DEV = 14;372373export opaque type Request = {374  destination: null | Destination,375  flushScheduled: boolean,376  +resumableState: ResumableState,377  +renderState: RenderState,378  +rootFormatContext: FormatContext,379  +progressiveChunkSize: number,380  status: 10 | 11 | 12 | 13 | 14,381  fatalError: mixed,382  aborted: boolean,383  nextSegmentId: number,384  allPendingTasks: number, // when it reaches zero, we can close the connection.385  pendingRootTasks: number, // when this reaches zero, we've finished at least the root boundary.386  completedRootSegment: null | Segment, // Completed but not yet flushed root segments.387  completedPreambleSegments: null | Array<Array<Segment>>, // contains the ready-to-flush segments that make up the preamble388  byteSize: number, // counts the number of bytes accumulated in the shell389  abortableTasks: Set<Task>,390  pingedTasks: Array<Task>, // High priority tasks that should be worked on first.391  currentTask: null | Task, // The task currently executing in this request.392  // Queues to flush in order of priority393  clientRenderedBoundaries: Array<SuspenseBoundary>, // Errored or client rendered but not yet flushed.394  completedBoundaries: Array<SuspenseBoundary>, // Completed but not yet fully flushed boundaries to show.395  partialBoundaries: Array<SuspenseBoundary>, // Partially completed boundaries that can flush its segments early.396  trackedPostpones: null | PostponedHoles, // Gets set to non-null while we want to track postponed holes. I.e. during a prerender.397  // While prerendering a postponed request that produced a real shell, this398  // holds the PostponedState returned by getPostponedState. getPostponedState399  // snapshots nextSegmentId before the (pull-driven) prelude flush runs, but the400  // flush outlines completed boundaries and advances nextSegmentId past that401  // snapshot. We finalize the snapshot from flushCompletedQueues so the resumed402  // render allocates segment ids strictly above the shell's; otherwise the shell403  // and resume emit duplicate B:/S: ids once concatenated. Stays null for live404  // renders and resumes.405  postponedState: null | PostponedState,406  // onError is called when an error happens anywhere in the tree. It might recover.407  // The return string is used in production  primarily to avoid leaking internals, secondarily to save bytes.408  // Returning null/undefined will cause a default error message in production409  onError: (error: mixed, errorInfo: ThrownInfo) => ?string,410  // onAllReady is called when all pending task is done but it may not have flushed yet.411  // This is a good time to start writing if you want only HTML and no intermediate steps.412  onAllReady: () => void,413  // onShellReady is called when there is at least a root fallback ready to show.414  // Typically you don't need this callback because it's best practice to always have a415  // root fallback ready so there's no need to wait.416  onShellReady: () => void,417  // onShellError is called when the shell didn't complete. That means you probably want to418  // emit a different response to the stream instead.419  onShellError: (error: mixed) => void,420  onFatalError: (error: mixed) => void,421  // Form state that was the result of an MPA submission, if it was provided.422  formState: null | ReactFormState<any, any>,423  // DEV-only, warning dedupe424  didWarnForKey?: null | WeakSet<ComponentStackNode>,425};426427type Preamble = {428  content: PreambleState,429  fallback: PreambleState,430};431432function createPreamble(): Preamble {433  return {434    content: createPreambleState(),435    fallback: createPreambleState(),436  };437}438439// This is a default heuristic for how to split up the HTML content into progressive440// loading. Our goal is to be able to display additional new content about every 500ms.441// Faster than that is unnecessary and should be throttled on the client. It also442// adds unnecessary overhead to do more splits. We don't know if it's a higher or lower443// end device but higher end suffer less from the overhead than lower end does from444// not getting small enough pieces. We error on the side of low end.445// We base this on low end 3G speeds which is about 500kbits per second. We assume446// that there can be a reasonable drop off from max bandwidth which leaves you with447// as little as 80%. We can receive half of that each 500ms - at best. In practice,448// a little bandwidth is lost to processing and contention - e.g. CSS and images that449// are downloaded along with the main content. So we estimate about half of that to be450// the lower end throughput. In other words, we expect that you can at least show451// about 12.5kb of content per 500ms. Not counting starting latency for the first452// paint.453// 500 * 1024 / 8 * .8 * 0.5 / 2454const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800;455456function getBlockingRenderMaxSize(request: Request): number {457  // We want to make sure that we can block the reveal of a well designed complete458  // shell but if you have constructed a too large shell (e.g. by not adding any459  // Suspense boundaries) then that might take too long to render. We shouldn't460  // punish users (or overzealous metrics tracking) in that scenario.461  // There's a trade off here. If this limit is too low then you can't fit a462  // reasonably well built UI within it without getting errors. If it's too high463  // then things that accidentally fall below it might take too long to load.464  // Web Vitals target 1.8 seconds for first paint and our goal to have the limit465  // be fast enough to hit that. For this argument we assume that most external466  // resources are already cached because it's a return visit, or inline styles.467  // If it's not, then it's highly unlikely that any render blocking instructions468  // we add has any impact what so ever on the paint.469  // Assuming a first byte of about 600ms which is kind of bad but common with a470  // decent static host. If it's longer e.g. due to dynamic rendering, then you471  // are going to bound by dynamic production of the content and you're better off472  // with Suspense boundaries anyway. This number doesn't matter much. Then you473  // have about 1.2 seconds left for bandwidth. On 3G that gives you about 112.5kb474  // worth of data. That's worth about 10x in terms of uncompressed bytes. Then we475  // half that just to account for longer latency, slower bandwidth and CPU processing.476  // Now we're down to about 500kb. In fact, looking at metrics we've collected with477  // rel="expect" examples and other documents, the impact on documents smaller than478  // that is within the noise. That's because there's enough happening within that479  // start up to not make HTML streaming not significantly better.480  // Content above the fold tends to be about 100-200kb tops. Therefore 500kb should481  // be enough head room for a good loading state. After that you should use482  // Suspense or SuspenseList to improve it.483  // Since this is highly related to the reason you would adjust the484  // progressiveChunkSize option, and always has to be higher, we define this limit485  // in terms of it. So if you want to increase the limit because you have high486  // bandwidth users, then you can adjust it up. If you are concerned about even487  // slower bandwidth then you can adjust it down.488  return request.progressiveChunkSize * 40; // 512kb by default.489}490491function isEligibleForOutlining(492  request: Request,493  boundary: SuspenseBoundary,494): boolean {495  // For very small boundaries, don't bother producing a fallback for outlining.496  // The larger this limit is, the more we can save on preparing fallbacks in case we end up497  // outlining.498  return (499    (boundary.byteSize > 500 ||500      hasSuspenseyContent(boundary.contentState, /* flushingInShell */ false) ||501      boundary.defer) &&502    // For boundaries that can possibly contribute to the preamble we don't want to outline503    // them regardless of their size since the fallbacks should only be emitted if we've504    // errored the boundary.505    boundary.preamble === null506  );507}508509function defaultErrorHandler(error: mixed) {510  if (511    typeof error === 'object' &&512    error !== null &&513    typeof error.environmentName === 'string'514  ) {515    // This was a Server error. We print the environment name in a badge just like we do with516    // replays of console logs to indicate that the source of this throw as actually the Server.517    bindToConsole('error', [error], error.environmentName)();518  } else {519    console['error'](error); // Don't transform to our wrapper520  }521  return null;522}523524function RequestInstance(525  this: $FlowFixMe,526  resumableState: ResumableState,527  renderState: RenderState,528  rootFormatContext: FormatContext,529  progressiveChunkSize: void | number,530  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),531  onAllReady: void | (() => void),532  onShellReady: void | (() => void),533  onShellError: void | ((error: mixed) => void),534  onFatalError: void | ((error: mixed) => void),535  formState: void | null | ReactFormState<any, any>,536) {537  const pingedTasks: Array<Task> = [];538  const abortSet: Set<Task> = new Set();539  this.destination = null;540  this.flushScheduled = false;541  this.resumableState = resumableState;542  this.renderState = renderState;543  this.rootFormatContext = rootFormatContext;544  this.progressiveChunkSize =545    progressiveChunkSize === undefined546      ? DEFAULT_PROGRESSIVE_CHUNK_SIZE547      : progressiveChunkSize;548  // $FlowFixMe[constant-condition]549  this.status = isWorkLoopExternallyDriven ? OPEN : OPENING;550  this.fatalError = null;551  this.aborted = false;552  this.nextSegmentId = 0;553  this.allPendingTasks = 0;554  this.pendingRootTasks = 0;555  this.completedRootSegment = null;556  this.completedPreambleSegments = null;557  this.byteSize = 0;558  this.abortableTasks = abortSet;559  this.pingedTasks = pingedTasks;560  this.currentTask = null;561  this.clientRenderedBoundaries = [] as Array<SuspenseBoundary>;562  this.completedBoundaries = [] as Array<SuspenseBoundary>;563  this.partialBoundaries = [] as Array<SuspenseBoundary>;564  this.trackedPostpones = null;565  this.postponedState = null;566  this.onError = onError === undefined ? defaultErrorHandler : onError;567  this.onAllReady = onAllReady === undefined ? noop : onAllReady;568  this.onShellReady = onShellReady === undefined ? noop : onShellReady;569  this.onShellError = onShellError === undefined ? noop : onShellError;570  this.onFatalError = onFatalError === undefined ? noop : onFatalError;571  this.formState = formState === undefined ? null : formState;572  if (__DEV__) {573    this.didWarnForKey = null;574  }575}576577export function createRequest(578  children: ReactNodeList,579  resumableState: ResumableState,580  renderState: RenderState,581  rootFormatContext: FormatContext,582  progressiveChunkSize: void | number,583  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),584  onAllReady: void | (() => void),585  onShellReady: void | (() => void),586  onShellError: void | ((error: mixed) => void),587  onFatalError: void | ((error: mixed) => void),588  formState: void | null | ReactFormState<any, any>,589): Request {590  if (__DEV__) {591    resetOwnerStackLimit();592  }593594  // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors595  const request: Request = new RequestInstance(596    resumableState,597    renderState,598    rootFormatContext,599    progressiveChunkSize,600    onError,601    onAllReady,602    onShellReady,603    onShellError,604    onFatalError,605    formState,606  );607608  // This segment represents the root fallback.609  const rootSegment = createPendingSegment(610    request,611    0,612    null,613    rootFormatContext,614    // Root segments are never embedded in Text on either edge615    false,616    false,617  );618  // There is no parent so conceptually, we're unblocked to flush this segment.619  rootSegment.parentFlushed = true;620  const rootTask = createRenderTask(621    request,622    null,623    children,624    -1,625    null,626    rootSegment,627    null,628    null,629    request.abortableTasks,630    null,631    rootFormatContext,632    rootContextSnapshot,633    emptyTreeContext,634    null,635    null,636    emptyContextObject,637    null,638  );639  pushComponentStack(rootTask);640  request.pingedTasks.push(rootTask);641  return request;642}643644export function createPrerenderRequest(645  children: ReactNodeList,646  resumableState: ResumableState,647  renderState: RenderState,648  rootFormatContext: FormatContext,649  progressiveChunkSize: void | number,650  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),651  onAllReady: void | (() => void),652  onShellReady: void | (() => void),653  onShellError: void | ((error: mixed) => void),654  onFatalError: void | ((error: mixed) => void),655): Request {656  const request = createRequest(657    children,658    resumableState,659    renderState,660    rootFormatContext,661    progressiveChunkSize,662    onError,663    onAllReady,664    onShellReady,665    onShellError,666    onFatalError,667    undefined,668  );669  // Start tracking postponed holes during this render.670  request.trackedPostpones = {671    workingMap: new Map(),672    rootNodes: [],673    rootSlots: null,674  };675  return request;676}677678export function resumeRequest(679  children: ReactNodeList,680  postponedState: PostponedState,681  renderState: RenderState,682  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),683  onAllReady: void | (() => void),684  onShellReady: void | (() => void),685  onShellError: void | ((error: mixed) => void),686  onFatalError: void | ((error: mixed) => void),687): Request {688  if (__DEV__) {689    resetOwnerStackLimit();690  }691692  // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors693  const request: Request = new RequestInstance(694    postponedState.resumableState,695    renderState,696    postponedState.rootFormatContext,697    postponedState.progressiveChunkSize,698    onError,699    onAllReady,700    onShellReady,701    onShellError,702    onFatalError,703    null,704  );705  request.nextSegmentId = postponedState.nextSegmentId;706707  if (typeof postponedState.replaySlots === 'number') {708    // We have a resume slot at the very root. This is effectively just a full rerender.709    const rootSegment = createPendingSegment(710      request,711      0,712      null,713      postponedState.rootFormatContext,714      // Root segments are never embedded in Text on either edge715      false,716      false,717    );718    // There is no parent so conceptually, we're unblocked to flush this segment.719    rootSegment.parentFlushed = true;720    const rootTask = createRenderTask(721      request,722      null,723      children,724      -1,725      null,726      rootSegment,727      null,728      null,729      request.abortableTasks,730      null,731      postponedState.rootFormatContext,732      rootContextSnapshot,733      emptyTreeContext,734      null,735      null,736      emptyContextObject,737      null,738    );739    pushComponentStack(rootTask);740    request.pingedTasks.push(rootTask);741    return request;742  }743744  const replay: ReplaySet = {745    nodes: postponedState.replayNodes,746    slots: postponedState.replaySlots,747    pendingTasks: 0,748  };749  const rootTask = createReplayTask(750    request,751    null,752    replay,753    children,754    -1,755    null,756    null,757    request.abortableTasks,758    null,759    postponedState.rootFormatContext,760    rootContextSnapshot,761    emptyTreeContext,762    null,763    null,764    emptyContextObject,765    null,766  );767  pushComponentStack(rootTask);768  request.pingedTasks.push(rootTask);769  return request;770}771772export function resumeAndPrerenderRequest(773  children: ReactNodeList,774  postponedState: PostponedState,775  renderState: RenderState,776  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),777  onAllReady: void | (() => void),778  onShellReady: void | (() => void),779  onShellError: void | ((error: mixed) => void),780  onFatalError: void | ((error: mixed) => void),781): Request {782  const request = resumeRequest(783    children,784    postponedState,785    renderState,786    onError,787    onAllReady,788    onShellReady,789    onShellError,790    onFatalError,791  );792  // Start tracking postponed holes during this render.793  request.trackedPostpones = {794    workingMap: new Map(),795    rootNodes: [],796    rootSlots: null,797  };798  return request;799}800801let currentRequest: null | Request = null;802803export function resolveRequest(): null | Request {804  if (currentRequest) return currentRequest;805  // $FlowFixMe[constant-condition]806  if (supportsRequestStorage) {807    const store = requestStorage.getStore();808    if (store) return store;809  }810  return null;811}812813function pingTask(request: Request, task: Task): void {814  const pingedTasks = request.pingedTasks;815  pingedTasks.push(task);816  // $FlowFixMe[constant-condition]817  if (isWorkLoopExternallyDriven) {818    return;819  } else {820    if (request.pingedTasks.length === 1) {821      request.flushScheduled = request.destination !== null;822      if (request.trackedPostpones !== null || request.status === OPENING) {823        scheduleMicrotask(() => performWork(request));824      } else {825        scheduleWork(() => performWork(request));826      }827    }828  }829}830831function pingRejectedTask(request: Request, task: Task, error: mixed): void {832  if (!request.aborted) {833    // Replaying the task is what gives ordinary render errors their complete834    // component stack.835    pingTask(request, task);836    return;837  }838  if (!task.abortSet.delete(task)) {839    // finishAbort already completed this task with the request's abort reason.840    return;841  }842  // abortTask synchronously claimed this task before abort listeners could843  // reject its wakeable. Finish it with the more specific reason before the844  // scheduled final abort uses the reason for the whole request.845  if (__DEV__) {846    finishAbortedTaskDEV(task, request, error);847  } else {848    finishAbortedTask(task, request, error);849  }850}851852function createSuspenseBoundary(853  request: Request,854  row: null | SuspenseListRow,855  fallbackAbortableTasks: Set<Task>,856  preamble: null | Preamble,857  defer: boolean,858): SuspenseBoundary {859  const boundary: SuspenseBoundary = {860    status: PENDING,861    rootSegmentID: -1,862    parentFlushed: false,863    pendingTasks: 0,864    row: row,865    completedSegments: [],866    byteSize: 0,867    defer: defer,868    fallbackAbortableTasks,869    errorDigest: null,870    contentState: createHoistableState(),871    fallbackState: createHoistableState(),872    preamble,873    tracked: null,874  };875  if (__DEV__) {876    // DEV-only fields for hidden class877    boundary.errorMessage = null;878    boundary.errorStack = null;879    boundary.errorComponentStack = null;880  }881  if (row !== null) {882    // This boundary will block this row from completing.883    row.pendingTasks++;884    const blockedBoundaries = row.boundaries;885    if (blockedBoundaries !== null) {886      // Previous rows will block this boundary itself from completing.887      request.allPendingTasks++;888      boundary.pendingTasks++;889      blockedBoundaries.push(boundary);890    }891    const inheritedHoistables = row.inheritedHoistables;892    if (inheritedHoistables !== null) {893      hoistHoistables(boundary.contentState, inheritedHoistables);894    }895  }896  return boundary;897}898899function createRenderTask(900  request: Request,901  thenableState: ThenableState | null,902  node: ReactNodeList,903  childIndex: number,904  blockedBoundary: Root | SuspenseBoundary,905  blockedSegment: Segment,906  blockedPreamble: null | PreambleState,907  hoistableState: null | HoistableState,908  abortSet: Set<Task>,909  keyPath: Root | KeyNode,910  formatContext: FormatContext,911  context: ContextSnapshot,912  treeContext: TreeContext,913  row: null | SuspenseListRow,914  componentStack: null | ComponentStackNode,915  legacyContext: LegacyContext,916  debugTask: null | ConsoleTask,917): RenderTask {918  request.allPendingTasks++;919  if (blockedBoundary === null) {920    request.pendingRootTasks++;921  } else {922    blockedBoundary.pendingTasks++;923  }924  if (row !== null) {925    row.pendingTasks++;926  }927  const task: RenderTask = {928    replay: null,929    node,930    childIndex,931    ping: {932      resolve: () => pingTask(request, task),933      reject: error => pingRejectedTask(request, task, error),934    },935    blockedBoundary,936    blockedSegment,937    blockedPreamble,938    hoistableState,939    abortSet,940    keyPath,941    formatContext,942    context,943    treeContext,944    row,945    componentStack,946    thenableState,947  } as any;948  if (!disableLegacyContext) {949    task.legacyContext = legacyContext;950  }951  if (__DEV__) {952    task.debugTask = debugTask;953  }954  abortSet.add(task);955  return task;956}957958function createReplayTask(959  request: Request,960  thenableState: ThenableState | null,961  replay: ReplaySet,962  node: ReactNodeList,963  childIndex: number,964  blockedBoundary: Root | SuspenseBoundary,965  hoistableState: null | HoistableState,966  abortSet: Set<Task>,967  keyPath: Root | KeyNode,968  formatContext: FormatContext,969  context: ContextSnapshot,970  treeContext: TreeContext,971  row: null | SuspenseListRow,972  componentStack: null | ComponentStackNode,973  legacyContext: LegacyContext,974  debugTask: null | ConsoleTask,975): ReplayTask {976  request.allPendingTasks++;977  if (blockedBoundary === null) {978    request.pendingRootTasks++;979  } else {980    blockedBoundary.pendingTasks++;981  }982  if (row !== null) {983    row.pendingTasks++;984  }985  replay.pendingTasks++;986  const task: ReplayTask = {987    replay,988    node,989    childIndex,990    ping: {991      resolve: () => pingTask(request, task),992      reject: error => pingRejectedTask(request, task, error),993    },994    blockedBoundary,995    blockedSegment: null,996    blockedPreamble: null,997    hoistableState,998    abortSet,999    keyPath,1000    formatContext,1001    context,1002    treeContext,1003    row,1004    componentStack,1005    thenableState,1006  } as any;1007  if (!disableLegacyContext) {1008    task.legacyContext = legacyContext;1009  }1010  if (__DEV__) {1011    task.debugTask = debugTask;1012  }1013  abortSet.add(task);1014  return task;1015}10161017function createPendingSegment(1018  request: Request,1019  index: number,1020  boundary: null | SuspenseBoundary,1021  parentFormatContext: FormatContext,1022  lastPushedText: boolean,1023  textEmbedded: boolean,1024): Segment {1025  return {1026    status: PENDING,1027    parentFlushed: false,1028    id: -1, // lazily assigned later1029    index,1030    chunks: [],1031    children: [],1032    preambleChildren: [],1033    parentFormatContext,1034    boundary,1035    lastPushedText,1036    textEmbedded,1037  };1038}10391040function getCurrentStackInDEV(): string {1041  if (__DEV__) {1042    if (currentTaskInDEV === null || currentTaskInDEV.componentStack === null) {1043      return '';1044    }1045    return getOwnerStackByComponentStackNodeInDev(1046      currentTaskInDEV.componentStack,1047    );1048  }1049  return '';1050}10511052function getStackFromNode(stackNode: ComponentStackNode): string {1053  return getStackByComponentStackNode(stackNode);1054}10551056function pushHaltedAwaitOnComponentStack(1057  task: Task,1058  debugInfo: void | null | ReactDebugInfo,1059): void {1060  if (!__DEV__) {1061    // eslint-disable-next-line react-internal/prod-error-codes1062    throw new Error(1063      'pushHaltedAwaitOnComponentStack should never be called in production. This is a bug in React.',1064    );1065  }1066  if (debugInfo != null) {1067    for (let i = debugInfo.length - 1; i >= 0; i--) {1068      const info = debugInfo[i];1069      if (info.awaited != null) {1070        const asyncInfo: ReactAsyncInfo = info as any;1071        const bestStack =1072          asyncInfo.debugStack == null ? asyncInfo.awaited : asyncInfo;1073        if (bestStack.debugStack !== undefined) {1074          task.componentStack = {1075            parent: task.componentStack,1076            type: asyncInfo,1077            owner: bestStack.owner,1078            stack: bestStack.debugStack,1079          };1080          task.debugTask = bestStack.debugTask as any;1081          break;1082        }1083      }1084    }1085  }1086}10871088// performWork + retryTask without mutation1089function rerenderStalledTask(request: Request, task: Task): void {1090  const prevStatus = request.status;1091  const prevAborted = request.aborted;1092  request.status = STALLED_DEV;1093  // This diagnostic replay must reach the suspended call site instead of1094  // taking the abort path.1095  request.aborted = false;10961097  const prevContext = getActiveContext();1098  const prevDispatcher = ReactSharedInternals.H;1099  ReactSharedInternals.H = HooksDispatcher;1100  const prevAsyncDispatcher = ReactSharedInternals.A;1101  ReactSharedInternals.A = DefaultAsyncDispatcher;11021103  const prevRequest = currentRequest;1104  currentRequest = request;11051106  const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;1107  ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;11081109  const prevResumableState = currentResumableState;1110  setCurrentResumableState(request.resumableState);1111  switchContext(task.context);1112  const prevTaskInDEV = currentTaskInDEV;1113  setCurrentTaskInDEV(task);1114  try {1115    retryNode(request, task);1116  } catch (x) {1117    // Suspended again.1118    resetHooksState();1119  } finally {1120    setCurrentTaskInDEV(prevTaskInDEV);1121    setCurrentResumableState(prevResumableState);11221123    ReactSharedInternals.H = prevDispatcher;1124    ReactSharedInternals.A = prevAsyncDispatcher;11251126    ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;1127    if (prevDispatcher === HooksDispatcher) {1128      // This means that we were in a reentrant work loop. This could happen1129      // in a renderer that supports synchronous work like renderToString,1130      // when it's called from within another renderer.1131      // Normally we don't bother switching the contexts to their root/default1132      // values when leaving because we'll likely need the same or similar1133      // context again. However, when we're inside a synchronous loop like this1134      // we'll to restore the context to what it was before returning.1135      switchContext(prevContext);1136    }1137    currentRequest = prevRequest;1138    request.status = prevStatus;1139    request.aborted = prevAborted;1140  }1141}11421143function pushSuspendedCallSiteOnComponentStack(1144  request: Request,1145  task: Task,1146): void {1147  setCaptureSuspendedCallSiteDEV(true);1148  const restoreThenableState = ensureSuspendableThenableStateDEV(1149    // refined at the callsite1150    task.thenableState as any as ThenableState,1151  );1152  try {1153    rerenderStalledTask(request, task);1154  } finally {1155    restoreThenableState();1156    setCaptureSuspendedCallSiteDEV(false);1157  }11581159  const suspendCallSiteStack = getSuspendedCallSiteStackDEV();1160  const suspendCallSiteDebugTask = getSuspendedCallSiteDebugTaskDEV();11611162  if (suspendCallSiteStack !== null) {1163    const ownerStack = task.componentStack;1164    task.componentStack = {1165      // The owner of the suspended call site would be the owner of this task.1166      // We need the task itself otherwise we'd miss a frame.1167      owner: ownerStack,1168      parent: suspendCallSiteStack.parent,1169      stack: suspendCallSiteStack.stack,1170      type: suspendCallSiteStack.type,1171    };1172  }1173  task.debugTask = suspendCallSiteDebugTask;1174}11751176function pushServerComponentStack(1177  task: Task,1178  debugInfo: void | null | ReactDebugInfo,1179): void {1180  if (!__DEV__) {1181    // eslint-disable-next-line react-internal/prod-error-codes1182    throw new Error(1183      'pushServerComponentStack should never be called in production. This is a bug in React.',1184    );1185  }1186  // Build a Server Component parent stack from the debugInfo.1187  if (debugInfo != null) {1188    const stack: ReactDebugInfo = debugInfo;1189    for (let i = 0; i < stack.length; i++) {1190      const componentInfo: ReactComponentInfo = stack[i] as any;1191      if (typeof componentInfo.name !== 'string') {1192        continue;1193      }1194      if (componentInfo.debugStack === undefined) {1195        continue;1196      }1197      task.componentStack = {1198        parent: task.componentStack,1199        type: componentInfo,1200        owner: componentInfo.owner,1201        stack: componentInfo.debugStack,1202      };1203      task.debugTask = componentInfo.debugTask as any;1204    }1205  }1206}12071208function pushComponentStack(task: Task): void {1209  const node = task.node;1210  // Create the Component Stack frame for the element we're about to try.1211  // It's unfortunate that we need to do this refinement twice. Once for1212  // the stack frame and then once again while actually1213  if (typeof node === 'object' && node !== null) {1214    switch ((node as any).$$typeof) {1215      case REACT_ELEMENT_TYPE: {1216        const element: any = node;1217        const type = element.type;1218        const owner = __DEV__ ? element._owner : null;1219        const stack = __DEV__ ? element._debugStack : null;1220        if (__DEV__) {1221          pushServerComponentStack(task, element._debugInfo);1222          task.debugTask = element._debugTask;1223        }1224        task.componentStack = createComponentStackFromType(1225          task.componentStack,1226          type,1227          owner,1228          stack,1229        );1230        break;1231      }1232      case REACT_LAZY_TYPE: {1233        if (__DEV__) {1234          const lazyNode: LazyComponentType<any, any> = node as any;1235          pushServerComponentStack(task, lazyNode._debugInfo);1236        }1237        break;1238      }1239      default: {1240        if (__DEV__) {1241          const maybeUsable: Object = node;1242          if (typeof maybeUsable.then === 'function') {1243            const thenable: Thenable<ReactNodeList> = maybeUsable as any;1244            pushServerComponentStack(task, thenable._debugInfo);1245          }1246        }1247      }1248    }1249  }1250}12511252function createComponentStackFromType(1253  parent: null | ComponentStackNode,1254  type: Function | string | symbol,1255  owner: void | null | ReactComponentInfo | ComponentStackNode, // DEV only1256  stack: void | null | string | Error, // DEV only1257): ComponentStackNode {1258  if (__DEV__) {1259    return {1260      parent,1261      type,1262      owner,1263      stack,1264    };1265  }1266  return {1267    parent,1268    type,1269  };1270}12711272function replaceSuspenseComponentStackWithSuspenseFallbackStack(1273  componentStack: null | ComponentStackNode,1274): null | ComponentStackNode {1275  if (componentStack === null) {1276    return null;1277  }1278  return createComponentStackFromType(1279    componentStack.parent,1280    'Suspense Fallback',1281    __DEV__ ? componentStack.owner : null,1282    __DEV__ ? componentStack.stack : null,1283  );1284}12851286type ThrownInfo = {1287  componentStack?: string,1288};1289export type ErrorInfo = ThrownInfo;12901291function getThrownInfo(node: null | ComponentStackNode): ThrownInfo {1292  const errorInfo: ThrownInfo = {};1293  if (node) {1294    Object.defineProperty(errorInfo, 'componentStack', {1295      configurable: true,1296      enumerable: true,1297      get() {1298        // Lazyily generate the stack since it's expensive.1299        const stack = getStackFromNode(node);1300        Object.defineProperty(errorInfo, 'componentStack', {1301          value: stack,1302        });1303        return stack;1304      },1305    });1306  }1307  return errorInfo;1308}13091310function encodeErrorForBoundary(1311  boundary: SuspenseBoundary,1312  digest: ?string,1313  error: mixed,1314  thrownInfo: ThrownInfo,1315  wasAborted: boolean,1316) {1317  boundary.errorDigest = digest;1318  if (__DEV__) {1319    let message, stack;1320    // In dev we additionally encode the error message and component stack on the boundary1321    if (error instanceof Error) {1322      // eslint-disable-next-line react-internal/safe-string-coercion1323      message = String(error.message);1324      // eslint-disable-next-line react-internal/safe-string-coercion1325      stack = String(error.stack);1326    } else if (typeof error === 'object' && error !== null) {1327      message = describeObjectForErrorMessage(error);1328      stack = null;1329    } else {1330      // eslint-disable-next-line react-internal/safe-string-coercion1331      message = String(error);1332      stack = null;1333    }1334    const prefix = wasAborted1335      ? 'Switched to client rendering because the server rendering aborted due to:\n\n'1336      : 'Switched to client rendering because the server rendering errored:\n\n';1337    boundary.errorMessage = prefix + message;1338    boundary.errorStack = stack !== null ? prefix + stack : null;1339    boundary.errorComponentStack = thrownInfo.componentStack;1340  }1341}13421343function logRecoverableError(1344  request: Request,1345  error: any,1346  errorInfo: ThrownInfo,1347  debugTask: null | ConsoleTask,1348): ?string {1349  // If this callback errors, we intentionally let that error bubble up to become a fatal error1350  // so that someone fixes the error reporting instead of hiding it.1351  const onError = request.onError;1352  const errorDigest =1353    __DEV__ && debugTask1354      ? debugTask.run(onError.bind(null, error, errorInfo))1355      : onError(error, errorInfo);1356  if (errorDigest != null && typeof errorDigest !== 'string') {1357    // We used to throw here but since this gets called from a variety of unprotected places it1358    // seems better to just warn and discard the returned value.1359    if (__DEV__) {1360      console.error(1361        'onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type "%s" instead',1362        typeof errorDigest,1363      );1364    }1365    return;1366  }1367  return errorDigest;1368}13691370function fatalError(1371  request: Request,1372  error: mixed,1373  errorInfo: ThrownInfo,1374  debugTask: null | ConsoleTask,1375): void {1376  // This is called outside error handling code such as if the root errors outside1377  // a suspense boundary or if the root suspense boundary's fallback errors.1378  // It's also called if React itself or its host configs errors.1379  const onShellError = request.onShellError;1380  const onFatalError = request.onFatalError;1381  if (__DEV__ && debugTask) {1382    debugTask.run(onShellError.bind(null, error));1383    debugTask.run(onFatalError.bind(null, error));1384  } else {1385    onShellError(error);1386    onFatalError(error);1387  }1388  if (request.destination !== null) {1389    request.status = CLOSED;1390    closeWithError(request.destination, error);1391  } else {1392    request.status = CLOSING;1393    request.fatalError = error;1394  }1395}13961397function renderSuspenseBoundary(1398  request: Request,1399  someTask: Task,1400  keyPath: KeyNode,1401  props: SuspenseProps,1402): void {1403  if (someTask.replay !== null) {1404    // If we're replaying through this pass, it means we're replaying through1405    // an already completed Suspense boundary. It's too late to do anything about it1406    // so we can just render through it.1407    const prevKeyPath = someTask.keyPath;1408    const prevContext = someTask.formatContext;1409    const prevRow = someTask.row;1410    someTask.keyPath = keyPath;1411    someTask.formatContext = getSuspenseContentFormatContext(1412      request.resumableState,1413      prevContext,1414    );1415    someTask.row = null;1416    const content: ReactNodeList = props.children;1417    try {1418      renderNode(request, someTask, content, -1);1419    } finally {1420      someTask.keyPath = prevKeyPath;1421      someTask.formatContext = prevContext;1422      someTask.row = prevRow;1423    }1424    return;1425  }1426  // $FlowFixMe[incompatible-type]: Refined.1427  const task: RenderTask = someTask;14281429  const prevKeyPath = task.keyPath;1430  const prevContext = task.formatContext;1431  const prevRow = task.row;1432  const parentBoundary = task.blockedBoundary;1433  const parentPreamble = task.blockedPreamble;1434  const parentHoistableState = task.hoistableState;1435  const parentSegment = task.blockedSegment;14361437  // Each time we enter a suspense boundary, we split out into a new segment for1438  // the fallback so that we can later replace that segment with the content.1439  // This also lets us split out the main content even if it doesn't suspend,1440  // in case it ends up generating a large subtree of content.1441  const fallback: ReactNodeList = props.fallback;1442  const content: ReactNodeList = props.children;1443  const defer: boolean = enableCPUSuspense && props.defer === true;14441445  const fallbackAbortSet: Set<Task> = new Set();1446  const newBoundary = createSuspenseBoundary(1447    request,1448    task.row,1449    fallbackAbortSet,1450    canHavePreamble(task.formatContext) ? createPreamble() : null,1451    defer,1452  );14531454  const insertionIndex = parentSegment.chunks.length;1455  // The children of the boundary segment is actually the fallback.1456  const boundarySegment = createPendingSegment(1457    request,1458    insertionIndex,1459    newBoundary,1460    task.formatContext,1461    // boundaries never require text embedding at their edges because comment nodes bound them1462    false,1463    false,1464  );1465  parentSegment.children.push(boundarySegment);1466  // The parentSegment has a child Segment at this index so we reset the lastPushedText marker on the parent1467  parentSegment.lastPushedText = false;14681469  // This segment is the actual child content. We can start rendering that immediately.1470  const contentRootSegment = createPendingSegment(1471    request,1472    0,1473    null,1474    task.formatContext,1475    // boundaries never require text embedding at their edges because comment nodes bound them1476    false,1477    false,1478  );1479  // We mark the root segment as having its parent flushed. It's not really flushed but there is1480  // no parent segment so there's nothing to wait on.1481  contentRootSegment.parentFlushed = true;14821483  const trackedPostpones = request.trackedPostpones;1484  if (trackedPostpones !== null || defer) {1485    // This is a prerender or deferred boundary. In this mode we want to render the fallback synchronously1486    // and schedule the content to render later. This is the opposite of what we do during a normal render1487    // where we try to skip rendering the fallback if the content itself can render synchronously14881489    // Stash the original stack frame.1490    const suspenseComponentStack = task.componentStack;14911492    const fallbackKeyPath: KeyNode = [1493      keyPath[0],1494      'Suspense Fallback',1495      keyPath[2],1496    ];1497    if (trackedPostpones !== null) {1498      const fallbackReplayNode: ReplayNode = [1499        fallbackKeyPath[1],1500        fallbackKeyPath[2],1501        [] as Array<ReplayNode>,1502        null,1503      ];1504      trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode);1505      newBoundary.tracked = {1506        contentKeyPath: keyPath,1507        // We are rendering the fallback before the boundary content so we keep track of1508        // the fallback replay node until we determine if the primary content suspends1509        fallbackNode: fallbackReplayNode,1510      };1511    }15121513    task.blockedSegment = boundarySegment;1514    task.blockedPreamble =1515      newBoundary.preamble === null ? null : newBoundary.preamble.fallback;1516    task.keyPath = fallbackKeyPath;1517    task.formatContext = getSuspenseFallbackFormatContext(1518      request.resumableState,1519      prevContext,1520    );1521    task.componentStack =1522      replaceSuspenseComponentStackWithSuspenseFallbackStack(1523        suspenseComponentStack,1524      );1525    try {1526      renderNode(request, task, fallback, -1);1527      pushSegmentFinale(1528        boundarySegment.chunks,1529        request.renderState,1530        boundarySegment.lastPushedText,1531        boundarySegment.textEmbedded,1532      );1533      boundarySegment.status = COMPLETED;1534      finishedSegment(request, parentBoundary, boundarySegment);1535    } catch (thrownValue: mixed) {1536      if (request.aborted) {1537        boundarySegment.status = ABORTED;1538      } else {1539        boundarySegment.status = ERRORED;1540      }1541      throw thrownValue;1542    } finally {1543      task.blockedSegment = parentSegment;1544      task.blockedPreamble = parentPreamble;1545      task.keyPath = prevKeyPath;1546      task.formatContext = prevContext;1547    }15481549    // We create a suspended task for the primary content because we want to allow1550    // sibling fallbacks to be rendered first.1551    const suspendedPrimaryTask = createRenderTask(1552      request,1553      null,1554      content,1555      -1,1556      newBoundary,1557      contentRootSegment,1558      newBoundary.preamble === null ? null : newBoundary.preamble.content,1559      newBoundary.contentState,1560      task.abortSet,1561      keyPath,1562      getSuspenseContentFormatContext(1563        request.resumableState,1564        task.formatContext,1565      ),1566      task.context,1567      task.treeContext,1568      null, // The row gets reset inside the Suspense boundary.1569      suspenseComponentStack,1570      !disableLegacyContext ? task.legacyContext : emptyContextObject,1571      __DEV__ ? task.debugTask : null,1572    );1573    pushComponentStack(suspendedPrimaryTask);1574    request.pingedTasks.push(suspendedPrimaryTask);1575  } else {1576    // This is a normal render. We will attempt to synchronously render the boundary content1577    // If it is successful we will elide the fallback task but if it suspends or errors we schedule1578    // the fallback to render. Unlike with prerenders we attempt to deprioritize the fallback render15791580    // Currently this is running synchronously. We could instead schedule this to pingedTasks.1581    // I suspect that there might be some efficiency benefits from not creating the suspended task1582    // and instead just using the stack if possible.1583    // TODO: Call this directly instead of messing with saving and restoring contexts.15841585    // We can reuse the current context and task to render the content immediately without1586    // context switching. We just need to temporarily switch which boundary and which segment1587    // we're writing to. If something suspends, it'll spawn new suspended task with that context.1588    task.blockedBoundary = newBoundary;1589    task.blockedPreamble =1590      newBoundary.preamble === null ? null : newBoundary.preamble.content;1591    task.hoistableState = newBoundary.contentState;1592    task.blockedSegment = contentRootSegment;1593    task.keyPath = keyPath;1594    task.formatContext = getSuspenseContentFormatContext(1595      request.resumableState,1596      prevContext,1597    );1598    task.row = null;1599    try {1600      // We use the safe form because we don't handle suspending here. Only error handling.1601      renderNode(request, task, content, -1);1602      pushSegmentFinale(1603        contentRootSegment.chunks,1604        request.renderState,1605        contentRootSegment.lastPushedText,1606        contentRootSegment.textEmbedded,1607      );1608      contentRootSegment.status = COMPLETED;1609      finishedSegment(request, newBoundary, contentRootSegment);1610      queueCompletedSegment(newBoundary, contentRootSegment);1611      if (newBoundary.pendingTasks === 0 && newBoundary.status === PENDING) {1612        // This must have been the last segment we were waiting on. This boundary is now complete.1613        newBoundary.status = COMPLETED;1614        // Therefore we won't need the fallback. We early return so that we don't have to create1615        // the fallback. However, if this boundary ended up big enough to be eligible for outlining1616        // we can't do that because we might still need the fallback if we outline it.1617        if (!isEligibleForOutlining(request, newBoundary)) {1618          if (prevRow !== null) {1619            // If we have synchronously completed the boundary and it's not eligible for outlining1620            // then we don't have to wait for it to be flushed before we unblock future rows.1621            // This lets us inline small rows in order.1622            if (--prevRow.pendingTasks === 0) {1623              finishSuspenseListRow(request, prevRow);1624            }1625          }1626          if (request.pendingRootTasks === 0 && task.blockedPreamble) {1627            // The root is complete and this boundary may contribute part of the preamble.1628            // We eagerly attempt to prepare the preamble here because we expect most requests1629            // to have few boundaries which contribute preambles and it allow us to do this1630            // preparation work during the work phase rather than the when flushing.1631            preparePreamble(request);1632          }1633          return;1634        }1635      } else {1636        const boundaryRow = prevRow;1637        if (boundaryRow !== null && boundaryRow.together) {1638          tryToResolveTogetherRow(request, boundaryRow);1639        }1640      }1641    } catch (thrownValue: mixed) {1642      newBoundary.status = CLIENT_RENDERED;1643      let error: mixed;1644      if (request.aborted) {1645        contentRootSegment.status = ABORTED;1646        error = request.fatalError;1647      } else {1648        contentRootSegment.status = ERRORED;1649        error = thrownValue;1650      }16511652      const thrownInfo = getThrownInfo(task.componentStack);1653      const errorDigest = logRecoverableError(1654        request,1655        error,1656        thrownInfo,1657        __DEV__ ? task.debugTask : null,1658      );1659      encodeErrorForBoundary(1660        newBoundary,1661        errorDigest,1662        error,1663        thrownInfo,1664        false,1665      );16661667      untrackBoundary(request, newBoundary);16681669      // We don't need to decrement any task numbers because we didn't spawn any new task.1670      // We don't need to schedule any task because we know the parent has written yet.1671      // We do need to fallthrough to create the fallback though.1672    } finally {1673      task.blockedBoundary = parentBoundary;1674      task.blockedPreamble = parentPreamble;1675      task.hoistableState = parentHoistableState;1676      task.blockedSegment = parentSegment;1677      task.keyPath = prevKeyPath;1678      task.formatContext = prevContext;1679      task.row = prevRow;1680    }16811682    const fallbackKeyPath: KeyNode = [1683      keyPath[0],1684      'Suspense Fallback',1685      keyPath[2],1686    ];1687    // We create suspended task for the fallback because we don't want to actually work1688    // on it yet in case we finish the main content, so we queue for later.1689    const suspendedFallbackTask = createRenderTask(1690      request,1691      null,1692      fallback,1693      -1,1694      parentBoundary,1695      boundarySegment,1696      newBoundary.preamble === null ? null : newBoundary.preamble.fallback,1697      newBoundary.fallbackState,1698      fallbackAbortSet,1699      fallbackKeyPath,1700      getSuspenseFallbackFormatContext(1701        request.resumableState,1702        task.formatContext,1703      ),1704      task.context,1705      task.treeContext,1706      task.row,1707      replaceSuspenseComponentStackWithSuspenseFallbackStack(1708        task.componentStack,1709      ),1710      !disableLegacyContext ? task.legacyContext : emptyContextObject,1711      __DEV__ ? task.debugTask : null,1712    );1713    pushComponentStack(suspendedFallbackTask);1714    // TODO: This should be queued at a separate lower priority queue so that we only work1715    // on preparing fallbacks if we don't have any more main content to task on.1716    request.pingedTasks.push(suspendedFallbackTask);1717  }1718}17191720function replaySuspenseBoundary(1721  request: Request,1722  task: ReplayTask,1723  keyPath: KeyNode,1724  props: Object,1725  id: number,1726  childNodes: Array<ReplayNode>,1727  childSlots: ResumeSlots,1728  fallbackNodes: Array<ReplayNode>,1729  fallbackSlots: ResumeSlots,1730): void {1731  const prevKeyPath = task.keyPath;1732  const prevContext = task.formatContext;1733  const prevRow = task.row;1734  const previousReplaySet: ReplaySet = task.replay;17351736  const parentBoundary = task.blockedBoundary;1737  const parentHoistableState = task.hoistableState;17381739  const content: ReactNodeList = props.children;1740  const fallback: ReactNodeList = props.fallback;1741  const defer: boolean = enableCPUSuspense && props.defer === true;17421743  const fallbackAbortSet: Set<Task> = new Set();1744  const resumedBoundary = createSuspenseBoundary(1745    request,1746    task.row,1747    fallbackAbortSet,1748    canHavePreamble(task.formatContext) ? createPreamble() : null,1749    defer,1750  );1751  resumedBoundary.parentFlushed = true;1752  // We restore the same id of this boundary as was used during prerender.1753  resumedBoundary.rootSegmentID = id;17541755  // We can reuse the current context and task to render the content immediately without1756  // context switching. We just need to temporarily switch which boundary and replay node1757  // we're writing to. If something suspends, it'll spawn new suspended task with that context.1758  task.blockedBoundary = resumedBoundary;1759  task.hoistableState = resumedBoundary.contentState;1760  task.keyPath = keyPath;1761  task.formatContext = getSuspenseContentFormatContext(1762    request.resumableState,1763    prevContext,1764  );1765  task.row = null;1766  task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};17671768  try {1769    // We use the safe form because we don't handle suspending here. Only error handling.1770    renderNode(request, task, content, -1);17711772    if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {1773      throw new Error(1774        "Couldn't find all resumable slots by key/index during replaying. " +1775          "The tree doesn't match so React will fallback to client rendering.",1776      );1777    }1778    task.replay.pendingTasks--;1779    if (1780      resumedBoundary.pendingTasks === 0 &&1781      resumedBoundary.status === PENDING1782    ) {1783      // This must have been the last segment we were waiting on. This boundary is now complete.1784      // Therefore we won't need the fallback. We early return so that we don't have to create1785      // the fallback.1786      resumedBoundary.status = COMPLETED;1787      request.completedBoundaries.push(resumedBoundary);1788      // We restore the parent componentStack. Semantically this is the same as1789      // popComponentStack(task) but we do this instead because it should be slightly1790      // faster1791      return;1792    }1793  } catch (thrownValue: mixed) {1794    resumedBoundary.status = CLIENT_RENDERED;1795    const error = request.aborted ? request.fatalError : thrownValue;1796    const thrownInfo = getThrownInfo(task.componentStack);1797    const errorDigest = logRecoverableError(1798      request,1799      error,1800      thrownInfo,1801      __DEV__ ? task.debugTask : null,1802    );1803    encodeErrorForBoundary(1804      resumedBoundary,1805      errorDigest,1806      error,1807      thrownInfo,1808      false,1809    );18101811    task.replay.pendingTasks--;18121813    // The parent already flushed in the prerender so we need to schedule this to be emitted.1814    request.clientRenderedBoundaries.push(resumedBoundary);18151816    // We don't need to decrement any task numbers because we didn't spawn any new task.1817    // We don't need to schedule any task because we know the parent has written yet.1818    // We do need to fallthrough to create the fallback though.1819  } finally {1820    task.blockedBoundary = parentBoundary;1821    task.hoistableState = parentHoistableState;1822    task.replay = previousReplaySet;1823    task.keyPath = prevKeyPath;1824    task.formatContext = prevContext;1825    task.row = prevRow;1826  }18271828  const fallbackKeyPath: KeyNode = [1829    keyPath[0],1830    'Suspense Fallback',1831    keyPath[2],1832  ];18331834  // We create suspended task for the fallback because we don't want to actually work1835  // on it yet in case we finish the main content, so we queue for later.1836  const fallbackReplay = {1837    nodes: fallbackNodes,1838    slots: fallbackSlots,1839    pendingTasks: 0,1840  };1841  const suspendedFallbackTask = createReplayTask(1842    request,1843    null,1844    fallbackReplay,1845    fallback,1846    -1,1847    parentBoundary,1848    resumedBoundary.fallbackState,1849    fallbackAbortSet,1850    fallbackKeyPath,1851    getSuspenseFallbackFormatContext(1852      request.resumableState,1853      task.formatContext,1854    ),1855    task.context,1856    task.treeContext,1857    task.row,1858    replaceSuspenseComponentStackWithSuspenseFallbackStack(task.componentStack),1859    !disableLegacyContext ? task.legacyContext : emptyContextObject,1860    __DEV__ ? task.debugTask : null,1861  );18621863  pushComponentStack(suspendedFallbackTask);1864  // TODO: This should be queued at a separate lower priority queue so that we only work1865  // on preparing fallbacks if we don't have any more main content to task on.1866  request.pingedTasks.push(suspendedFallbackTask);1867}18681869function finishSuspenseListRow(request: Request, row: SuspenseListRow): void {1870  // This row finished. Now we have to unblock all the next rows that were blocked on this.1871  unblockSuspenseListRow(request, row.next, row.hoistables);1872}18731874function unblockSuspenseListRow(1875  request: Request,1876  unblockedRow: null | SuspenseListRow,1877  inheritedHoistables: null | HoistableState,1878): void {1879  // We do this in a loop to avoid stack overflow for very long lists that get unblocked.1880  while (unblockedRow !== null) {1881    if (inheritedHoistables !== null) {1882      // Hoist any hoistables from the previous row into the next row so that it can be1883      // later transferred to all the rows.1884      hoistHoistables(unblockedRow.hoistables, inheritedHoistables);1885      // Mark the row itself for any newly discovered Suspense boundaries to inherit.1886      // This is different from hoistables because that also includes hoistables from1887      // all the boundaries below this row and not just previous rows.1888      unblockedRow.inheritedHoistables = inheritedHoistables;1889    }1890    // Unblocking the boundaries will decrement the count of this row but we keep it above1891    // zero so they never finish this row recursively.1892    const unblockedBoundaries = unblockedRow.boundaries;1893    if (unblockedBoundaries !== null) {1894      unblockedRow.boundaries = null;1895      for (let i = 0; i < unblockedBoundaries.length; i++) {1896        const unblockedBoundary = unblockedBoundaries[i];1897        if (inheritedHoistables !== null) {1898          hoistHoistables(unblockedBoundary.contentState, inheritedHoistables);1899        }1900        finishedTask(request, unblockedBoundary, null, null);1901      }1902    }1903    // Instead we decrement at the end to keep it all in this loop.1904    unblockedRow.pendingTasks--;1905    if (unblockedRow.pendingTasks > 0) {1906      // Still blocked.1907      break;1908    }1909    inheritedHoistables = unblockedRow.hoistables;1910    unblockedRow = unblockedRow.next;1911  }1912}19131914function trackPostponedSuspenseListRow(1915  request: Request,1916  trackedPostpones: PostponedHoles,1917  postponedRow: null | SuspenseListRow,1918): void {1919  // TODO: Because we unconditionally call this, it will be called by finishedTask1920  // and so ends up recursive which can lead to stack overflow for very long lists.1921  if (postponedRow !== null) {1922    const postponedBoundaries = postponedRow.boundaries;1923    if (postponedBoundaries !== null) {1924      postponedRow.boundaries = null;1925      for (let i = 0; i < postponedBoundaries.length; i++) {1926        const postponedBoundary = postponedBoundaries[i];1927        trackPostponedBoundary(request, trackedPostpones, postponedBoundary);1928        finishedTask(request, postponedBoundary, null, null);1929      }1930    }1931  }1932}19331934function tryToResolveTogetherRow(1935  request: Request,1936  togetherRow: SuspenseListRow,1937): void {1938  // If we have a "together" row and all the pendingTasks are really the boundaries themselves,1939  // and we won't outline any of them then we can unblock this row early so that we can inline1940  // all the boundaries at once.1941  const boundaries = togetherRow.boundaries;1942  if (boundaries === null || togetherRow.pendingTasks !== boundaries.length) {1943    return;1944  }1945  let allCompleteAndInlinable = true;1946  for (let i = 0; i < boundaries.length; i++) {1947    const rowBoundary = boundaries[i];1948    if (1949      rowBoundary.pendingTasks !== 1 ||1950      rowBoundary.parentFlushed ||1951      isEligibleForOutlining(request, rowBoundary)1952    ) {1953      allCompleteAndInlinable = false;1954      break;1955    }1956  }1957  if (allCompleteAndInlinable) {1958    unblockSuspenseListRow(request, togetherRow, togetherRow.hoistables);1959  }1960}19611962function createSuspenseListRow(1963  previousRow: null | SuspenseListRow,1964): SuspenseListRow {1965  const newRow: SuspenseListRow = {1966    pendingTasks: 1, // At first the row is blocked on attempting rendering itself.1967    boundaries: null,1968    hoistables: createHoistableState(),1969    inheritedHoistables: null,1970    together: false,1971    next: null,1972  };1973  if (previousRow !== null && previousRow.pendingTasks > 0) {1974    // If the previous row is not done yet, we add ourselves to be blocked on it.1975    // When it finishes, we'll decrement our pending tasks.1976    newRow.pendingTasks++;1977    newRow.boundaries = [];1978    previousRow.next = newRow;1979  }1980  return newRow;1981}19821983function renderSuspenseListRows(1984  request: Request,1985  task: Task,1986  keyPath: KeyNode,1987  rows: Array<ReactNodeList>,1988  revealOrder: void | 'forwards' | 'backwards' | 'unstable_legacy-backwards',1989): void {1990  // This is a fork of renderChildrenArray that's aware of tracking rows.1991  const prevKeyPath = task.keyPath;1992  const prevTreeContext = task.treeContext;1993  const prevRow = task.row;1994  const previousComponentStack = task.componentStack;1995  let previousDebugTask = null;1996  if (__DEV__) {1997    previousDebugTask = task.debugTask;1998    // We read debugInfo from task.node.props.children instead of rows because it1999    // might have been an unwrapped iterable so we read from the original node.2000    pushServerComponentStack(

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.