packages/react-server/src/ReactFizzServer.js JAVASCRIPT 6,845 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 6,845.
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  enableCPUSuspense,191} from 'shared/ReactFeatureFlags';192193import assign from 'shared/assign';194import noop from 'shared/noop';195import getComponentNameFromType from 'shared/getComponentNameFromType';196import isArray from 'shared/isArray';197import {REACT_RECOVERABLE_DIGEST} from 'shared/ReactRecoverable';198import {199  SuspenseException,200  getSuspendedThenable,201  ensureSuspendableThenableStateDEV,202  getSuspendedCallSiteStackDEV,203  getSuspendedCallSiteDebugTaskDEV,204  setCaptureSuspendedCallSiteDEV,205} from './ReactFizzThenable';206207// Linked list representing the identity of a component given the component/tag name and key.208// The name might be minified but we assume that it's going to be the same generated name. Typically209// because it's just the same compiled output in practice.210export type KeyNode = [211  Root | KeyNode /* parent */,212  string | null /* name */,213  string | number /* key */,214];215216type ResumeSlots =217  | null // nothing to resume218  | number // resume with segment ID at the root position219  | {[index: number]: number}; // resume with segmentID at the index220221type ReplaySuspenseBoundary = [222  string | null /* name */,223  string | number /* key */,224  Array<ReplayNode> /* content keyed children */,225  ResumeSlots /* content resumable slots */,226  null | ReplayNode /* fallback content */,227  number /* rootSegmentID */,228];229230type ReplayNode =231  | [232      string | null /* name */,233      string | number /* key */,234      Array<ReplayNode> /* keyed children */,235      ResumeSlots /* resumable slots */,236    ]237  | ReplaySuspenseBoundary;238239type PostponedHoles = {240  workingMap: Map<KeyNode, ReplayNode>,241  rootNodes: Array<ReplayNode>,242  rootSlots: ResumeSlots,243};244245type LegacyContext = {246  [key: string]: any,247};248249type SuspenseListRow = {250  pendingTasks: number, // The number of tasks, previous rows and inner suspense boundaries blocking this row.251  boundaries: null | Array<SuspenseBoundary>, // The boundaries in this row waiting to be unblocked by the previous row. (null means this row is not blocked)252  hoistables: HoistableState, // Any dependencies that this row depends on. Future rows need to also depend on it.253  inheritedHoistables: null | HoistableState, // Any dependencies that previous row depend on, that new boundaries of this row needs.254  together: boolean, // All the boundaries within this row must be revealed together.255  next: null | SuspenseListRow, // The next row blocked by this one.256};257258const CLIENT_RENDERED = 4; // if it errors or infinitely suspends259260type SuspenseBoundary = {261  status: 0 | 1 | 4 | 5,262  rootSegmentID: number,263  parentFlushed: boolean,264  pendingTasks: number, // when it reaches zero we can show this boundary's content265  row: null | SuspenseListRow, // the row that this boundary blocks from completing.266  completedSegments: Array<Segment>, // completed but not yet flushed segments.267  byteSize: number, // used to determine whether to inline children boundaries.268  defer: boolean, // never inline deferred boundaries269  fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.270  contentState: HoistableState,271  fallbackState: HoistableState,272  preamble: null | Preamble,273  tracked: null | {274    contentKeyPath: null | KeyNode, // used to track the path for replay nodes275    fallbackNode: null | ReplayNode, // used to track the fallback for replay nodes276  },277  errorDigest: ?string, // the error hash if it errors278  // DEV-only fields279  errorMessage?: null | string, // the error string if it errors280  errorStack?: null | string, // the error stack if it errors281  errorComponentStack?: null | string, // the error component stack if it errors282};283284type Ping = {285  resolve: () => void,286  reject: (error: mixed) => void,287};288289type RenderTask = {290  replay: null,291  node: ReactNodeList,292  childIndex: number,293  ping: Ping,294  blockedBoundary: Root | SuspenseBoundary,295  blockedSegment: Segment, // the segment we'll write to296  blockedPreamble: null | PreambleState,297  hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary298  abortSet: Set<Task>, // the abortable set that this task belongs to299  keyPath: Root | KeyNode, // the path of all parent keys currently rendering300  formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)301  context: ContextSnapshot, // the current new context that this task is executing in302  treeContext: TreeContext, // the current tree context that this task is executing in303  row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside304  componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component305  thenableState: null | ThenableState,306  legacyContext: LegacyContext, // the current legacy context that this task is executing in307  debugTask: null | ConsoleTask, // DEV only308  // DON'T ANY MORE FIELDS. We at 16 in prod already which otherwise requires converting to a constructor.309  // Consider splitting into multiple objects or consolidating some fields.310};311312type ReplaySet = {313  nodes: Array<ReplayNode>, // the possible paths to follow down the replaying314  slots: ResumeSlots, // slots to resume315  pendingTasks: number, // tracks the number of tasks currently tracking this set of nodes316  // if pending tasks reach zero but there are still nodes left, it means we couldn't find317  // them all in the tree, so we need to abort and client render the boundary.318};319320type ReplayTask = {321  replay: ReplaySet,322  node: ReactNodeList,323  childIndex: number,324  ping: Ping,325  blockedBoundary: Root | SuspenseBoundary,326  blockedSegment: null, // we don't write to anything when we replay327  blockedPreamble: null,328  hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary329  abortSet: Set<Task>, // the abortable set that this task belongs to330  keyPath: Root | KeyNode, // the path of all parent keys currently rendering331  formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)332  context: ContextSnapshot, // the current new context that this task is executing in333  treeContext: TreeContext, // the current tree context that this task is executing in334  row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside335  componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component336  thenableState: null | ThenableState,337  legacyContext: LegacyContext, // the current legacy context that this task is executing in338  debugTask: null | ConsoleTask, // DEV only339};340341export type Task = RenderTask | ReplayTask;342343const PENDING = 0;344const COMPLETED = 1;345const FLUSHED = 2;346const ABORTED = 3;347const ERRORED = 4;348const POSTPONED = 5;349350type Root = null;351352type Segment = {353  status: 0 | 1 | 2 | 3 | 4 | 5,354  parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed355  id: number, // starts as 0 and is lazily assigned if the parent flushes early356  +index: number, // the index within the parent's chunks or 0 at the root357  +chunks: Array<Chunk | PrecomputedChunk>,358  +children: Array<Segment>,359  +preambleChildren: Array<Segment>,360  // The context that this segment was created in.361  parentFormatContext: FormatContext,362  // If this segment represents a fallback, this is the content that will replace that fallback.363  boundary: null | SuspenseBoundary,364  // used to discern when text separator boundaries are needed365  lastPushedText: boolean,366  textEmbedded: boolean,367};368369// The ordering of these statuses matters. OPENING and OPEN are the only370// statuses in which newly scheduled work may be performed. Any status greater371// than OPEN represents a request that no longer admits work.372const OPENING = 10;373const OPEN = 11;374const CLOSING = 12;375const CLOSED = 13;376const STALLED_DEV = 14;377378// Passed to renderLifetimeController.abort(). Nothing reads the reason, but a379// call to abort() without one constructs an AbortError DOMException. Capturing380// the stack trace dominates that cost, and the cost grows with the depth of the381// stack.382const RENDER_ENDED = 'The render ended.';383384export opaque type Request = {385  destination: null | Destination,386  flushScheduled: boolean,387  +resumableState: ResumableState,388  +renderState: RenderState,389  +rootFormatContext: FormatContext,390  +progressiveChunkSize: number,391  status: 10 | 11 | 12 | 13 | 14,392  fatalError: mixed,393  aborted: boolean,394  nextSegmentId: number,395  allPendingTasks: number, // when it reaches zero, we can close the connection.396  pendingRootTasks: number, // when this reaches zero, we've finished at least the root boundary.397  completedRootSegment: null | Segment, // Completed but not yet flushed root segments.398  completedPreambleSegments: null | Array<Array<Segment>>, // contains the ready-to-flush segments that make up the preamble399  byteSize: number, // counts the number of bytes accumulated in the shell400  abortableTasks: Set<Task>,401  pingedTasks: Array<Task>, // High priority tasks that should be worked on first.402  currentTask: null | Task, // The task currently executing in this request.403  // Queues to flush in order of priority404  clientRenderedBoundaries: Array<SuspenseBoundary>, // Errored or client rendered but not yet flushed.405  completedBoundaries: Array<SuspenseBoundary>, // Completed but not yet fully flushed boundaries to show.406  partialBoundaries: Array<SuspenseBoundary>, // Partially completed boundaries that can flush its segments early.407  trackedPostpones: null | PostponedHoles, // Gets set to non-null while we want to track postponed holes. I.e. during a prerender.408  // While prerendering a postponed request that produced a real shell, this409  // holds the PostponedState returned by getPostponedState. getPostponedState410  // snapshots nextSegmentId before the (pull-driven) prelude flush runs, but the411  // flush outlines completed boundaries and advances nextSegmentId past that412  // snapshot. We finalize the snapshot from flushCompletedQueues so the resumed413  // render allocates segment ids strictly above the shell's; otherwise the shell414  // and resume emit duplicate B:/S: ids once concatenated. Stays null for live415  // renders and resumes.416  postponedState: null | PostponedState,417  // onError is called when an error happens anywhere in the tree. It might recover.418  // The return string is used in production  primarily to avoid leaking internals, secondarily to save bytes.419  // Returning null/undefined will cause a default error message in production420  onError: (error: mixed, errorInfo: ThrownInfo) => ?string,421  // onBrowserBailout is called when Fizz recovers by intentionally deferring422  // rendering to the browser.423  onBrowserBailout: (error: mixed, errorInfo: ThrownInfo) => void,424  // onAllReady is called when all pending task is done but it may not have flushed yet.425  // This is a good time to start writing if you want only HTML and no intermediate steps.426  onAllReady: () => void,427  // onShellReady is called when there is at least a root fallback ready to show.428  // Typically you don't need this callback because it's best practice to always have a429  // root fallback ready so there's no need to wait.430  onShellReady: () => void,431  // onShellError is called when the shell didn't complete. That means you probably want to432  // emit a different response to the stream instead.433  onShellError: (error: mixed) => void,434  onFatalError: (error: mixed) => void,435  // Aborted once the render ends, whether it completed, failed fatally or was436  // aborted. Bounds the lifetime of anything that must not outlive the render.437  // Null until attachAbortSignal creates it, so a render that is given no438  // signal constructs no controller.439  renderLifetimeController: null | AbortController,440  // Form state that was the result of an MPA submission, if it was provided.441  formState: null | ReactFormState<any, any>,442  // DEV-only, warning dedupe443  didWarnForKey?: null | WeakSet<ComponentStackNode>,444};445446type Preamble = {447  content: PreambleState,448  fallback: PreambleState,449};450451function createPreamble(): Preamble {452  return {453    content: createPreambleState(),454    fallback: createPreambleState(),455  };456}457458// This is a default heuristic for how to split up the HTML content into progressive459// loading. Our goal is to be able to display additional new content about every 500ms.460// Faster than that is unnecessary and should be throttled on the client. It also461// adds unnecessary overhead to do more splits. We don't know if it's a higher or lower462// end device but higher end suffer less from the overhead than lower end does from463// not getting small enough pieces. We error on the side of low end.464// We base this on low end 3G speeds which is about 500kbits per second. We assume465// that there can be a reasonable drop off from max bandwidth which leaves you with466// as little as 80%. We can receive half of that each 500ms - at best. In practice,467// a little bandwidth is lost to processing and contention - e.g. CSS and images that468// are downloaded along with the main content. So we estimate about half of that to be469// the lower end throughput. In other words, we expect that you can at least show470// about 12.5kb of content per 500ms. Not counting starting latency for the first471// paint.472// 500 * 1024 / 8 * .8 * 0.5 / 2473const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800;474475function getBlockingRenderMaxSize(request: Request): number {476  // We want to make sure that we can block the reveal of a well designed complete477  // shell but if you have constructed a too large shell (e.g. by not adding any478  // Suspense boundaries) then that might take too long to render. We shouldn't479  // punish users (or overzealous metrics tracking) in that scenario.480  // There's a trade off here. If this limit is too low then you can't fit a481  // reasonably well built UI within it without getting errors. If it's too high482  // then things that accidentally fall below it might take too long to load.483  // Web Vitals target 1.8 seconds for first paint and our goal to have the limit484  // be fast enough to hit that. For this argument we assume that most external485  // resources are already cached because it's a return visit, or inline styles.486  // If it's not, then it's highly unlikely that any render blocking instructions487  // we add has any impact what so ever on the paint.488  // Assuming a first byte of about 600ms which is kind of bad but common with a489  // decent static host. If it's longer e.g. due to dynamic rendering, then you490  // are going to bound by dynamic production of the content and you're better off491  // with Suspense boundaries anyway. This number doesn't matter much. Then you492  // have about 1.2 seconds left for bandwidth. On 3G that gives you about 112.5kb493  // worth of data. That's worth about 10x in terms of uncompressed bytes. Then we494  // half that just to account for longer latency, slower bandwidth and CPU processing.495  // Now we're down to about 500kb. In fact, looking at metrics we've collected with496  // rel="expect" examples and other documents, the impact on documents smaller than497  // that is within the noise. That's because there's enough happening within that498  // start up to not make HTML streaming not significantly better.499  // Content above the fold tends to be about 100-200kb tops. Therefore 500kb should500  // be enough head room for a good loading state. After that you should use501  // Suspense or SuspenseList to improve it.502  // Since this is highly related to the reason you would adjust the503  // progressiveChunkSize option, and always has to be higher, we define this limit504  // in terms of it. So if you want to increase the limit because you have high505  // bandwidth users, then you can adjust it up. If you are concerned about even506  // slower bandwidth then you can adjust it down.507  return request.progressiveChunkSize * 40; // 512kb by default.508}509510function isEligibleForOutlining(511  request: Request,512  boundary: SuspenseBoundary,513): boolean {514  // For very small boundaries, don't bother producing a fallback for outlining.515  // The larger this limit is, the more we can save on preparing fallbacks in case we end up516  // outlining.517  return (518    (boundary.byteSize > 500 ||519      hasSuspenseyContent(boundary.contentState, /* flushingInShell */ false) ||520      boundary.defer) &&521    // For boundaries that can possibly contribute to the preamble we don't want to outline522    // them regardless of their size since the fallbacks should only be emitted if we've523    // errored the boundary.524    boundary.preamble === null525  );526}527528function defaultErrorHandler(error: mixed) {529  if (530    typeof error === 'object' &&531    error !== null &&532    typeof error.environmentName === 'string'533  ) {534    // This was a Server error. We print the environment name in a badge just like we do with535    // replays of console logs to indicate that the source of this throw as actually the Server.536    bindToConsole('error', [error], error.environmentName)();537  } else {538    console['error'](error); // Don't transform to our wrapper539  }540  return null;541}542543function RequestInstance(544  this: $FlowFixMe,545  resumableState: ResumableState,546  renderState: RenderState,547  rootFormatContext: FormatContext,548  progressiveChunkSize: void | number,549  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),550  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),551  onAllReady: void | (() => void),552  onShellReady: void | (() => void),553  onShellError: void | ((error: mixed) => void),554  onFatalError: void | ((error: mixed) => void),555  formState: void | null | ReactFormState<any, any>,556) {557  const pingedTasks: Array<Task> = [];558  const abortSet: Set<Task> = new Set();559  this.destination = null;560  this.flushScheduled = false;561  this.resumableState = resumableState;562  this.renderState = renderState;563  this.rootFormatContext = rootFormatContext;564  this.progressiveChunkSize =565    progressiveChunkSize === undefined566      ? DEFAULT_PROGRESSIVE_CHUNK_SIZE567      : progressiveChunkSize;568  // $FlowFixMe[constant-condition]569  this.status = isWorkLoopExternallyDriven ? OPEN : OPENING;570  this.fatalError = null;571  this.aborted = false;572  this.nextSegmentId = 0;573  this.allPendingTasks = 0;574  this.pendingRootTasks = 0;575  this.completedRootSegment = null;576  this.completedPreambleSegments = null;577  this.byteSize = 0;578  this.abortableTasks = abortSet;579  this.pingedTasks = pingedTasks;580  this.currentTask = null;581  this.clientRenderedBoundaries = [] as Array<SuspenseBoundary>;582  this.completedBoundaries = [] as Array<SuspenseBoundary>;583  this.partialBoundaries = [] as Array<SuspenseBoundary>;584  this.trackedPostpones = null;585  this.postponedState = null;586  this.onError = onError === undefined ? defaultErrorHandler : onError;587  this.onBrowserBailout =588    onBrowserBailout === undefined ? noop : onBrowserBailout;589  this.onAllReady = onAllReady === undefined ? noop : onAllReady;590  this.onShellReady = onShellReady === undefined ? noop : onShellReady;591  this.onShellError = onShellError === undefined ? noop : onShellError;592  this.onFatalError = onFatalError === undefined ? noop : onFatalError;593  this.renderLifetimeController = null;594  this.formState = formState === undefined ? null : formState;595  if (__DEV__) {596    this.didWarnForKey = null;597  }598}599600export function createRequest(601  children: ReactNodeList,602  resumableState: ResumableState,603  renderState: RenderState,604  rootFormatContext: FormatContext,605  progressiveChunkSize: void | number,606  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),607  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),608  onAllReady: void | (() => void),609  onShellReady: void | (() => void),610  onShellError: void | ((error: mixed) => void),611  onFatalError: void | ((error: mixed) => void),612  formState: void | null | ReactFormState<any, any>,613): Request {614  if (__DEV__) {615    resetOwnerStackLimit();616  }617618  // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors619  const request: Request = new RequestInstance(620    resumableState,621    renderState,622    rootFormatContext,623    progressiveChunkSize,624    onError,625    onBrowserBailout,626    onAllReady,627    onShellReady,628    onShellError,629    onFatalError,630    formState,631  );632633  // This segment represents the root fallback.634  const rootSegment = createPendingSegment(635    request,636    0,637    null,638    rootFormatContext,639    // Root segments are never embedded in Text on either edge640    false,641    false,642  );643  // There is no parent so conceptually, we're unblocked to flush this segment.644  rootSegment.parentFlushed = true;645  const rootTask = createRenderTask(646    request,647    null,648    children,649    -1,650    null,651    rootSegment,652    null,653    null,654    request.abortableTasks,655    null,656    rootFormatContext,657    rootContextSnapshot,658    emptyTreeContext,659    null,660    null,661    emptyContextObject,662    null,663  );664  pushComponentStack(rootTask);665  request.pingedTasks.push(rootTask);666  return request;667}668669export function createPrerenderRequest(670  children: ReactNodeList,671  resumableState: ResumableState,672  renderState: RenderState,673  rootFormatContext: FormatContext,674  progressiveChunkSize: void | number,675  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),676  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),677  onAllReady: void | (() => void),678  onShellReady: void | (() => void),679  onShellError: void | ((error: mixed) => void),680  onFatalError: void | ((error: mixed) => void),681): Request {682  const request = createRequest(683    children,684    resumableState,685    renderState,686    rootFormatContext,687    progressiveChunkSize,688    onError,689    onBrowserBailout,690    onAllReady,691    onShellReady,692    onShellError,693    onFatalError,694    undefined,695  );696  // Start tracking postponed holes during this render.697  request.trackedPostpones = {698    workingMap: new Map(),699    rootNodes: [],700    rootSlots: null,701  };702  return request;703}704705export function resumeRequest(706  children: ReactNodeList,707  postponedState: PostponedState,708  renderState: RenderState,709  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),710  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),711  onAllReady: void | (() => void),712  onShellReady: void | (() => void),713  onShellError: void | ((error: mixed) => void),714  onFatalError: void | ((error: mixed) => void),715): Request {716  if (__DEV__) {717    resetOwnerStackLimit();718  }719720  // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors721  const request: Request = new RequestInstance(722    postponedState.resumableState,723    renderState,724    postponedState.rootFormatContext,725    postponedState.progressiveChunkSize,726    onError,727    onBrowserBailout,728    onAllReady,729    onShellReady,730    onShellError,731    onFatalError,732    null,733  );734  request.nextSegmentId = postponedState.nextSegmentId;735736  if (typeof postponedState.replaySlots === 'number') {737    // We have a resume slot at the very root. This is effectively just a full rerender.738    const rootSegment = createPendingSegment(739      request,740      0,741      null,742      postponedState.rootFormatContext,743      // Root segments are never embedded in Text on either edge744      false,745      false,746    );747    // There is no parent so conceptually, we're unblocked to flush this segment.748    rootSegment.parentFlushed = true;749    const rootTask = createRenderTask(750      request,751      null,752      children,753      -1,754      null,755      rootSegment,756      null,757      null,758      request.abortableTasks,759      null,760      postponedState.rootFormatContext,761      rootContextSnapshot,762      emptyTreeContext,763      null,764      null,765      emptyContextObject,766      null,767    );768    pushComponentStack(rootTask);769    request.pingedTasks.push(rootTask);770    return request;771  }772773  const replay: ReplaySet = {774    nodes: postponedState.replayNodes,775    slots: postponedState.replaySlots,776    pendingTasks: 0,777  };778  const rootTask = createReplayTask(779    request,780    null,781    replay,782    children,783    -1,784    null,785    null,786    request.abortableTasks,787    null,788    postponedState.rootFormatContext,789    rootContextSnapshot,790    emptyTreeContext,791    null,792    null,793    emptyContextObject,794    null,795  );796  pushComponentStack(rootTask);797  request.pingedTasks.push(rootTask);798  return request;799}800801export function resumeAndPrerenderRequest(802  children: ReactNodeList,803  postponedState: PostponedState,804  renderState: RenderState,805  onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),806  onBrowserBailout: void | ((error: mixed, errorInfo: ErrorInfo) => void),807  onAllReady: void | (() => void),808  onShellReady: void | (() => void),809  onShellError: void | ((error: mixed) => void),810  onFatalError: void | ((error: mixed) => void),811): Request {812  const request = resumeRequest(813    children,814    postponedState,815    renderState,816    onError,817    onBrowserBailout,818    onAllReady,819    onShellReady,820    onShellError,821    onFatalError,822  );823  // Start tracking postponed holes during this render.824  request.trackedPostpones = {825    workingMap: new Map(),826    rootNodes: [],827    rootSlots: null,828  };829  return request;830}831832let currentRequest: null | Request = null;833834export function resolveRequest(): null | Request {835  if (currentRequest) return currentRequest;836  // $FlowFixMe[constant-condition]837  if (supportsRequestStorage) {838    const store = requestStorage.getStore();839    if (store) return store;840  }841  return null;842}843844function pingTask(request: Request, task: Task): void {845  const pingedTasks = request.pingedTasks;846  pingedTasks.push(task);847  // $FlowFixMe[constant-condition]848  if (isWorkLoopExternallyDriven) {849    return;850  } else {851    if (request.pingedTasks.length === 1) {852      request.flushScheduled = request.destination !== null;853      if (request.trackedPostpones !== null || request.status === OPENING) {854        scheduleMicrotask(() => performWork(request));855      } else {856        scheduleWork(() => performWork(request));857      }858    }859  }860}861862function pingRejectedTask(request: Request, task: Task, error: mixed): void {863  if (!request.aborted) {864    // Replaying the task is what gives ordinary render errors their complete865    // component stack.866    pingTask(request, task);867    return;868  }869  if (!task.abortSet.delete(task)) {870    // finishAbort already completed this task with the request's abort reason.871    return;872  }873  // abortTask synchronously claimed this task before abort listeners could874  // reject its wakeable. Finish it with the more specific reason before the875  // scheduled final abort uses the reason for the whole request.876  if (__DEV__) {877    finishAbortedTaskDEV(task, request, error);878  } else {879    finishAbortedTask(task, request, error);880  }881}882883function createSuspenseBoundary(884  request: Request,885  row: null | SuspenseListRow,886  fallbackAbortableTasks: Set<Task>,887  preamble: null | Preamble,888  defer: boolean,889): SuspenseBoundary {890  const boundary: SuspenseBoundary = {891    status: PENDING,892    rootSegmentID: -1,893    parentFlushed: false,894    pendingTasks: 0,895    row: row,896    completedSegments: [],897    byteSize: 0,898    defer: defer,899    fallbackAbortableTasks,900    errorDigest: null,901    contentState: createHoistableState(),902    fallbackState: createHoistableState(),903    preamble,904    tracked: null,905  };906  if (__DEV__) {907    // DEV-only fields for hidden class908    boundary.errorMessage = null;909    boundary.errorStack = null;910    boundary.errorComponentStack = null;911  }912  if (row !== null) {913    // This boundary will block this row from completing.914    row.pendingTasks++;915    const blockedBoundaries = row.boundaries;916    if (blockedBoundaries !== null) {917      // Previous rows will block this boundary itself from completing.918      request.allPendingTasks++;919      boundary.pendingTasks++;920      blockedBoundaries.push(boundary);921    }922    const inheritedHoistables = row.inheritedHoistables;923    if (inheritedHoistables !== null) {924      hoistHoistables(boundary.contentState, inheritedHoistables);925    }926  }927  return boundary;928}929930function createRenderTask(931  request: Request,932  thenableState: ThenableState | null,933  node: ReactNodeList,934  childIndex: number,935  blockedBoundary: Root | SuspenseBoundary,936  blockedSegment: Segment,937  blockedPreamble: null | PreambleState,938  hoistableState: null | HoistableState,939  abortSet: Set<Task>,940  keyPath: Root | KeyNode,941  formatContext: FormatContext,942  context: ContextSnapshot,943  treeContext: TreeContext,944  row: null | SuspenseListRow,945  componentStack: null | ComponentStackNode,946  legacyContext: LegacyContext,947  debugTask: null | ConsoleTask,948): RenderTask {949  request.allPendingTasks++;950  if (blockedBoundary === null) {951    request.pendingRootTasks++;952  } else {953    blockedBoundary.pendingTasks++;954  }955  if (row !== null) {956    row.pendingTasks++;957  }958  const task: RenderTask = {959    replay: null,960    node,961    childIndex,962    ping: {963      resolve: () => pingTask(request, task),964      reject: error => pingRejectedTask(request, task, error),965    },966    blockedBoundary,967    blockedSegment,968    blockedPreamble,969    hoistableState,970    abortSet,971    keyPath,972    formatContext,973    context,974    treeContext,975    row,976    componentStack,977    thenableState,978  } as any;979  if (!disableLegacyContext) {980    task.legacyContext = legacyContext;981  }982  if (__DEV__) {983    task.debugTask = debugTask;984  }985  abortSet.add(task);986  return task;987}988989function createReplayTask(990  request: Request,991  thenableState: ThenableState | null,992  replay: ReplaySet,993  node: ReactNodeList,994  childIndex: number,995  blockedBoundary: Root | SuspenseBoundary,996  hoistableState: null | HoistableState,997  abortSet: Set<Task>,998  keyPath: Root | KeyNode,999  formatContext: FormatContext,1000  context: ContextSnapshot,1001  treeContext: TreeContext,1002  row: null | SuspenseListRow,1003  componentStack: null | ComponentStackNode,1004  legacyContext: LegacyContext,1005  debugTask: null | ConsoleTask,1006): ReplayTask {1007  request.allPendingTasks++;1008  if (blockedBoundary === null) {1009    request.pendingRootTasks++;1010  } else {1011    blockedBoundary.pendingTasks++;1012  }1013  if (row !== null) {1014    row.pendingTasks++;1015  }1016  replay.pendingTasks++;1017  const task: ReplayTask = {1018    replay,1019    node,1020    childIndex,1021    ping: {1022      resolve: () => pingTask(request, task),1023      reject: error => pingRejectedTask(request, task, error),1024    },1025    blockedBoundary,1026    blockedSegment: null,1027    blockedPreamble: null,1028    hoistableState,1029    abortSet,1030    keyPath,1031    formatContext,1032    context,1033    treeContext,1034    row,1035    componentStack,1036    thenableState,1037  } as any;1038  if (!disableLegacyContext) {1039    task.legacyContext = legacyContext;1040  }1041  if (__DEV__) {1042    task.debugTask = debugTask;1043  }1044  abortSet.add(task);1045  return task;1046}10471048function createPendingSegment(1049  request: Request,1050  index: number,1051  boundary: null | SuspenseBoundary,1052  parentFormatContext: FormatContext,1053  lastPushedText: boolean,1054  textEmbedded: boolean,1055): Segment {1056  return {1057    status: PENDING,1058    parentFlushed: false,1059    id: -1, // lazily assigned later1060    index,1061    chunks: [],1062    children: [],1063    preambleChildren: [],1064    parentFormatContext,1065    boundary,1066    lastPushedText,1067    textEmbedded,1068  };1069}10701071function getCurrentStackInDEV(): string {1072  if (__DEV__) {1073    if (currentTaskInDEV === null || currentTaskInDEV.componentStack === null) {1074      return '';1075    }1076    return getOwnerStackByComponentStackNodeInDev(1077      currentTaskInDEV.componentStack,1078    );1079  }1080  return '';1081}10821083function getStackFromNode(stackNode: ComponentStackNode): string {1084  return getStackByComponentStackNode(stackNode);1085}10861087function pushHaltedAwaitOnComponentStack(1088  task: Task,1089  debugInfo: void | null | ReactDebugInfo,1090): void {1091  if (!__DEV__) {1092    // eslint-disable-next-line react-internal/prod-error-codes1093    throw new Error(1094      'pushHaltedAwaitOnComponentStack should never be called in production. This is a bug in React.',1095    );1096  }1097  if (debugInfo != null) {1098    for (let i = debugInfo.length - 1; i >= 0; i--) {1099      const info = debugInfo[i];1100      if (info.awaited != null) {1101        const asyncInfo: ReactAsyncInfo = info as any;1102        const bestStack =1103          asyncInfo.debugStack == null ? asyncInfo.awaited : asyncInfo;1104        if (bestStack.debugStack !== undefined) {1105          task.componentStack = {1106            parent: task.componentStack,1107            type: asyncInfo,1108            owner: bestStack.owner,1109            stack: bestStack.debugStack,1110          };1111          task.debugTask = bestStack.debugTask as any;1112          break;1113        }1114      }1115    }1116  }1117}11181119// performWork + retryTask without mutation1120function rerenderStalledTask(request: Request, task: Task): void {1121  const prevStatus = request.status;1122  const prevAborted = request.aborted;1123  request.status = STALLED_DEV;1124  // This diagnostic replay must reach the suspended call site instead of1125  // taking the abort path.1126  request.aborted = false;11271128  const prevContext = getActiveContext();1129  const prevDispatcher = ReactSharedInternals.H;1130  ReactSharedInternals.H = HooksDispatcher;1131  const prevAsyncDispatcher = ReactSharedInternals.A;1132  ReactSharedInternals.A = DefaultAsyncDispatcher;11331134  const prevRequest = currentRequest;1135  currentRequest = request;11361137  const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;1138  ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;11391140  const prevResumableState = currentResumableState;1141  setCurrentResumableState(request.resumableState);1142  switchContext(task.context);1143  const prevTaskInDEV = currentTaskInDEV;1144  setCurrentTaskInDEV(task);1145  try {1146    retryNode(request, task);1147  } catch (x) {1148    // Suspended again.1149    resetHooksState();1150  } finally {1151    setCurrentTaskInDEV(prevTaskInDEV);1152    setCurrentResumableState(prevResumableState);11531154    ReactSharedInternals.H = prevDispatcher;1155    ReactSharedInternals.A = prevAsyncDispatcher;11561157    ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;1158    if (prevDispatcher === HooksDispatcher) {1159      // This means that we were in a reentrant work loop. This could happen1160      // in a renderer that supports synchronous work like renderToString,1161      // when it's called from within another renderer.1162      // Normally we don't bother switching the contexts to their root/default1163      // values when leaving because we'll likely need the same or similar1164      // context again. However, when we're inside a synchronous loop like this1165      // we'll to restore the context to what it was before returning.1166      switchContext(prevContext);1167    }1168    currentRequest = prevRequest;1169    request.status = prevStatus;1170    request.aborted = prevAborted;1171  }1172}11731174function pushSuspendedCallSiteOnComponentStack(1175  request: Request,1176  task: Task,1177): void {1178  setCaptureSuspendedCallSiteDEV(true);1179  const restoreThenableState = ensureSuspendableThenableStateDEV(1180    // refined at the callsite1181    task.thenableState as any as ThenableState,1182  );1183  try {1184    rerenderStalledTask(request, task);1185  } finally {1186    restoreThenableState();1187    setCaptureSuspendedCallSiteDEV(false);1188  }11891190  const suspendCallSiteStack = getSuspendedCallSiteStackDEV();1191  const suspendCallSiteDebugTask = getSuspendedCallSiteDebugTaskDEV();11921193  if (suspendCallSiteStack !== null) {1194    const ownerStack = task.componentStack;1195    task.componentStack = {1196      // The owner of the suspended call site would be the owner of this task.1197      // We need the task itself otherwise we'd miss a frame.1198      owner: ownerStack,1199      parent: suspendCallSiteStack.parent,1200      stack: suspendCallSiteStack.stack,1201      type: suspendCallSiteStack.type,1202    };1203  }1204  task.debugTask = suspendCallSiteDebugTask;1205}12061207function pushServerComponentStack(1208  task: Task,1209  debugInfo: void | null | ReactDebugInfo,1210): void {1211  if (!__DEV__) {1212    // eslint-disable-next-line react-internal/prod-error-codes1213    throw new Error(1214      'pushServerComponentStack should never be called in production. This is a bug in React.',1215    );1216  }1217  // Build a Server Component parent stack from the debugInfo.1218  if (debugInfo != null) {1219    const stack: ReactDebugInfo = debugInfo;1220    for (let i = 0; i < stack.length; i++) {1221      const componentInfo: ReactComponentInfo = stack[i] as any;1222      if (typeof componentInfo.name !== 'string') {1223        continue;1224      }1225      if (componentInfo.debugStack === undefined) {1226        continue;1227      }1228      task.componentStack = {1229        parent: task.componentStack,1230        type: componentInfo,1231        owner: componentInfo.owner,1232        stack: componentInfo.debugStack,1233      };1234      task.debugTask = componentInfo.debugTask as any;1235    }1236  }1237}12381239function pushComponentStack(task: Task): void {1240  const node = task.node;1241  // Create the Component Stack frame for the element we're about to try.1242  // It's unfortunate that we need to do this refinement twice. Once for1243  // the stack frame and then once again while actually1244  if (typeof node === 'object' && node !== null) {1245    switch ((node as any).$$typeof) {1246      case REACT_ELEMENT_TYPE: {1247        const element: any = node;1248        const type = element.type;1249        const owner = __DEV__ ? element._owner : null;1250        const stack = __DEV__ ? element._debugStack : null;1251        if (__DEV__) {1252          pushServerComponentStack(task, element._debugInfo);1253          task.debugTask = element._debugTask;1254        }1255        task.componentStack = createComponentStackFromType(1256          task.componentStack,1257          type,1258          owner,1259          stack,1260        );1261        break;1262      }1263      case REACT_LAZY_TYPE: {1264        if (__DEV__) {1265          const lazyNode: LazyComponentType<any, any> = node as any;1266          pushServerComponentStack(task, lazyNode._debugInfo);1267        }1268        break;1269      }1270      default: {1271        if (__DEV__) {1272          const maybeUsable: Object = node;1273          if (typeof maybeUsable.then === 'function') {1274            const thenable: Thenable<ReactNodeList> = maybeUsable as any;1275            pushServerComponentStack(task, thenable._debugInfo);1276          }1277        }1278      }1279    }1280  }1281}12821283function createComponentStackFromType(1284  parent: null | ComponentStackNode,1285  type: Function | string | symbol,1286  owner: void | null | ReactComponentInfo | ComponentStackNode, // DEV only1287  stack: void | null | string | Error, // DEV only1288): ComponentStackNode {1289  if (__DEV__) {1290    return {1291      parent,1292      type,1293      owner,1294      stack,1295    };1296  }1297  return {1298    parent,1299    type,1300  };1301}13021303function replaceSuspenseComponentStackWithSuspenseFallbackStack(1304  componentStack: null | ComponentStackNode,1305): null | ComponentStackNode {1306  if (componentStack === null) {1307    return null;1308  }1309  return createComponentStackFromType(1310    componentStack.parent,1311    'Suspense Fallback',1312    __DEV__ ? componentStack.owner : null,1313    __DEV__ ? componentStack.stack : null,1314  );1315}13161317type ThrownInfo = {1318  componentStack?: string,1319};1320export type ErrorInfo = ThrownInfo;13211322function getThrownInfo(node: null | ComponentStackNode): ThrownInfo {1323  const errorInfo: ThrownInfo = {};1324  if (node) {1325    Object.defineProperty(errorInfo, 'componentStack', {1326      configurable: true,1327      enumerable: true,1328      get() {1329        // Lazyily generate the stack since it's expensive.1330        const stack = getStackFromNode(node);1331        Object.defineProperty(errorInfo, 'componentStack', {1332          value: stack,1333        });1334        return stack;1335      },1336    });1337  }1338  return errorInfo;1339}13401341function encodeErrorForBoundary(1342  boundary: SuspenseBoundary,1343  digest: ?string,1344  error: mixed,1345  thrownInfo: ThrownInfo,1346  wasAborted: boolean,1347) {1348  boundary.errorDigest = digest;1349  if (__DEV__) {1350    if (isRecoverableError(error)) {1351      boundary.errorMessage = wasAborted1352        ? 'Switched to client rendering because the server render was aborted ' +1353          'with a request to render on the client.'1354        : 'Switched to client rendering because a component requested it.';1355      boundary.errorComponentStack = thrownInfo.componentStack;1356      return;1357    }1358    let message, stack;1359    // In dev we additionally encode the error message and component stack on the boundary1360    if (error instanceof Error) {1361      // eslint-disable-next-line react-internal/safe-string-coercion1362      message = String(error.message);1363      // eslint-disable-next-line react-internal/safe-string-coercion1364      stack = String(error.stack);1365    } else if (typeof error === 'object' && error !== null) {1366      message = describeObjectForErrorMessage(error);1367      stack = null;1368    } else {1369      // eslint-disable-next-line react-internal/safe-string-coercion1370      message = String(error);1371      stack = null;1372    }1373    const prefix = wasAborted1374      ? 'Switched to client rendering because the server rendering aborted due to:\n\n'1375      : 'Switched to client rendering because the server rendering errored:\n\n';1376    boundary.errorMessage = prefix + message;1377    boundary.errorStack = stack !== null ? prefix + stack : null;1378    boundary.errorComponentStack = thrownInfo.componentStack;1379  }1380}13811382function logRecoverableError(1383  request: Request,1384  error: any,1385  errorInfo: ThrownInfo,1386  debugTask: null | ConsoleTask,1387): ?string {1388  if (isRecoverableError(error)) {1389    logBrowserBailout(request, error, errorInfo, debugTask);1390    return REACT_RECOVERABLE_DIGEST;1391  }13921393  // If this callback errors, we intentionally let that error bubble up to become a fatal error1394  // so that someone fixes the error reporting instead of hiding it.1395  const onError = request.onError;1396  const errorDigest =1397    __DEV__ && debugTask1398      ? debugTask.run(onError.bind(null, error, errorInfo))1399      : onError(error, errorInfo);1400  if (errorDigest != null && typeof errorDigest !== 'string') {1401    // We used to throw here but since this gets called from a variety of unprotected places it1402    // seems better to just warn and discard the returned value.1403    if (__DEV__) {1404      console.error(1405        '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',1406        typeof errorDigest,1407      );1408    }1409    return;1410  }1411  // An empty digest is reserved for React's internal client-render signal.1412  // Historically an empty digest was omitted from the wire format, so1413  // normalizing it to undefined preserves the existing user-space semantics.1414  return errorDigest === '' ? undefined : errorDigest;1415}14161417function logBrowserBailout(1418  request: Request,1419  error: mixed,1420  errorInfo: ThrownInfo,1421  debugTask: null | ConsoleTask,1422): void {1423  // If this callback errors, we intentionally let that error bubble up to1424  // become a fatal error, matching the behavior of onError.1425  const onBrowserBailout = request.onBrowserBailout;1426  if (__DEV__ && debugTask) {1427    debugTask.run(onBrowserBailout.bind(null, error, errorInfo));1428  } else {1429    onBrowserBailout(error, errorInfo);1430  }1431}14321433function fatalError(1434  request: Request,1435  error: mixed,1436  errorInfo: ThrownInfo,1437  debugTask: null | ConsoleTask,1438): void {1439  // This is called outside error handling code such as if the root errors outside1440  // a suspense boundary or if the root suspense boundary's fallback errors.1441  // It's also called if React itself or its host configs errors.1442  const onShellError = request.onShellError;1443  const onFatalError = request.onFatalError;1444  // Once the shell has completed it can't error anymore, so onShellError only1445  // fires while root tasks are still pending. onFatalError always fires because1446  // the error is always fatal to the request.1447  const shellComplete = request.pendingRootTasks === 0;1448  if (__DEV__ && debugTask) {1449    if (!shellComplete) {1450      debugTask.run(onShellError.bind(null, error));1451    }1452    debugTask.run(onFatalError.bind(null, error));1453  } else {1454    if (!shellComplete) {1455      onShellError(error);1456    }1457    onFatalError(error);1458  }1459  endRenderLifetime(request);1460  if (request.destination !== null) {1461    request.status = CLOSED;1462    closeWithError(request.destination, error);1463  } else {1464    request.status = CLOSING;1465    // abort() already stored the reason that every remaining task must1466    // observe. This error may only be a fatal diagnostic derived from it.1467    if (!request.aborted) {1468      request.fatalError = error;1469    }1470  }1471}14721473function renderSuspenseBoundary(1474  request: Request,1475  someTask: Task,1476  keyPath: KeyNode,1477  props: SuspenseProps,1478): void {1479  if (someTask.replay !== null) {1480    // If we're replaying through this pass, it means we're replaying through1481    // an already completed Suspense boundary. It's too late to do anything about it1482    // so we can just render through it.1483    const prevKeyPath = someTask.keyPath;1484    const prevContext = someTask.formatContext;1485    const prevRow = someTask.row;1486    someTask.keyPath = keyPath;1487    someTask.formatContext = getSuspenseContentFormatContext(1488      request.resumableState,1489      prevContext,1490    );1491    someTask.row = null;1492    const content: ReactNodeList = props.children;1493    try {1494      renderNode(request, someTask, content, -1);1495    } finally {1496      someTask.keyPath = prevKeyPath;1497      someTask.formatContext = prevContext;1498      someTask.row = prevRow;1499    }1500    return;1501  }1502  // $FlowFixMe[incompatible-type]: Refined.1503  const task: RenderTask = someTask;15041505  const prevKeyPath = task.keyPath;1506  const prevContext = task.formatContext;1507  const prevRow = task.row;1508  const parentBoundary = task.blockedBoundary;1509  const parentPreamble = task.blockedPreamble;1510  const parentHoistableState = task.hoistableState;1511  const parentSegment = task.blockedSegment;15121513  // Each time we enter a suspense boundary, we split out into a new segment for1514  // the fallback so that we can later replace that segment with the content.1515  // This also lets us split out the main content even if it doesn't suspend,1516  // in case it ends up generating a large subtree of content.1517  const fallback: ReactNodeList = props.fallback;1518  const content: ReactNodeList = props.children;1519  const defer: boolean = enableCPUSuspense && props.defer === true;15201521  const fallbackAbortSet: Set<Task> = new Set();1522  const newBoundary = createSuspenseBoundary(1523    request,1524    task.row,1525    fallbackAbortSet,1526    canHavePreamble(task.formatContext) ? createPreamble() : null,1527    defer,1528  );15291530  const insertionIndex = parentSegment.chunks.length;1531  // The children of the boundary segment is actually the fallback.1532  const boundarySegment = createPendingSegment(1533    request,1534    insertionIndex,1535    newBoundary,1536    task.formatContext,1537    // boundaries never require text embedding at their edges because comment nodes bound them1538    false,1539    false,1540  );1541  parentSegment.children.push(boundarySegment);1542  // The parentSegment has a child Segment at this index so we reset the lastPushedText marker on the parent1543  parentSegment.lastPushedText = false;15441545  // This segment is the actual child content. We can start rendering that immediately.1546  const contentRootSegment = createPendingSegment(1547    request,1548    0,1549    null,1550    task.formatContext,1551    // boundaries never require text embedding at their edges because comment nodes bound them1552    false,1553    false,1554  );1555  // We mark the root segment as having its parent flushed. It's not really flushed but there is1556  // no parent segment so there's nothing to wait on.1557  contentRootSegment.parentFlushed = true;15581559  const trackedPostpones = request.trackedPostpones;1560  if (trackedPostpones !== null || defer) {1561    // This is a prerender or deferred boundary. In this mode we want to render the fallback synchronously1562    // and schedule the content to render later. This is the opposite of what we do during a normal render1563    // where we try to skip rendering the fallback if the content itself can render synchronously15641565    // Stash the original stack frame.1566    const suspenseComponentStack = task.componentStack;15671568    const fallbackKeyPath: KeyNode = [1569      keyPath[0],1570      'Suspense Fallback',1571      keyPath[2],1572    ];1573    if (trackedPostpones !== null) {1574      const fallbackReplayNode: ReplayNode = [1575        fallbackKeyPath[1],1576        fallbackKeyPath[2],1577        [] as Array<ReplayNode>,1578        null,1579      ];1580      trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode);1581      newBoundary.tracked = {1582        contentKeyPath: keyPath,1583        // We are rendering the fallback before the boundary content so we keep track of1584        // the fallback replay node until we determine if the primary content suspends1585        fallbackNode: fallbackReplayNode,1586      };1587    }15881589    task.blockedSegment = boundarySegment;1590    task.blockedPreamble =1591      newBoundary.preamble === null ? null : newBoundary.preamble.fallback;1592    task.keyPath = fallbackKeyPath;1593    task.formatContext = getSuspenseFallbackFormatContext(1594      request.resumableState,1595      prevContext,1596    );1597    task.componentStack =1598      replaceSuspenseComponentStackWithSuspenseFallbackStack(1599        suspenseComponentStack,1600      );1601    try {1602      renderNode(request, task, fallback, -1);1603      pushSegmentFinale(1604        boundarySegment.chunks,1605        request.renderState,1606        boundarySegment.lastPushedText,1607        boundarySegment.textEmbedded,1608      );1609      boundarySegment.status = COMPLETED;1610      finishedSegment(request, parentBoundary, boundarySegment);1611    } catch (thrownValue: mixed) {1612      if (request.aborted) {1613        boundarySegment.status = ABORTED;1614      } else {1615        boundarySegment.status = ERRORED;1616      }1617      throw thrownValue;1618    } finally {1619      task.blockedSegment = parentSegment;1620      task.blockedPreamble = parentPreamble;1621      task.keyPath = prevKeyPath;1622      task.formatContext = prevContext;1623    }16241625    // We create a suspended task for the primary content because we want to allow1626    // sibling fallbacks to be rendered first.1627    const suspendedPrimaryTask = createRenderTask(1628      request,1629      null,1630      content,1631      -1,1632      newBoundary,1633      contentRootSegment,1634      newBoundary.preamble === null ? null : newBoundary.preamble.content,1635      newBoundary.contentState,1636      task.abortSet,1637      keyPath,1638      getSuspenseContentFormatContext(1639        request.resumableState,1640        task.formatContext,1641      ),1642      task.context,1643      task.treeContext,1644      null, // The row gets reset inside the Suspense boundary.1645      suspenseComponentStack,1646      !disableLegacyContext ? task.legacyContext : emptyContextObject,1647      __DEV__ ? task.debugTask : null,1648    );1649    pushComponentStack(suspendedPrimaryTask);1650    request.pingedTasks.push(suspendedPrimaryTask);1651  } else {1652    // This is a normal render. We will attempt to synchronously render the boundary content1653    // If it is successful we will elide the fallback task but if it suspends or errors we schedule1654    // the fallback to render. Unlike with prerenders we attempt to deprioritize the fallback render16551656    // Currently this is running synchronously. We could instead schedule this to pingedTasks.1657    // I suspect that there might be some efficiency benefits from not creating the suspended task1658    // and instead just using the stack if possible.1659    // TODO: Call this directly instead of messing with saving and restoring contexts.16601661    // We can reuse the current context and task to render the content immediately without1662    // context switching. We just need to temporarily switch which boundary and which segment1663    // we're writing to. If something suspends, it'll spawn new suspended task with that context.1664    task.blockedBoundary = newBoundary;1665    task.blockedPreamble =1666      newBoundary.preamble === null ? null : newBoundary.preamble.content;1667    task.hoistableState = newBoundary.contentState;1668    task.blockedSegment = contentRootSegment;1669    task.keyPath = keyPath;1670    task.formatContext = getSuspenseContentFormatContext(1671      request.resumableState,1672      prevContext,1673    );1674    task.row = null;1675    try {1676      // We use the safe form because we don't handle suspending here. Only error handling.1677      renderNode(request, task, content, -1);1678      pushSegmentFinale(1679        contentRootSegment.chunks,1680        request.renderState,1681        contentRootSegment.lastPushedText,1682        contentRootSegment.textEmbedded,1683      );1684      contentRootSegment.status = COMPLETED;1685      finishedSegment(request, newBoundary, contentRootSegment);1686      queueCompletedSegment(newBoundary, contentRootSegment);1687      if (newBoundary.pendingTasks === 0 && newBoundary.status === PENDING) {1688        // This must have been the last segment we were waiting on. This boundary is now complete.1689        newBoundary.status = COMPLETED;1690        // Therefore we won't need the fallback. We early return so that we don't have to create1691        // the fallback. However, if this boundary ended up big enough to be eligible for outlining1692        // we can't do that because we might still need the fallback if we outline it.1693        if (!isEligibleForOutlining(request, newBoundary)) {1694          if (prevRow !== null) {1695            // If we have synchronously completed the boundary and it's not eligible for outlining1696            // then we don't have to wait for it to be flushed before we unblock future rows.1697            // This lets us inline small rows in order.1698            if (--prevRow.pendingTasks === 0) {1699              finishSuspenseListRow(request, prevRow);1700            }1701          }1702          if (request.pendingRootTasks === 0 && task.blockedPreamble) {1703            // The root is complete and this boundary may contribute part of the preamble.1704            // We eagerly attempt to prepare the preamble here because we expect most requests1705            // to have few boundaries which contribute preambles and it allow us to do this1706            // preparation work during the work phase rather than the when flushing.1707            preparePreamble(request);1708          }1709          return;1710        }1711      } else {1712        const boundaryRow = prevRow;1713        if (boundaryRow !== null && boundaryRow.together) {1714          tryToResolveTogetherRow(request, boundaryRow);1715        }1716      }1717    } catch (thrownValue: mixed) {1718      newBoundary.status = CLIENT_RENDERED;1719      let error: mixed;1720      if (request.aborted) {1721        contentRootSegment.status = ABORTED;1722        error = request.fatalError;1723      } else {1724        contentRootSegment.status = ERRORED;1725        error = thrownValue;1726      }17271728      const thrownInfo = getThrownInfo(task.componentStack);1729      const errorDigest = logRecoverableError(1730        request,1731        error,1732        thrownInfo,1733        __DEV__ ? task.debugTask : null,1734      );1735      encodeErrorForBoundary(1736        newBoundary,1737        errorDigest,1738        error,1739        thrownInfo,1740        false,1741      );17421743      untrackBoundary(request, newBoundary);17441745      // We don't need to decrement any task numbers because we didn't spawn any new task.1746      // We don't need to schedule any task because we know the parent has written yet.1747      // We do need to fallthrough to create the fallback though.1748    } finally {1749      task.blockedBoundary = parentBoundary;1750      task.blockedPreamble = parentPreamble;1751      task.hoistableState = parentHoistableState;1752      task.blockedSegment = parentSegment;1753      task.keyPath = prevKeyPath;1754      task.formatContext = prevContext;1755      task.row = prevRow;1756    }17571758    const fallbackKeyPath: KeyNode = [1759      keyPath[0],1760      'Suspense Fallback',1761      keyPath[2],1762    ];1763    // We create suspended task for the fallback because we don't want to actually work1764    // on it yet in case we finish the main content, so we queue for later.1765    const suspendedFallbackTask = createRenderTask(1766      request,1767      null,1768      fallback,1769      -1,1770      parentBoundary,1771      boundarySegment,1772      newBoundary.preamble === null ? null : newBoundary.preamble.fallback,1773      newBoundary.fallbackState,1774      fallbackAbortSet,1775      fallbackKeyPath,1776      getSuspenseFallbackFormatContext(1777        request.resumableState,1778        task.formatContext,1779      ),1780      task.context,1781      task.treeContext,1782      task.row,1783      replaceSuspenseComponentStackWithSuspenseFallbackStack(1784        task.componentStack,1785      ),1786      !disableLegacyContext ? task.legacyContext : emptyContextObject,1787      __DEV__ ? task.debugTask : null,1788    );1789    pushComponentStack(suspendedFallbackTask);1790    // TODO: This should be queued at a separate lower priority queue so that we only work1791    // on preparing fallbacks if we don't have any more main content to task on.1792    request.pingedTasks.push(suspendedFallbackTask);1793  }1794}17951796function replaySuspenseBoundary(1797  request: Request,1798  task: ReplayTask,1799  keyPath: KeyNode,1800  props: Object,1801  id: number,1802  childNodes: Array<ReplayNode>,1803  childSlots: ResumeSlots,1804  fallbackNodes: Array<ReplayNode>,1805  fallbackSlots: ResumeSlots,1806): void {1807  const prevKeyPath = task.keyPath;1808  const prevContext = task.formatContext;1809  const prevRow = task.row;1810  const previousReplaySet: ReplaySet = task.replay;18111812  const parentBoundary = task.blockedBoundary;1813  const parentHoistableState = task.hoistableState;18141815  const content: ReactNodeList = props.children;1816  const fallback: ReactNodeList = props.fallback;1817  const defer: boolean = enableCPUSuspense && props.defer === true;18181819  const fallbackAbortSet: Set<Task> = new Set();1820  const resumedBoundary = createSuspenseBoundary(1821    request,1822    task.row,1823    fallbackAbortSet,1824    canHavePreamble(task.formatContext) ? createPreamble() : null,1825    defer,1826  );1827  resumedBoundary.parentFlushed = true;1828  // We restore the same id of this boundary as was used during prerender.1829  resumedBoundary.rootSegmentID = id;18301831  const fallbackKeyPath: KeyNode = [1832    keyPath[0],1833    'Suspense Fallback',1834    keyPath[2],1835  ];1836  const trackedPostpones = request.trackedPostpones;1837  if (trackedPostpones !== null) {1838    const fallbackReplayNode: ReplayNode = [1839      fallbackKeyPath[1],1840      fallbackKeyPath[2],1841      [] as Array<ReplayNode>,1842      null,1843    ];1844    trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode);1845    resumedBoundary.tracked = {1846      contentKeyPath: keyPath,1847      fallbackNode: fallbackReplayNode,1848    };1849  }18501851  // We can reuse the current context and task to render the content immediately without1852  // context switching. We just need to temporarily switch which boundary and replay node1853  // we're writing to. If something suspends, it'll spawn new suspended task with that context.1854  task.blockedBoundary = resumedBoundary;1855  task.hoistableState = resumedBoundary.contentState;1856  task.keyPath = keyPath;1857  task.formatContext = getSuspenseContentFormatContext(1858    request.resumableState,1859    prevContext,1860  );1861  task.row = null;1862  task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};18631864  try {1865    // We use the safe form because we don't handle suspending here. Only error handling.1866    renderNode(request, task, content, -1);18671868    if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {1869      throw new Error(1870        "Couldn't find all resumable slots by key/index during replaying. " +1871          "The tree doesn't match so React will fallback to client rendering.",1872      );1873    }1874    task.replay.pendingTasks--;1875    if (1876      resumedBoundary.pendingTasks === 0 &&1877      resumedBoundary.status === PENDING1878    ) {1879      // This must have been the last segment we were waiting on. This boundary is now complete.1880      // Therefore we won't need the fallback. We early return so that we don't have to create1881      // the fallback.1882      resumedBoundary.status = COMPLETED;1883      request.completedBoundaries.push(resumedBoundary);1884      // We restore the parent componentStack. Semantically this is the same as1885      // popComponentStack(task) but we do this instead because it should be slightly1886      // faster1887      return;1888    }1889  } catch (thrownValue: mixed) {1890    resumedBoundary.status = CLIENT_RENDERED;1891    const error = request.aborted ? request.fatalError : thrownValue;1892    const thrownInfo = getThrownInfo(task.componentStack);1893    const errorDigest = logRecoverableError(1894      request,1895      error,1896      thrownInfo,1897      __DEV__ ? task.debugTask : null,1898    );1899    encodeErrorForBoundary(1900      resumedBoundary,1901      errorDigest,1902      error,1903      thrownInfo,1904      false,1905    );19061907    task.replay.pendingTasks--;19081909    // The parent already flushed in the prerender so we need to schedule this to be emitted.1910    request.clientRenderedBoundaries.push(resumedBoundary);19111912    // We don't need to decrement any task numbers because we didn't spawn any new task.1913    // We don't need to schedule any task because we know the parent has written yet.1914    // We do need to fallthrough to create the fallback though.1915  } finally {1916    task.blockedBoundary = parentBoundary;1917    task.hoistableState = parentHoistableState;1918    task.replay = previousReplaySet;1919    task.keyPath = prevKeyPath;1920    task.formatContext = prevContext;1921    task.row = prevRow;1922  }19231924  // We create suspended task for the fallback because we don't want to actually work1925  // on it yet in case we finish the main content, so we queue for later.1926  const fallbackReplay = {1927    nodes: fallbackNodes,1928    slots: fallbackSlots,1929    pendingTasks: 0,1930  };1931  const suspendedFallbackTask = createReplayTask(1932    request,1933    null,1934    fallbackReplay,1935    fallback,1936    -1,1937    parentBoundary,1938    resumedBoundary.fallbackState,1939    fallbackAbortSet,1940    fallbackKeyPath,1941    getSuspenseFallbackFormatContext(1942      request.resumableState,1943      task.formatContext,1944    ),1945    task.context,1946    task.treeContext,1947    task.row,1948    replaceSuspenseComponentStackWithSuspenseFallbackStack(task.componentStack),1949    !disableLegacyContext ? task.legacyContext : emptyContextObject,1950    __DEV__ ? task.debugTask : null,1951  );19521953  pushComponentStack(suspendedFallbackTask);1954  // TODO: This should be queued at a separate lower priority queue so that we only work1955  // on preparing fallbacks if we don't have any more main content to task on.1956  request.pingedTasks.push(suspendedFallbackTask);1957}19581959function finishSuspenseListRow(request: Request, row: SuspenseListRow): void {1960  // This row finished. Now we have to unblock all the next rows that were blocked on this.1961  unblockSuspenseListRow(request, row.next, row.hoistables);1962}19631964function unblockSuspenseListRow(1965  request: Request,1966  unblockedRow: null | SuspenseListRow,1967  inheritedHoistables: null | HoistableState,1968): void {1969  // We do this in a loop to avoid stack overflow for very long lists that get unblocked.1970  while (unblockedRow !== null) {1971    if (inheritedHoistables !== null) {1972      // Hoist any hoistables from the previous row into the next row so that it can be1973      // later transferred to all the rows.1974      hoistHoistables(unblockedRow.hoistables, inheritedHoistables);1975      // Mark the row itself for any newly discovered Suspense boundaries to inherit.1976      // This is different from hoistables because that also includes hoistables from1977      // all the boundaries below this row and not just previous rows.1978      unblockedRow.inheritedHoistables = inheritedHoistables;1979    }1980    // Unblocking the boundaries will decrement the count of this row but we keep it above1981    // zero so they never finish this row recursively.1982    const unblockedBoundaries = unblockedRow.boundaries;1983    if (unblockedBoundaries !== null) {1984      unblockedRow.boundaries = null;1985      for (let i = 0; i < unblockedBoundaries.length; i++) {1986        const unblockedBoundary = unblockedBoundaries[i];1987        if (inheritedHoistables !== null) {1988          hoistHoistables(unblockedBoundary.contentState, inheritedHoistables);1989        }1990        finishedTask(request, unblockedBoundary, null, null);1991      }1992    }1993    // Instead we decrement at the end to keep it all in this loop.1994    unblockedRow.pendingTasks--;1995    if (unblockedRow.pendingTasks > 0) {1996      // Still blocked.1997      break;1998    }1999    inheritedHoistables = unblockedRow.hoistables;2000    unblockedRow = unblockedRow.next;

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.

Generated by searchcode.com · powered by code_get_file + code_get_findings