packages/react-server/src/ReactFizzServer.js JAVASCRIPT 6,707 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 6,707.
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  createRecoverableError,138  isRecoverableError,139  cloneRecoverableErrorAsFatal,140} from './ReactFizzHooks';141import {DefaultAsyncDispatcher} from './ReactFizzAsyncDispatcher';142import {143  getStackByComponentStackNode,144  getOwnerStackByComponentStackNodeInDev,145} from './ReactFizzComponentStack';146import {emptyTreeContext, pushTreeContext} from './ReactFizzTreeContext';147import {currentTaskInDEV, setCurrentTaskInDEV} from './ReactFizzCurrentTask';148import {149  callLazyInitInDEV,150  callComponentInDEV,151  callRenderInDEV,152} from './ReactFizzCallUserSpace';153import {154  getViewTransitionClassName,155  getViewTransitionName,156} from './ReactFizzViewTransitionComponent';157158import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';159import {160  getIteratorFn,161  ASYNC_ITERATOR,162  REACT_ELEMENT_TYPE,163  REACT_PORTAL_TYPE,164  REACT_LAZY_TYPE,165  REACT_SUSPENSE_TYPE,166  REACT_LEGACY_HIDDEN_TYPE,167  REACT_STRICT_MODE_TYPE,168  REACT_PROFILER_TYPE,169  REACT_SUSPENSE_LIST_TYPE,170  REACT_FRAGMENT_TYPE,171  REACT_FORWARD_REF_TYPE,172  REACT_MEMO_TYPE,173  REACT_CONTEXT_TYPE,174  REACT_CONSUMER_TYPE,175  REACT_SCOPE_TYPE,176  REACT_VIEW_TRANSITION_TYPE,177  REACT_ACTIVITY_TYPE,178  REACT_OPTIMISTIC_KEY,179  REACT_RECOVERABLE_TYPE,180} from 'shared/ReactSymbols';181import ReactSharedInternals from 'shared/ReactSharedInternals';182import {183  disableLegacyContext,184  disableLegacyContextForFunctionComponents,185  enableScopeAPI,186  enableAsyncIterableChildren,187  enableViewTransition,188  enableViewTransitionParentEnterExit,189  enableFizzBlockingRender,190  enableAsyncDebugInfo,191  enableCPUSuspense,192} from 'shared/ReactFeatureFlags';193194import assign from 'shared/assign';195import noop from 'shared/noop';196import getComponentNameFromType from 'shared/getComponentNameFromType';197import isArray from 'shared/isArray';198import {REACT_RECOVERABLE_DIGEST} from 'shared/ReactRecoverable';199import {200  SuspenseException,201  getSuspendedThenable,202  ensureSuspendableThenableStateDEV,203  getSuspendedCallSiteStackDEV,204  getSuspendedCallSiteDebugTaskDEV,205  setCaptureSuspendedCallSiteDEV,206} from './ReactFizzThenable';207208// Linked list representing the identity of a component given the component/tag name and key.209// The name might be minified but we assume that it's going to be the same generated name. Typically210// because it's just the same compiled output in practice.211export type KeyNode = [212  Root | KeyNode /* parent */,213  string | null /* name */,214  string | number /* key */,215];216217type ResumeSlots =218  | null // nothing to resume219  | number // resume with segment ID at the root position220  | {[index: number]: number}; // resume with segmentID at the index221222type ReplaySuspenseBoundary = [223  string | null /* name */,224  string | number /* key */,225  Array<ReplayNode> /* content keyed children */,226  ResumeSlots /* content resumable slots */,227  null | ReplayNode /* fallback content */,228  number /* rootSegmentID */,229];230231type ReplayNode =232  | [233      string | null /* name */,234      string | number /* key */,235      Array<ReplayNode> /* keyed children */,236      ResumeSlots /* resumable slots */,237    ]238  | ReplaySuspenseBoundary;239240type PostponedHoles = {241  workingMap: Map<KeyNode, ReplayNode>,242  rootNodes: Array<ReplayNode>,243  rootSlots: ResumeSlots,244};245246type LegacyContext = {247  [key: string]: any,248};249250type SuspenseListRow = {251  pendingTasks: number, // The number of tasks, previous rows and inner suspense boundaries blocking this row.252  boundaries: null | Array<SuspenseBoundary>, // The boundaries in this row waiting to be unblocked by the previous row. (null means this row is not blocked)253  hoistables: HoistableState, // Any dependencies that this row depends on. Future rows need to also depend on it.254  inheritedHoistables: null | HoistableState, // Any dependencies that previous row depend on, that new boundaries of this row needs.255  together: boolean, // All the boundaries within this row must be revealed together.256  next: null | SuspenseListRow, // The next row blocked by this one.257};258259const CLIENT_RENDERED = 4; // if it errors or infinitely suspends260261type SuspenseBoundary = {262  status: 0 | 1 | 4 | 5,263  rootSegmentID: number,264  parentFlushed: boolean,265  pendingTasks: number, // when it reaches zero we can show this boundary's content266  row: null | SuspenseListRow, // the row that this boundary blocks from completing.267  completedSegments: Array<Segment>, // completed but not yet flushed segments.268  byteSize: number, // used to determine whether to inline children boundaries.269  defer: boolean, // never inline deferred boundaries270  fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.271  contentState: HoistableState,272  fallbackState: HoistableState,273  preamble: null | Preamble,274  tracked: null | {275    contentKeyPath: null | KeyNode, // used to track the path for replay nodes276    fallbackNode: null | ReplayNode, // used to track the fallback for replay nodes277  },278  errorDigest: ?string, // the error hash if it errors279  // DEV-only fields280  errorMessage?: null | string, // the error string if it errors281  errorStack?: null | string, // the error stack if it errors282  errorComponentStack?: null | string, // the error component stack if it errors283};284285type Ping = {286  resolve: () => void,287  reject: (error: mixed) => void,288};289290type RenderTask = {291  replay: null,292  node: ReactNodeList,293  childIndex: number,294  ping: Ping,295  blockedBoundary: Root | SuspenseBoundary,296  blockedSegment: Segment, // the segment we'll write to297  blockedPreamble: null | PreambleState,298  hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary299  abortSet: Set<Task>, // the abortable set that this task belongs to300  keyPath: Root | KeyNode, // the path of all parent keys currently rendering301  formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)302  context: ContextSnapshot, // the current new context that this task is executing in303  treeContext: TreeContext, // the current tree context that this task is executing in304  row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside305  componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component306  thenableState: null | ThenableState,307  legacyContext: LegacyContext, // the current legacy context that this task is executing in308  debugTask: null | ConsoleTask, // DEV only309  // DON'T ANY MORE FIELDS. We at 16 in prod already which otherwise requires converting to a constructor.310  // Consider splitting into multiple objects or consolidating some fields.311};312313type ReplaySet = {314  nodes: Array<ReplayNode>, // the possible paths to follow down the replaying315  slots: ResumeSlots, // slots to resume316  pendingTasks: number, // tracks the number of tasks currently tracking this set of nodes317  // if pending tasks reach zero but there are still nodes left, it means we couldn't find318  // them all in the tree, so we need to abort and client render the boundary.319};320321type ReplayTask = {322  replay: ReplaySet,323  node: ReactNodeList,324  childIndex: number,325  ping: Ping,326  blockedBoundary: Root | SuspenseBoundary,327  blockedSegment: null, // we don't write to anything when we replay328  blockedPreamble: null,329  hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary330  abortSet: Set<Task>, // the abortable set that this task belongs to331  keyPath: Root | KeyNode, // the path of all parent keys currently rendering332  formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)333  context: ContextSnapshot, // the current new context that this task is executing in334  treeContext: TreeContext, // the current tree context that this task is executing in335  row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside336  componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component337  thenableState: null | ThenableState,338  legacyContext: LegacyContext, // the current legacy context that this task is executing in339  debugTask: null | ConsoleTask, // DEV only340};341342export type Task = RenderTask | ReplayTask;343344const PENDING = 0;345const COMPLETED = 1;346const FLUSHED = 2;347const ABORTED = 3;348const ERRORED = 4;349const POSTPONED = 5;350351type Root = null;352353type Segment = {354  status: 0 | 1 | 2 | 3 | 4 | 5,355  parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed356  id: number, // starts as 0 and is lazily assigned if the parent flushes early357  +index: number, // the index within the parent's chunks or 0 at the root358  +chunks: Array<Chunk | PrecomputedChunk>,359  +children: Array<Segment>,360  +preambleChildren: Array<Segment>,361  // The context that this segment was created in.362  parentFormatContext: FormatContext,363  // If this segment represents a fallback, this is the content that will replace that fallback.364  boundary: null | SuspenseBoundary,365  // used to discern when text separator boundaries are needed366  lastPushedText: boolean,367  textEmbedded: boolean,368};369370// The ordering of these statuses matters. OPENING and OPEN are the only371// statuses in which newly scheduled work may be performed. Any status greater372// than OPEN represents a request that no longer admits work.373const OPENING = 10;374const OPEN = 11;375const CLOSING = 12;376const CLOSED = 13;377const STALLED_DEV = 14;378379// Passed to renderLifetimeController.abort(). Nothing reads the reason, but a380// call to abort() without one constructs an AbortError DOMException. Capturing381// the stack trace dominates that cost, and the cost grows with the depth of the382// stack.383const RENDER_ENDED = 'The render ended.';384385export opaque type Request = {386  destination: null | Destination,387  flushScheduled: boolean,388  +resumableState: ResumableState,389  +renderState: RenderState,390  +rootFormatContext: FormatContext,391  +progressiveChunkSize: number,392  status: 10 | 11 | 12 | 13 | 14,393  fatalError: mixed,394  aborted: boolean,395  nextSegmentId: number,396  allPendingTasks: number, // when it reaches zero, we can close the connection.397  pendingRootTasks: number, // when this reaches zero, we've finished at least the root boundary.398  completedRootSegment: null | Segment, // Completed but not yet flushed root segments.399  completedPreambleSegments: null | Array<Array<Segment>>, // contains the ready-to-flush segments that make up the preamble400  byteSize: number, // counts the number of bytes accumulated in the shell401  abortableTasks: Set<Task>,402  pingedTasks: Array<Task>, // High priority tasks that should be worked on first.403  currentTask: null | Task, // The task currently executing in this request.404  // Queues to flush in order of priority405  clientRenderedBoundaries: Array<SuspenseBoundary>, // Errored or client rendered but not yet flushed.406  completedBoundaries: Array<SuspenseBoundary>, // Completed but not yet fully flushed boundaries to show.407  partialBoundaries: Array<SuspenseBoundary>, // Partially completed boundaries that can flush its segments early.408  trackedPostpones: null | PostponedHoles, // Gets set to non-null while we want to track postponed holes. I.e. during a prerender.409  // While prerendering a postponed request that produced a real shell, this410  // holds the PostponedState returned by getPostponedState. getPostponedState411  // snapshots nextSegmentId before the (pull-driven) prelude flush runs, but the412  // flush outlines completed boundaries and advances nextSegmentId past that413  // snapshot. We finalize the snapshot from flushCompletedQueues so the resumed414  // render allocates segment ids strictly above the shell's; otherwise the shell415  // and resume emit duplicate B:/S: ids once concatenated. Stays null for live416  // renders and resumes.417  postponedState: null | PostponedState,418  // onError is called when an error happens anywhere in the tree. It might recover.419  // The return string is used in production  primarily to avoid leaking internals, secondarily to save bytes.420  // Returning null/undefined will cause a default error message in production421  onError: (error: mixed, errorInfo: ThrownInfo) => ?string,422  // onBrowserBailout is called when Fizz recovers by intentionally deferring423  // rendering to the browser.424  onBrowserBailout: (error: mixed, errorInfo: ThrownInfo) => void,425  // onAllReady is called when all pending task is done but it may not have flushed yet.426  // This is a good time to start writing if you want only HTML and no intermediate steps.427  onAllReady: () => void,428  // onShellReady is called when there is at least a root fallback ready to show.429  // Typically you don't need this callback because it's best practice to always have a430  // root fallback ready so there's no need to wait.431  onShellReady: () => void,432  // onShellError is called when the shell didn't complete. That means you probably want to433  // emit a different response to the stream instead.434  onShellError: (error: mixed) => void,435  onFatalError: (error: mixed) => void,436  // Aborted once the render ends, whether it completed, failed fatally or was437  // aborted. Bounds the lifetime of anything that must not outlive the render.438  // Null until attachAbortSignal creates it, so a render that is given no439  // signal constructs no controller.440  renderLifetimeController: null | AbortController,441  // Form state that was the result of an MPA submission, if it was provided.442  formState: null | ReactFormState<any, any>,443  // DEV-only, warning dedupe444  didWarnForKey?: null | WeakSet<ComponentStackNode>,445};446447type Preamble = {448  content: PreambleState,449  fallback: PreambleState,450};451452function createPreamble(): Preamble {453  return {454    content: createPreambleState(),455    fallback: createPreambleState(),456  };457}458459// This is a default heuristic for how to split up the HTML content into progressive460// loading. Our goal is to be able to display additional new content about every 500ms.461// Faster than that is unnecessary and should be throttled on the client. It also462// adds unnecessary overhead to do more splits. We don't know if it's a higher or lower463// end device but higher end suffer less from the overhead than lower end does from464// not getting small enough pieces. We error on the side of low end.465// We base this on low end 3G speeds which is about 500kbits per second. We assume466// that there can be a reasonable drop off from max bandwidth which leaves you with467// as little as 80%. We can receive half of that each 500ms - at best. In practice,468// a little bandwidth is lost to processing and contention - e.g. CSS and images that469// are downloaded along with the main content. So we estimate about half of that to be470// the lower end throughput. In other words, we expect that you can at least show471// about 12.5kb of content per 500ms. Not counting starting latency for the first472// paint.473// 500 * 1024 / 8 * .8 * 0.5 / 2474const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800;475476function getBlockingRenderMaxSize(request: Request): number {477  // We want to make sure that we can block the reveal of a well designed complete478  // shell but if you have constructed a too large shell (e.g. by not adding any479  // Suspense boundaries) then that might take too long to render. We shouldn't480  // punish users (or overzealous metrics tracking) in that scenario.481  // There's a trade off here. If this limit is too low then you can't fit a482  // reasonably well built UI within it without getting errors. If it's too high483  // then things that accidentally fall below it might take too long to load.484  // Web Vitals target 1.8 seconds for first paint and our goal to have the limit485  // be fast enough to hit that. For this argument we assume that most external486  // resources are already cached because it's a return visit, or inline styles.487  // If it's not, then it's highly unlikely that any render blocking instructions488  // we add has any impact what so ever on the paint.489  // Assuming a first byte of about 600ms which is kind of bad but common with a490  // decent static host. If it's longer e.g. due to dynamic rendering, then you491  // are going to bound by dynamic production of the content and you're better off492  // with Suspense boundaries anyway. This number doesn't matter much. Then you493  // have about 1.2 seconds left for bandwidth. On 3G that gives you about 112.5kb494  // worth of data. That's worth about 10x in terms of uncompressed bytes. Then we495  // half that just to account for longer latency, slower bandwidth and CPU processing.496  // Now we're down to about 500kb. In fact, looking at metrics we've collected with497  // rel="expect" examples and other documents, the impact on documents smaller than498  // that is within the noise. That's because there's enough happening within that499  // start up to not make HTML streaming not significantly better.500  // Content above the fold tends to be about 100-200kb tops. Therefore 500kb should501  // be enough head room for a good loading state. After that you should use502  // Suspense or SuspenseList to improve it.503  // Since this is highly related to the reason you would adjust the504  // progressiveChunkSize option, and always has to be higher, we define this limit505  // in terms of it. So if you want to increase the limit because you have high506  // bandwidth users, then you can adjust it up. If you are concerned about even507  // slower bandwidth then you can adjust it down.508  return request.progressiveChunkSize * 40; // 512kb by default.509}510511function isEligibleForOutlining(512  request: Request,513  boundary: SuspenseBoundary,514): boolean {515  // For very small boundaries, don't bother producing a fallback for outlining.516  // The larger this limit is, the more we can save on preparing fallbacks in case we end up517  // outlining.518  return (519    (boundary.byteSize > 500 ||520      hasSuspenseyContent(boundary.contentState, /* flushingInShell */ false) ||521      boundary.defer) &&522    // For boundaries that can possibly contribute to the preamble we don't want to outline523    // them regardless of their size since the fallbacks should only be emitted if we've524    // errored the boundary.525    boundary.preamble === null526  );527}528529function defaultErrorHandler(error: mixed) {530  if (531    typeof error === 'object' &&532    error !== null &&533    typeof error.environmentName === 'string'534  ) {535    // This was a Server error. We print the environment name in a badge just like we do with536    // replays of console logs to indicate that the source of this throw as actually the Server.537    bindToConsole('error', [error], error.environmentName)();538  } else {539    console['error'](error); // Don't transform to our wrapper540  }541  return null;542}543544function RequestInstance(545  this: $FlowFixMe,546  resumableState: ResumableState,547  renderState: RenderState,548  rootFormatContext: FormatContext,549  progressiveChunkSize: void | number,550  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),551  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),552  onAllReady: void | (() => void),553  onShellReady: void | (() => void),554  onShellError: void | ((error: mixed) => void),555  onFatalError: void | ((error: mixed) => void),556  formState: void | null | ReactFormState<any, any>,557) {558  const pingedTasks: Array<Task> = [];559  const abortSet: Set<Task> = new Set();560  this.destination = null;561  this.flushScheduled = false;562  this.resumableState = resumableState;563  this.renderState = renderState;564  this.rootFormatContext = rootFormatContext;565  this.progressiveChunkSize =566    progressiveChunkSize === undefined567      ? DEFAULT_PROGRESSIVE_CHUNK_SIZE568      : progressiveChunkSize;569  // $FlowFixMe[constant-condition]570  this.status = isWorkLoopExternallyDriven ? OPEN : OPENING;571  this.fatalError = null;572  this.aborted = false;573  this.nextSegmentId = 0;574  this.allPendingTasks = 0;575  this.pendingRootTasks = 0;576  this.completedRootSegment = null;577  this.completedPreambleSegments = null;578  this.byteSize = 0;579  this.abortableTasks = abortSet;580  this.pingedTasks = pingedTasks;581  this.currentTask = null;582  this.clientRenderedBoundaries = [] as Array<SuspenseBoundary>;583  this.completedBoundaries = [] as Array<SuspenseBoundary>;584  this.partialBoundaries = [] as Array<SuspenseBoundary>;585  this.trackedPostpones = null;586  this.postponedState = null;587  this.onError = onError === undefined ? defaultErrorHandler : onError;588  this.onBrowserBailout =589    onBrowserBailout === undefined ? noop : onBrowserBailout;590  this.onAllReady = onAllReady === undefined ? noop : onAllReady;591  this.onShellReady = onShellReady === undefined ? noop : onShellReady;592  this.onShellError = onShellError === undefined ? noop : onShellError;593  this.onFatalError = onFatalError === undefined ? noop : onFatalError;594  this.renderLifetimeController = null;595  this.formState = formState === undefined ? null : formState;596  if (__DEV__) {597    this.didWarnForKey = null;598  }599}600601export function createRequest(602  children: ReactNodeList,603  resumableState: ResumableState,604  renderState: RenderState,605  rootFormatContext: FormatContext,606  progressiveChunkSize: void | number,607  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),608  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),609  onAllReady: void | (() => void),610  onShellReady: void | (() => void),611  onShellError: void | ((error: mixed) => void),612  onFatalError: void | ((error: mixed) => void),613  formState: void | null | ReactFormState<any, any>,614): Request {615  if (__DEV__) {616    resetOwnerStackLimit();617  }618619  // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors620  const request: Request = new RequestInstance(621    resumableState,622    renderState,623    rootFormatContext,624    progressiveChunkSize,625    onError,626    onBrowserBailout,627    onAllReady,628    onShellReady,629    onShellError,630    onFatalError,631    formState,632  );633634  // This segment represents the root fallback.635  const rootSegment = createPendingSegment(636    request,637    0,638    null,639    rootFormatContext,640    // Root segments are never embedded in Text on either edge641    false,642    false,643  );644  // There is no parent so conceptually, we're unblocked to flush this segment.645  rootSegment.parentFlushed = true;646  const rootTask = createRenderTask(647    request,648    null,649    children,650    -1,651    null,652    rootSegment,653    null,654    null,655    request.abortableTasks,656    null,657    rootFormatContext,658    rootContextSnapshot,659    emptyTreeContext,660    null,661    null,662    emptyContextObject,663    null,664  );665  pushComponentStack(rootTask);666  request.pingedTasks.push(rootTask);667  return request;668}669670export function createPrerenderRequest(671  children: ReactNodeList,672  resumableState: ResumableState,673  renderState: RenderState,674  rootFormatContext: FormatContext,675  progressiveChunkSize: void | number,676  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),677  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),678  onAllReady: void | (() => void),679  onShellReady: void | (() => void),680  onShellError: void | ((error: mixed) => void),681  onFatalError: void | ((error: mixed) => void),682): Request {683  const request = createRequest(684    children,685    resumableState,686    renderState,687    rootFormatContext,688    progressiveChunkSize,689    onError,690    onBrowserBailout,691    onAllReady,692    onShellReady,693    onShellError,694    onFatalError,695    undefined,696  );697  // Start tracking postponed holes during this render.698  request.trackedPostpones = {699    workingMap: new Map(),700    rootNodes: [],701    rootSlots: null,702  };703  return request;704}705706export function resumeRequest(707  children: ReactNodeList,708  postponedState: PostponedState,709  renderState: RenderState,710  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),711  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),712  onAllReady: void | (() => void),713  onShellReady: void | (() => void),714  onShellError: void | ((error: mixed) => void),715  onFatalError: void | ((error: mixed) => void),716): Request {717  if (__DEV__) {718    resetOwnerStackLimit();719  }720721  // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors722  const request: Request = new RequestInstance(723    postponedState.resumableState,724    renderState,725    postponedState.rootFormatContext,726    postponedState.progressiveChunkSize,727    onError,728    onBrowserBailout,729    onAllReady,730    onShellReady,731    onShellError,732    onFatalError,733    null,734  );735  request.nextSegmentId = postponedState.nextSegmentId;736737  if (typeof postponedState.replaySlots === 'number') {738    // We have a resume slot at the very root. This is effectively just a full rerender.739    const rootSegment = createPendingSegment(740      request,741      0,742      null,743      postponedState.rootFormatContext,744      // Root segments are never embedded in Text on either edge745      false,746      false,747    );748    // There is no parent so conceptually, we're unblocked to flush this segment.749    rootSegment.parentFlushed = true;750    const rootTask = createRenderTask(751      request,752      null,753      children,754      -1,755      null,756      rootSegment,757      null,758      null,759      request.abortableTasks,760      null,761      postponedState.rootFormatContext,762      rootContextSnapshot,763      emptyTreeContext,764      null,765      null,766      emptyContextObject,767      null,768    );769    pushComponentStack(rootTask);770    request.pingedTasks.push(rootTask);771    return request;772  }773774  const replay: ReplaySet = {775    nodes: postponedState.replayNodes,776    slots: postponedState.replaySlots,777    pendingTasks: 0,778  };779  const rootTask = createReplayTask(780    request,781    null,782    replay,783    children,784    -1,785    null,786    null,787    request.abortableTasks,788    null,789    postponedState.rootFormatContext,790    rootContextSnapshot,791    emptyTreeContext,792    null,793    null,794    emptyContextObject,795    null,796  );797  pushComponentStack(rootTask);798  request.pingedTasks.push(rootTask);799  return request;800}801802export function resumeAndPrerenderRequest(803  children: ReactNodeList,804  postponedState: PostponedState,805  renderState: RenderState,806  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),807  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),808  onAllReady: void | (() => void),809  onShellReady: void | (() => void),810  onShellError: void | ((error: mixed) => void),811  onFatalError: void | ((error: mixed) => void),812): Request {813  const request = resumeRequest(814    children,815    postponedState,816    renderState,817    onError,818    onBrowserBailout,819    onAllReady,820    onShellReady,821    onShellError,822    onFatalError,823  );824  // Start tracking postponed holes during this render.825  request.trackedPostpones = {826    workingMap: new Map(),827    rootNodes: [],828    rootSlots: null,829  };830  return request;831}832833let currentRequest: null | Request = null;834835export function resolveRequest(): null | Request {836  if (currentRequest) return currentRequest;837  // $FlowFixMe[constant-condition]838  if (supportsRequestStorage) {839    const store = requestStorage.getStore();840    if (store) return store;841  }842  return null;843}844845function pingTask(request: Request, task: Task): void {846  const pingedTasks = request.pingedTasks;847  pingedTasks.push(task);848  // $FlowFixMe[constant-condition]849  if (isWorkLoopExternallyDriven) {850    return;851  } else {852    if (request.pingedTasks.length === 1) {853      request.flushScheduled = request.destination !== null;854      if (request.trackedPostpones !== null || request.status === OPENING) {855        scheduleMicrotask(() => performWork(request));856      } else {857        scheduleWork(() => performWork(request));858      }859    }860  }861}862863function pingRejectedTask(request: Request, task: Task, error: mixed): void {864  if (!request.aborted) {865    // Replaying the task is what gives ordinary render errors their complete866    // component stack.867    pingTask(request, task);868    return;869  }870  if (!task.abortSet.delete(task)) {871    // finishAbort already completed this task with the request's abort reason.872    return;873  }874  // abortTask synchronously claimed this task before abort listeners could875  // reject its wakeable. Finish it with the more specific reason before the876  // scheduled final abort uses the reason for the whole request.877  if (__DEV__) {878    finishAbortedTaskDEV(task, request, error);879  } else {880    finishAbortedTask(task, request, error);881  }882}883884function createSuspenseBoundary(885  request: Request,886  row: null | SuspenseListRow,887  fallbackAbortableTasks: Set<Task>,888  preamble: null | Preamble,889  defer: boolean,890): SuspenseBoundary {891  const boundary: SuspenseBoundary = {892    status: PENDING,893    rootSegmentID: -1,894    parentFlushed: false,895    pendingTasks: 0,896    row: row,897    completedSegments: [],898    byteSize: 0,899    defer: defer,900    fallbackAbortableTasks,901    errorDigest: null,902    contentState: createHoistableState(),903    fallbackState: createHoistableState(),904    preamble,905    tracked: null,906  };907  if (__DEV__) {908    // DEV-only fields for hidden class909    boundary.errorMessage = null;910    boundary.errorStack = null;911    boundary.errorComponentStack = null;912  }913  if (row !== null) {914    // This boundary will block this row from completing.915    row.pendingTasks++;916    const blockedBoundaries = row.boundaries;917    if (blockedBoundaries !== null) {918      // Previous rows will block this boundary itself from completing.919      request.allPendingTasks++;920      boundary.pendingTasks++;921      blockedBoundaries.push(boundary);922    }923    const inheritedHoistables = row.inheritedHoistables;924    if (inheritedHoistables !== null) {925      hoistHoistables(boundary.contentState, inheritedHoistables);926    }927  }928  return boundary;929}930931function createRenderTask(932  request: Request,933  thenableState: ThenableState | null,934  node: ReactNodeList,935  childIndex: number,936  blockedBoundary: Root | SuspenseBoundary,937  blockedSegment: Segment,938  blockedPreamble: null | PreambleState,939  hoistableState: null | HoistableState,940  abortSet: Set<Task>,941  keyPath: Root | KeyNode,942  formatContext: FormatContext,943  context: ContextSnapshot,944  treeContext: TreeContext,945  row: null | SuspenseListRow,946  componentStack: null | ComponentStackNode,947  legacyContext: LegacyContext,948  debugTask: null | ConsoleTask,949): RenderTask {950  request.allPendingTasks++;951  if (blockedBoundary === null) {952    request.pendingRootTasks++;953  } else {954    blockedBoundary.pendingTasks++;955  }956  if (row !== null) {957    row.pendingTasks++;958  }959  const task: RenderTask = {960    replay: null,961    node,962    childIndex,963    ping: {964      resolve: () => pingTask(request, task),965      reject: error => pingRejectedTask(request, task, error),966    },967    blockedBoundary,968    blockedSegment,969    blockedPreamble,970    hoistableState,971    abortSet,972    keyPath,973    formatContext,974    context,975    treeContext,976    row,977    componentStack,978    thenableState,979  } as any;980  if (!disableLegacyContext) {981    task.legacyContext = legacyContext;982  }983  if (__DEV__) {984    task.debugTask = debugTask;985  }986  abortSet.add(task);987  return task;988}989990function createReplayTask(991  request: Request,992  thenableState: ThenableState | null,993  replay: ReplaySet,994  node: ReactNodeList,995  childIndex: number,996  blockedBoundary: Root | SuspenseBoundary,997  hoistableState: null | HoistableState,998  abortSet: Set<Task>,999  keyPath: Root | KeyNode,1000  formatContext: FormatContext,1001  context: ContextSnapshot,1002  treeContext: TreeContext,1003  row: null | SuspenseListRow,1004  componentStack: null | ComponentStackNode,1005  legacyContext: LegacyContext,1006  debugTask: null | ConsoleTask,1007): ReplayTask {1008  request.allPendingTasks++;1009  if (blockedBoundary === null) {1010    request.pendingRootTasks++;1011  } else {1012    blockedBoundary.pendingTasks++;1013  }1014  if (row !== null) {1015    row.pendingTasks++;1016  }1017  replay.pendingTasks++;1018  const task: ReplayTask = {1019    replay,1020    node,1021    childIndex,1022    ping: {1023      resolve: () => pingTask(request, task),1024      reject: error => pingRejectedTask(request, task, error),1025    },1026    blockedBoundary,1027    blockedSegment: null,1028    blockedPreamble: null,1029    hoistableState,1030    abortSet,1031    keyPath,1032    formatContext,1033    context,1034    treeContext,1035    row,1036    componentStack,1037    thenableState,1038  } as any;1039  if (!disableLegacyContext) {1040    task.legacyContext = legacyContext;1041  }1042  if (__DEV__) {1043    task.debugTask = debugTask;1044  }1045  abortSet.add(task);1046  return task;1047}10481049function createPendingSegment(1050  request: Request,1051  index: number,1052  boundary: null | SuspenseBoundary,1053  parentFormatContext: FormatContext,1054  lastPushedText: boolean,1055  textEmbedded: boolean,1056): Segment {1057  return {1058    status: PENDING,1059    parentFlushed: false,1060    id: -1, // lazily assigned later1061    index,1062    chunks: [],1063    children: [],1064    preambleChildren: [],1065    parentFormatContext,1066    boundary,1067    lastPushedText,1068    textEmbedded,1069  };1070}10711072function getCurrentStackInDEV(): string {1073  if (__DEV__) {1074    if (currentTaskInDEV === null || currentTaskInDEV.componentStack === null) {1075      return '';1076    }1077    return getOwnerStackByComponentStackNodeInDev(1078      currentTaskInDEV.componentStack,1079    );1080  }1081  return '';1082}10831084function getStackFromNode(stackNode: ComponentStackNode): string {1085  return getStackByComponentStackNode(stackNode);1086}10871088function pushHaltedAwaitOnComponentStack(1089  task: Task,1090  debugInfo: void | null | ReactDebugInfo,1091): void {1092  if (!__DEV__) {1093    // eslint-disable-next-line react-internal/prod-error-codes1094    throw new Error(1095      'pushHaltedAwaitOnComponentStack should never be called in production. This is a bug in React.',1096    );1097  }1098  if (debugInfo != null) {1099    for (let i = debugInfo.length - 1; i >= 0; i--) {1100      const info = debugInfo[i];1101      if (info.awaited != null) {1102        const asyncInfo: ReactAsyncInfo = info as any;1103        const bestStack =1104          asyncInfo.debugStack == null ? asyncInfo.awaited : asyncInfo;1105        if (bestStack.debugStack !== undefined) {1106          task.componentStack = {1107            parent: task.componentStack,1108            type: asyncInfo,1109            owner: bestStack.owner,1110            stack: bestStack.debugStack,1111          };1112          task.debugTask = bestStack.debugTask as any;1113          break;1114        }1115      }1116    }1117  }1118}11191120// performWork + retryTask without mutation1121function rerenderStalledTask(request: Request, task: Task): void {1122  const prevStatus = request.status;1123  const prevAborted = request.aborted;1124  request.status = STALLED_DEV;1125  // This diagnostic replay must reach the suspended call site instead of1126  // taking the abort path.1127  request.aborted = false;11281129  const prevContext = getActiveContext();1130  const prevDispatcher = ReactSharedInternals.H;1131  ReactSharedInternals.H = HooksDispatcher;1132  const prevAsyncDispatcher = ReactSharedInternals.A;1133  ReactSharedInternals.A = DefaultAsyncDispatcher;11341135  const prevRequest = currentRequest;1136  currentRequest = request;11371138  const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;1139  ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;11401141  const prevResumableState = currentResumableState;1142  setCurrentResumableState(request.resumableState);1143  switchContext(task.context);1144  const prevTaskInDEV = currentTaskInDEV;1145  setCurrentTaskInDEV(task);1146  try {1147    retryNode(request, task);1148  } catch (x) {1149    // Suspended again.1150    resetHooksState();1151  } finally {1152    setCurrentTaskInDEV(prevTaskInDEV);1153    setCurrentResumableState(prevResumableState);11541155    ReactSharedInternals.H = prevDispatcher;1156    ReactSharedInternals.A = prevAsyncDispatcher;11571158    ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;1159    if (prevDispatcher === HooksDispatcher) {1160      // This means that we were in a reentrant work loop. This could happen1161      // in a renderer that supports synchronous work like renderToString,1162      // when it's called from within another renderer.1163      // Normally we don't bother switching the contexts to their root/default1164      // values when leaving because we'll likely need the same or similar1165      // context again. However, when we're inside a synchronous loop like this1166      // we'll to restore the context to what it was before returning.1167      switchContext(prevContext);1168    }1169    currentRequest = prevRequest;1170    request.status = prevStatus;1171    request.aborted = prevAborted;1172  }1173}11741175function pushSuspendedCallSiteOnComponentStack(1176  request: Request,1177  task: Task,1178): void {1179  setCaptureSuspendedCallSiteDEV(true);1180  const restoreThenableState = ensureSuspendableThenableStateDEV(1181    // refined at the callsite1182    task.thenableState as any as ThenableState,1183  );1184  try {1185    rerenderStalledTask(request, task);1186  } finally {1187    restoreThenableState();1188    setCaptureSuspendedCallSiteDEV(false);1189  }11901191  const suspendCallSiteStack = getSuspendedCallSiteStackDEV();1192  const suspendCallSiteDebugTask = getSuspendedCallSiteDebugTaskDEV();11931194  if (suspendCallSiteStack !== null) {1195    const ownerStack = task.componentStack;1196    task.componentStack = {1197      // The owner of the suspended call site would be the owner of this task.1198      // We need the task itself otherwise we'd miss a frame.1199      owner: ownerStack,1200      parent: suspendCallSiteStack.parent,1201      stack: suspendCallSiteStack.stack,1202      type: suspendCallSiteStack.type,1203    };1204  }1205  task.debugTask = suspendCallSiteDebugTask;1206}12071208function pushServerComponentStack(1209  task: Task,1210  debugInfo: void | null | ReactDebugInfo,1211): void {1212  if (!__DEV__) {1213    // eslint-disable-next-line react-internal/prod-error-codes1214    throw new Error(1215      'pushServerComponentStack should never be called in production. This is a bug in React.',1216    );1217  }1218  // Build a Server Component parent stack from the debugInfo.1219  if (debugInfo != null) {1220    const stack: ReactDebugInfo = debugInfo;1221    for (let i = 0; i < stack.length; i++) {1222      const componentInfo: ReactComponentInfo = stack[i] as any;1223      if (typeof componentInfo.name !== 'string') {1224        continue;1225      }1226      if (componentInfo.debugStack === undefined) {1227        continue;1228      }1229      task.componentStack = {1230        parent: task.componentStack,1231        type: componentInfo,1232        owner: componentInfo.owner,1233        stack: componentInfo.debugStack,1234      };1235      task.debugTask = componentInfo.debugTask as any;1236    }1237  }1238}12391240function pushComponentStack(task: Task): void {1241  const node = task.node;1242  // Create the Component Stack frame for the element we're about to try.1243  // It's unfortunate that we need to do this refinement twice. Once for1244  // the stack frame and then once again while actually1245  if (typeof node === 'object' && node !== null) {1246    switch ((node as any).$$typeof) {1247      case REACT_ELEMENT_TYPE: {1248        const element: any = node;1249        const type = element.type;1250        const owner = __DEV__ ? element._owner : null;1251        const stack = __DEV__ ? element._debugStack : null;1252        if (__DEV__) {1253          pushServerComponentStack(task, element._debugInfo);1254          task.debugTask = element._debugTask;1255        }1256        task.componentStack = createComponentStackFromType(1257          task.componentStack,1258          type,1259          owner,1260          stack,1261        );1262        break;1263      }1264      case REACT_LAZY_TYPE: {1265        if (__DEV__) {1266          const lazyNode: LazyComponentType<any, any> = node as any;1267          pushServerComponentStack(task, lazyNode._debugInfo);1268        }1269        break;1270      }1271      default: {1272        if (__DEV__) {1273          const maybeUsable: Object = node;1274          if (typeof maybeUsable.then === 'function') {1275            const thenable: Thenable<ReactNodeList> = maybeUsable as any;1276            pushServerComponentStack(task, thenable._debugInfo);1277          }1278        }1279      }1280    }1281  }1282}12831284function createComponentStackFromType(1285  parent: null | ComponentStackNode,1286  type: Function | string | symbol,1287  owner: void | null | ReactComponentInfo | ComponentStackNode, // DEV only1288  stack: void | null | string | Error, // DEV only1289): ComponentStackNode {1290  if (__DEV__) {1291    return {1292      parent,1293      type,1294      owner,1295      stack,1296    };1297  }1298  return {1299    parent,1300    type,1301  };1302}13031304function replaceSuspenseComponentStackWithSuspenseFallbackStack(1305  componentStack: null | ComponentStackNode,1306): null | ComponentStackNode {1307  if (componentStack === null) {1308    return null;1309  }1310  return createComponentStackFromType(1311    componentStack.parent,1312    'Suspense Fallback',1313    __DEV__ ? componentStack.owner : null,1314    __DEV__ ? componentStack.stack : null,1315  );1316}13171318type ThrownInfo = {1319  componentStack?: string,1320};1321export type ErrorInfo = ThrownInfo;13221323function getThrownInfo(node: null | ComponentStackNode): ThrownInfo {1324  const errorInfo: ThrownInfo = {};1325  if (node) {1326    Object.defineProperty(errorInfo, 'componentStack', {1327      configurable: true,1328      enumerable: true,1329      get() {1330        // Lazyily generate the stack since it's expensive.1331        const stack = getStackFromNode(node);1332        Object.defineProperty(errorInfo, 'componentStack', {1333          value: stack,1334        });1335        return stack;1336      },1337    });1338  }1339  return errorInfo;1340}13411342function encodeErrorForBoundary(1343  boundary: SuspenseBoundary,1344  digest: ?string,1345  error: mixed,1346  thrownInfo: ThrownInfo,1347  wasAborted: boolean,1348) {1349  boundary.errorDigest = digest;1350  if (__DEV__) {1351    if (isRecoverableError(error)) {1352      boundary.errorMessage = wasAborted1353        ? 'Switched to client rendering because the server render was aborted ' +1354          'with a request to render on the client.'1355        : 'Switched to client rendering because a component requested it.';1356      boundary.errorComponentStack = thrownInfo.componentStack;1357      return;1358    }1359    let message, stack;1360    // In dev we additionally encode the error message and component stack on the boundary1361    if (error instanceof Error) {1362      // eslint-disable-next-line react-internal/safe-string-coercion1363      message = String(error.message);1364      // eslint-disable-next-line react-internal/safe-string-coercion1365      stack = String(error.stack);1366    } else if (typeof error === 'object' && error !== null) {1367      message = describeObjectForErrorMessage(error);1368      stack = null;1369    } else {1370      // eslint-disable-next-line react-internal/safe-string-coercion1371      message = String(error);1372      stack = null;1373    }1374    const prefix = wasAborted1375      ? 'Switched to client rendering because the server rendering aborted due to:\n\n'1376      : 'Switched to client rendering because the server rendering errored:\n\n';1377    boundary.errorMessage = prefix + message;1378    boundary.errorStack = stack !== null ? prefix + stack : null;1379    boundary.errorComponentStack = thrownInfo.componentStack;1380  }1381}13821383function logRecoverableError(1384  request: Request,1385  error: any,1386  errorInfo: ThrownInfo,1387  debugTask: null | ConsoleTask,1388): ?string {1389  if (isRecoverableError(error)) {1390    logBrowserBailout(request, error, errorInfo, debugTask);1391    return REACT_RECOVERABLE_DIGEST;1392  }13931394  // If this callback errors, we intentionally let that error bubble up to become a fatal error1395  // so that someone fixes the error reporting instead of hiding it.1396  const onError = request.onError;1397  const errorDigest =1398    __DEV__ && debugTask1399      ? debugTask.run(onError.bind(null, error, errorInfo))1400      : onError(error, errorInfo);1401  if (errorDigest != null && typeof errorDigest !== 'string') {1402    // We used to throw here but since this gets called from a variety of unprotected places it1403    // seems better to just warn and discard the returned value.1404    if (__DEV__) {1405      console.error(1406        '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',1407        typeof errorDigest,1408      );1409    }1410    return;1411  }1412  // An empty digest is reserved for React's internal client-render signal.1413  // Historically an empty digest was omitted from the wire format, so1414  // normalizing it to undefined preserves the existing user-space semantics.1415  return errorDigest === '' ? undefined : errorDigest;1416}14171418function logBrowserBailout(1419  request: Request,1420  error: mixed,1421  errorInfo: ThrownInfo,1422  debugTask: null | ConsoleTask,1423): void {1424  // If this callback errors, we intentionally let that error bubble up to1425  // become a fatal error, matching the behavior of onError.1426  const onBrowserBailout = request.onBrowserBailout;1427  if (__DEV__ && debugTask) {1428    debugTask.run(onBrowserBailout.bind(null, error, errorInfo));1429  } else {1430    onBrowserBailout(error, errorInfo);1431  }1432}14331434function fatalError(1435  request: Request,1436  error: mixed,1437  errorInfo: ThrownInfo,1438  debugTask: null | ConsoleTask,1439): void {1440  // This is called outside error handling code such as if the root errors outside1441  // a suspense boundary or if the root suspense boundary's fallback errors.1442  // It's also called if React itself or its host configs errors.1443  const onShellError = request.onShellError;1444  const onFatalError = request.onFatalError;1445  // Once the shell has completed it can't error anymore, so onShellError only1446  // fires while root tasks are still pending. onFatalError always fires because1447  // the error is always fatal to the request.1448  const shellComplete = request.pendingRootTasks === 0;1449  if (__DEV__ && debugTask) {1450    if (!shellComplete) {1451      debugTask.run(onShellError.bind(null, error));1452    }1453    debugTask.run(onFatalError.bind(null, error));1454  } else {1455    if (!shellComplete) {1456      onShellError(error);1457    }1458    onFatalError(error);1459  }1460  endRenderLifetime(request);1461  if (request.destination !== null) {1462    request.status = CLOSED;1463    closeWithError(request.destination, error);1464  } else {1465    request.status = CLOSING;1466    // abort() already stored the reason that every remaining task must1467    // observe. This error may only be a fatal diagnostic derived from it.1468    if (!request.aborted) {1469      request.fatalError = error;1470    }1471  }1472}14731474function renderSuspenseBoundary(1475  request: Request,1476  someTask: Task,1477  keyPath: KeyNode,1478  props: SuspenseProps,1479): void {1480  if (someTask.replay !== null) {1481    // If we're replaying through this pass, it means we're replaying through1482    // an already completed Suspense boundary. It's too late to do anything about it1483    // so we can just render through it.1484    const prevKeyPath = someTask.keyPath;1485    const prevContext = someTask.formatContext;1486    const prevRow = someTask.row;1487    someTask.keyPath = keyPath;1488    someTask.formatContext = getSuspenseContentFormatContext(1489      request.resumableState,1490      prevContext,1491    );1492    someTask.row = null;1493    const content: ReactNodeList = props.children;1494    try {1495      renderNode(request, someTask, content, -1);1496    } finally {1497      someTask.keyPath = prevKeyPath;1498      someTask.formatContext = prevContext;1499      someTask.row = prevRow;1500    }1501    return;1502  }1503  // $FlowFixMe[incompatible-type]: Refined.1504  const task: RenderTask = someTask;15051506  const prevKeyPath = task.keyPath;1507  const prevContext = task.formatContext;1508  const prevRow = task.row;1509  const parentBoundary = task.blockedBoundary;1510  const parentPreamble = task.blockedPreamble;1511  const parentHoistableState = task.hoistableState;1512  const parentSegment = task.blockedSegment;15131514  // Each time we enter a suspense boundary, we split out into a new segment for1515  // the fallback so that we can later replace that segment with the content.1516  // This also lets us split out the main content even if it doesn't suspend,1517  // in case it ends up generating a large subtree of content.1518  const fallback: ReactNodeList = props.fallback;1519  const content: ReactNodeList = props.children;1520  const defer: boolean = enableCPUSuspense && props.defer === true;15211522  const fallbackAbortSet: Set<Task> = new Set();1523  const newBoundary = createSuspenseBoundary(1524    request,1525    task.row,1526    fallbackAbortSet,1527    canHavePreamble(task.formatContext) ? createPreamble() : null,1528    defer,1529  );15301531  const insertionIndex = parentSegment.chunks.length;1532  // The children of the boundary segment is actually the fallback.1533  const boundarySegment = createPendingSegment(1534    request,1535    insertionIndex,1536    newBoundary,1537    task.formatContext,1538    // boundaries never require text embedding at their edges because comment nodes bound them1539    false,1540    false,1541  );1542  parentSegment.children.push(boundarySegment);1543  // The parentSegment has a child Segment at this index so we reset the lastPushedText marker on the parent1544  parentSegment.lastPushedText = false;15451546  // This segment is the actual child content. We can start rendering that immediately.1547  const contentRootSegment = createPendingSegment(1548    request,1549    0,1550    null,1551    task.formatContext,1552    // boundaries never require text embedding at their edges because comment nodes bound them1553    false,1554    false,1555  );1556  // We mark the root segment as having its parent flushed. It's not really flushed but there is1557  // no parent segment so there's nothing to wait on.1558  contentRootSegment.parentFlushed = true;15591560  const trackedPostpones = request.trackedPostpones;1561  if (trackedPostpones !== null || defer) {1562    // This is a prerender or deferred boundary. In this mode we want to render the fallback synchronously1563    // and schedule the content to render later. This is the opposite of what we do during a normal render1564    // where we try to skip rendering the fallback if the content itself can render synchronously15651566    // Stash the original stack frame.1567    const suspenseComponentStack = task.componentStack;15681569    const fallbackKeyPath: KeyNode = [1570      keyPath[0],1571      'Suspense Fallback',1572      keyPath[2],1573    ];1574    if (trackedPostpones !== null) {1575      const fallbackReplayNode: ReplayNode = [1576        fallbackKeyPath[1],1577        fallbackKeyPath[2],1578        [] as Array<ReplayNode>,1579        null,1580      ];1581      trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode);1582      newBoundary.tracked = {1583        contentKeyPath: keyPath,1584        // We are rendering the fallback before the boundary content so we keep track of1585        // the fallback replay node until we determine if the primary content suspends1586        fallbackNode: fallbackReplayNode,1587      };1588    }15891590    task.blockedSegment = boundarySegment;1591    task.blockedPreamble =1592      newBoundary.preamble === null ? null : newBoundary.preamble.fallback;1593    task.keyPath = fallbackKeyPath;1594    task.formatContext = getSuspenseFallbackFormatContext(1595      request.resumableState,1596      prevContext,1597    );1598    task.componentStack =1599      replaceSuspenseComponentStackWithSuspenseFallbackStack(1600        suspenseComponentStack,1601      );1602    try {1603      renderNode(request, task, fallback, -1);1604      pushSegmentFinale(1605        boundarySegment.chunks,1606        request.renderState,1607        boundarySegment.lastPushedText,1608        boundarySegment.textEmbedded,1609      );1610      boundarySegment.status = COMPLETED;1611      finishedSegment(request, parentBoundary, boundarySegment);1612    } catch (thrownValue: mixed) {1613      if (request.aborted) {1614        boundarySegment.status = ABORTED;1615      } else {1616        boundarySegment.status = ERRORED;1617      }1618      throw thrownValue;1619    } finally {1620      task.blockedSegment = parentSegment;1621      task.blockedPreamble = parentPreamble;1622      task.keyPath = prevKeyPath;1623      task.formatContext = prevContext;1624    }16251626    // We create a suspended task for the primary content because we want to allow1627    // sibling fallbacks to be rendered first.1628    const suspendedPrimaryTask = createRenderTask(1629      request,1630      null,1631      content,1632      -1,1633      newBoundary,1634      contentRootSegment,1635      newBoundary.preamble === null ? null : newBoundary.preamble.content,1636      newBoundary.contentState,1637      task.abortSet,1638      keyPath,1639      getSuspenseContentFormatContext(1640        request.resumableState,1641        task.formatContext,1642      ),1643      task.context,1644      task.treeContext,1645      null, // The row gets reset inside the Suspense boundary.1646      suspenseComponentStack,1647      !disableLegacyContext ? task.legacyContext : emptyContextObject,1648      __DEV__ ? task.debugTask : null,1649    );1650    pushComponentStack(suspendedPrimaryTask);1651    request.pingedTasks.push(suspendedPrimaryTask);1652  } else {1653    // This is a normal render. We will attempt to synchronously render the boundary content1654    // If it is successful we will elide the fallback task but if it suspends or errors we schedule1655    // the fallback to render. Unlike with prerenders we attempt to deprioritize the fallback render16561657    // Currently this is running synchronously. We could instead schedule this to pingedTasks.1658    // I suspect that there might be some efficiency benefits from not creating the suspended task1659    // and instead just using the stack if possible.1660    // TODO: Call this directly instead of messing with saving and restoring contexts.16611662    // We can reuse the current context and task to render the content immediately without1663    // context switching. We just need to temporarily switch which boundary and which segment1664    // we're writing to. If something suspends, it'll spawn new suspended task with that context.1665    task.blockedBoundary = newBoundary;1666    task.blockedPreamble =1667      newBoundary.preamble === null ? null : newBoundary.preamble.content;1668    task.hoistableState = newBoundary.contentState;1669    task.blockedSegment = contentRootSegment;1670    task.keyPath = keyPath;1671    task.formatContext = getSuspenseContentFormatContext(1672      request.resumableState,1673      prevContext,1674    );1675    task.row = null;1676    try {1677      // We use the safe form because we don't handle suspending here. Only error handling.1678      renderNode(request, task, content, -1);1679      pushSegmentFinale(1680        contentRootSegment.chunks,1681        request.renderState,1682        contentRootSegment.lastPushedText,1683        contentRootSegment.textEmbedded,1684      );1685      contentRootSegment.status = COMPLETED;1686      finishedSegment(request, newBoundary, contentRootSegment);1687      queueCompletedSegment(newBoundary, contentRootSegment);1688      if (newBoundary.pendingTasks === 0 && newBoundary.status === PENDING) {1689        // This must have been the last segment we were waiting on. This boundary is now complete.1690        newBoundary.status = COMPLETED;1691        // Therefore we won't need the fallback. We early return so that we don't have to create1692        // the fallback. However, if this boundary ended up big enough to be eligible for outlining1693        // we can't do that because we might still need the fallback if we outline it.1694        if (!isEligibleForOutlining(request, newBoundary)) {1695          if (prevRow !== null) {1696            // If we have synchronously completed the boundary and it's not eligible for outlining1697            // then we don't have to wait for it to be flushed before we unblock future rows.1698            // This lets us inline small rows in order.1699            if (--prevRow.pendingTasks === 0) {1700              finishSuspenseListRow(request, prevRow);1701            }1702          }1703          if (request.pendingRootTasks === 0 && task.blockedPreamble) {1704            // The root is complete and this boundary may contribute part of the preamble.1705            // We eagerly attempt to prepare the preamble here because we expect most requests1706            // to have few boundaries which contribute preambles and it allow us to do this1707            // preparation work during the work phase rather than the when flushing.1708            preparePreamble(request);1709          }1710          return;1711        }1712      } else {1713        const boundaryRow = prevRow;1714        if (boundaryRow !== null && boundaryRow.together) {1715          tryToResolveTogetherRow(request, boundaryRow);1716        }1717      }1718    } catch (thrownValue: mixed) {1719      newBoundary.status = CLIENT_RENDERED;1720      let error: mixed;1721      if (request.aborted) {1722        contentRootSegment.status = ABORTED;1723        error = request.fatalError;1724      } else {1725        contentRootSegment.status = ERRORED;1726        error = thrownValue;1727      }17281729      const thrownInfo = getThrownInfo(task.componentStack);1730      const errorDigest = logRecoverableError(1731        request,1732        error,1733        thrownInfo,1734        __DEV__ ? task.debugTask : null,1735      );1736      encodeErrorForBoundary(1737        newBoundary,1738        errorDigest,1739        error,1740        thrownInfo,1741        false,1742      );17431744      untrackBoundary(request, newBoundary);17451746      // We don't need to decrement any task numbers because we didn't spawn any new task.1747      // We don't need to schedule any task because we know the parent has written yet.1748      // We do need to fallthrough to create the fallback though.1749    } finally {1750      task.blockedBoundary = parentBoundary;1751      task.blockedPreamble = parentPreamble;1752      task.hoistableState = parentHoistableState;1753      task.blockedSegment = parentSegment;1754      task.keyPath = prevKeyPath;1755      task.formatContext = prevContext;1756      task.row = prevRow;1757    }17581759    const fallbackKeyPath: KeyNode = [1760      keyPath[0],1761      'Suspense Fallback',1762      keyPath[2],1763    ];1764    // We create suspended task for the fallback because we don't want to actually work1765    // on it yet in case we finish the main content, so we queue for later.1766    const suspendedFallbackTask = createRenderTask(1767      request,1768      null,1769      fallback,1770      -1,1771      parentBoundary,1772      boundarySegment,1773      newBoundary.preamble === null ? null : newBoundary.preamble.fallback,1774      newBoundary.fallbackState,1775      fallbackAbortSet,1776      fallbackKeyPath,1777      getSuspenseFallbackFormatContext(1778        request.resumableState,1779        task.formatContext,1780      ),1781      task.context,1782      task.treeContext,1783      task.row,1784      replaceSuspenseComponentStackWithSuspenseFallbackStack(1785        task.componentStack,1786      ),1787      !disableLegacyContext ? task.legacyContext : emptyContextObject,1788      __DEV__ ? task.debugTask : null,1789    );1790    pushComponentStack(suspendedFallbackTask);1791    // TODO: This should be queued at a separate lower priority queue so that we only work1792    // on preparing fallbacks if we don't have any more main content to task on.1793    request.pingedTasks.push(suspendedFallbackTask);1794  }1795}17961797function replaySuspenseBoundary(1798  request: Request,1799  task: ReplayTask,1800  keyPath: KeyNode,1801  props: Object,1802  id: number,1803  childNodes: Array<ReplayNode>,1804  childSlots: ResumeSlots,1805  fallbackNodes: Array<ReplayNode>,1806  fallbackSlots: ResumeSlots,1807): void {1808  const prevKeyPath = task.keyPath;1809  const prevContext = task.formatContext;1810  const prevRow = task.row;1811  const previousReplaySet: ReplaySet = task.replay;18121813  const parentBoundary = task.blockedBoundary;1814  const parentHoistableState = task.hoistableState;18151816  const content: ReactNodeList = props.children;1817  const fallback: ReactNodeList = props.fallback;1818  const defer: boolean = enableCPUSuspense && props.defer === true;18191820  const fallbackAbortSet: Set<Task> = new Set();1821  const resumedBoundary = createSuspenseBoundary(1822    request,1823    task.row,1824    fallbackAbortSet,1825    canHavePreamble(task.formatContext) ? createPreamble() : null,1826    defer,1827  );1828  resumedBoundary.parentFlushed = true;1829  // We restore the same id of this boundary as was used during prerender.1830  resumedBoundary.rootSegmentID = id;18311832  // We can reuse the current context and task to render the content immediately without1833  // context switching. We just need to temporarily switch which boundary and replay node1834  // we're writing to. If something suspends, it'll spawn new suspended task with that context.1835  task.blockedBoundary = resumedBoundary;1836  task.hoistableState = resumedBoundary.contentState;1837  task.keyPath = keyPath;1838  task.formatContext = getSuspenseContentFormatContext(1839    request.resumableState,1840    prevContext,1841  );1842  task.row = null;1843  task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};18441845  try {1846    // We use the safe form because we don't handle suspending here. Only error handling.1847    renderNode(request, task, content, -1);18481849    if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {1850      throw new Error(1851        "Couldn't find all resumable slots by key/index during replaying. " +1852          "The tree doesn't match so React will fallback to client rendering.",1853      );1854    }1855    task.replay.pendingTasks--;1856    if (1857      resumedBoundary.pendingTasks === 0 &&1858      resumedBoundary.status === PENDING1859    ) {1860      // This must have been the last segment we were waiting on. This boundary is now complete.1861      // Therefore we won't need the fallback. We early return so that we don't have to create1862      // the fallback.1863      resumedBoundary.status = COMPLETED;1864      request.completedBoundaries.push(resumedBoundary);1865      // We restore the parent componentStack. Semantically this is the same as1866      // popComponentStack(task) but we do this instead because it should be slightly1867      // faster1868      return;1869    }1870  } catch (thrownValue: mixed) {1871    resumedBoundary.status = CLIENT_RENDERED;1872    const error = request.aborted ? request.fatalError : thrownValue;1873    const thrownInfo = getThrownInfo(task.componentStack);1874    const errorDigest = logRecoverableError(1875      request,1876      error,1877      thrownInfo,1878      __DEV__ ? task.debugTask : null,1879    );1880    encodeErrorForBoundary(1881      resumedBoundary,1882      errorDigest,1883      error,1884      thrownInfo,1885      false,1886    );18871888    task.replay.pendingTasks--;18891890    // The parent already flushed in the prerender so we need to schedule this to be emitted.1891    request.clientRenderedBoundaries.push(resumedBoundary);18921893    // We don't need to decrement any task numbers because we didn't spawn any new task.1894    // We don't need to schedule any task because we know the parent has written yet.1895    // We do need to fallthrough to create the fallback though.1896  } finally {1897    task.blockedBoundary = parentBoundary;1898    task.hoistableState = parentHoistableState;1899    task.replay = previousReplaySet;1900    task.keyPath = prevKeyPath;1901    task.formatContext = prevContext;1902    task.row = prevRow;1903  }19041905  const fallbackKeyPath: KeyNode = [1906    keyPath[0],1907    'Suspense Fallback',1908    keyPath[2],1909  ];19101911  // We create suspended task for the fallback because we don't want to actually work1912  // on it yet in case we finish the main content, so we queue for later.1913  const fallbackReplay = {1914    nodes: fallbackNodes,1915    slots: fallbackSlots,1916    pendingTasks: 0,1917  };1918  const suspendedFallbackTask = createReplayTask(1919    request,1920    null,1921    fallbackReplay,1922    fallback,1923    -1,1924    parentBoundary,1925    resumedBoundary.fallbackState,1926    fallbackAbortSet,1927    fallbackKeyPath,1928    getSuspenseFallbackFormatContext(1929      request.resumableState,1930      task.formatContext,1931    ),1932    task.context,1933    task.treeContext,1934    task.row,1935    replaceSuspenseComponentStackWithSuspenseFallbackStack(task.componentStack),1936    !disableLegacyContext ? task.legacyContext : emptyContextObject,1937    __DEV__ ? task.debugTask : null,1938  );19391940  pushComponentStack(suspendedFallbackTask);1941  // TODO: This should be queued at a separate lower priority queue so that we only work1942  // on preparing fallbacks if we don't have any more main content to task on.1943  request.pingedTasks.push(suspendedFallbackTask);1944}19451946function finishSuspenseListRow(request: Request, row: SuspenseListRow): void {1947  // This row finished. Now we have to unblock all the next rows that were blocked on this.1948  unblockSuspenseListRow(request, row.next, row.hoistables);1949}19501951function unblockSuspenseListRow(1952  request: Request,1953  unblockedRow: null | SuspenseListRow,1954  inheritedHoistables: null | HoistableState,1955): void {1956  // We do this in a loop to avoid stack overflow for very long lists that get unblocked.1957  while (unblockedRow !== null) {1958    if (inheritedHoistables !== null) {1959      // Hoist any hoistables from the previous row into the next row so that it can be1960      // later transferred to all the rows.1961      hoistHoistables(unblockedRow.hoistables, inheritedHoistables);1962      // Mark the row itself for any newly discovered Suspense boundaries to inherit.1963      // This is different from hoistables because that also includes hoistables from1964      // all the boundaries below this row and not just previous rows.1965      unblockedRow.inheritedHoistables = inheritedHoistables;1966    }1967    // Unblocking the boundaries will decrement the count of this row but we keep it above1968    // zero so they never finish this row recursively.1969    const unblockedBoundaries = unblockedRow.boundaries;1970    if (unblockedBoundaries !== null) {1971      unblockedRow.boundaries = null;1972      for (let i = 0; i < unblockedBoundaries.length; i++) {1973        const unblockedBoundary = unblockedBoundaries[i];1974        if (inheritedHoistables !== null) {1975          hoistHoistables(unblockedBoundary.contentState, inheritedHoistables);1976        }1977        finishedTask(request, unblockedBoundary, null, null);1978      }1979    }1980    // Instead we decrement at the end to keep it all in this loop.1981    unblockedRow.pendingTasks--;1982    if (unblockedRow.pendingTasks > 0) {1983      // Still blocked.1984      break;1985    }1986    inheritedHoistables = unblockedRow.hoistables;1987    unblockedRow = unblockedRow.next;1988  }1989}19901991function trackPostponedSuspenseListRow(1992  request: Request,1993  trackedPostpones: PostponedHoles,1994  postponedRow: null | SuspenseListRow,1995): void {1996  // TODO: Because we unconditionally call this, it will be called by finishedTask1997  // and so ends up recursive which can lead to stack overflow for very long lists.1998  if (postponedRow !== null) {1999    const postponedBoundaries = postponedRow.boundaries;2000    if (postponedBoundaries !== null) {

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.