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 {Chunk, BinaryChunk, Destination} from './ReactServerStreamConfig';1112import type {TemporaryReferenceSet} from './ReactFlightServerTemporaryReferences';1314import {15 enableTaint,16 enableProfilerTimer,17 enableComponentPerformanceTrack,18 enableAsyncDebugInfo,19 enableFlightWeakThenables,20} from 'shared/ReactFeatureFlags';2122import {23 scheduleWork,24 scheduleMicrotask,25 flushBuffered,26 beginWriting,27 writeChunk,28 writeChunkAndReturn,29 stringToChunk,30 typedArrayToBinaryChunk,31 byteLengthOfChunk,32 byteLengthOfBinaryChunk,33 completeWriting,34 close,35 closeWithError,36} from './ReactServerStreamConfig';3738export type {Destination, Chunk} from './ReactServerStreamConfig';3940import type {41 ClientManifest,42 ClientReferenceMetadata,43 ClientReference,44 ClientReferenceKey,45 ServerReference,46 ServerReferenceId,47 Hints,48 HintCode,49 HintModel,50 FormatContext,51} from './ReactFlightServerConfig';52import type {ThenableState} from './ReactFlightThenable';53import type {54 Wakeable,55 Thenable,56 PendingThenable,57 FulfilledThenable,58 RejectedThenable,59 ReactDebugInfo,60 ReactDebugInfoEntry,61 ReactComponentInfo,62 ReactIOInfo,63 ReactAsyncInfo,64 ReactStackTrace,65 ReactCallSite,66 ReactFunctionLocation,67 ReactErrorInfo,68 ReactErrorInfoDev,69 ReactKey,70} from 'shared/ReactTypes';71import type {ReactElement} from 'shared/ReactElementType';72import type {LazyComponent} from 'react/src/ReactLazy';73import type {74 AsyncSequence,75 IONode,76 PromiseNode,77 UnresolvedPromiseNode,78} from './ReactFlightAsyncSequence';7980import {81 resolveClientReferenceMetadata,82 getServerReferenceId,83 getServerReferenceBoundArguments,84 getServerReferenceLocation,85 getClientReferenceKey,86 isClientReference,87 isServerReference,88 supportsRequestStorage,89 requestStorage,90 createHints,91 createRootFormatContext,92 getChildFormatContext,93 initAsyncDebugInfo,94 markAsyncSequenceRootTask,95 getCurrentAsyncSequence,96 getAsyncSequenceFromPromise,97 parseStackTrace,98 parseStackTracePrivate,99 supportsComponentStorage,100 componentStorage,101 unbadgeConsole,102} from './ReactFlightServerConfig';103104import {105 resolveTemporaryReference,106 isOpaqueTemporaryReference,107} from './ReactFlightServerTemporaryReferences';108109import {110 HooksDispatcher,111 prepareToUseHooksForRequest,112 prepareToUseHooksForComponent,113 getThenableStateAfterSuspending,114 getTrackedThenablesAfterRendering,115 resetHooksForRequest,116} from './ReactFlightHooks';117import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';118119import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner';120121import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';122import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';123124import noop from 'shared/noop';125126import {127 callComponentInDEV,128 callLazyInitInDEV,129 callIteratorInDEV,130} from './ReactFlightCallUserSpace';131132import {133 getIteratorFn,134 REACT_ELEMENT_TYPE,135 REACT_LEGACY_ELEMENT_TYPE,136 REACT_FORWARD_REF_TYPE,137 REACT_FRAGMENT_TYPE,138 REACT_LAZY_TYPE,139 REACT_MEMO_TYPE,140 ASYNC_ITERATOR,141 REACT_OPTIMISTIC_KEY,142} from 'shared/ReactSymbols';143144import {145 describeObjectForErrorMessage,146 isGetter,147 isSimpleObject,148 jsxPropsParents,149 jsxChildrenParents,150 objectName,151} from 'shared/ReactSerializationErrors';152153import ReactSharedInternals from './ReactSharedInternalsServer';154import isArray from 'shared/isArray';155import getPrototypeOf from 'shared/getPrototypeOf';156import hasOwnProperty from 'shared/hasOwnProperty';157import binaryToComparableString from 'shared/binaryToComparableString';158159import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';160161import {162 IO_NODE,163 PROMISE_NODE,164 AWAIT_NODE,165 UNRESOLVED_AWAIT_NODE,166 UNRESOLVED_PROMISE_NODE,167} from './ReactFlightAsyncSequence';168169// DEV-only set containing internal objects that should not be limited and turned into getters.170const doNotLimit: WeakSet<Reference> = __DEV__ ? new WeakSet() : (null as any);171172function defaultFilterStackFrame(173 filename: string,174 functionName: string,175): boolean {176 return (177 filename !== '' &&178 !filename.startsWith('node:') &&179 !filename.includes('node_modules')180 );181}182183function devirtualizeURL(url: string): string {184 if (url.startsWith('about://React/')) {185 // This callsite is a virtual fake callsite that came from another Flight client.186 // We need to reverse it back into the original location by stripping its prefix187 // and suffix. We don't need the environment name because it's available on the188 // parent object that will contain the stack.189 const envIdx = url.indexOf('/', 'about://React/'.length);190 const suffixIdx = url.lastIndexOf('?');191 if (envIdx > -1 && suffixIdx > -1) {192 return decodeURI(url.slice(envIdx + 1, suffixIdx));193 }194 }195 return url;196}197198function isPromiseCreationInternal(url: string, functionName: string): boolean {199 // Various internals of the JS VM can create Promises but the call frame of the200 // internals are not very interesting for our purposes so we need to skip those.201 if (url === 'node:internal/async_hooks') {202 // Ignore the stack frames from the async hooks themselves.203 return true;204 }205 if (url !== '') {206 return false;207 }208 // V8 used to name the frames of static methods on the Promise constructor209 // "Function.x" but newer versions name them "Promise.x". We match both.210 switch (functionName) {211 case 'new Promise':212 case 'Function.withResolvers':213 case 'Promise.withResolvers':214 case 'Function.reject':215 case 'Promise.reject':216 case 'Function.resolve':217 case 'Promise.resolve':218 case 'Function.all':219 case 'Promise.all':220 case 'Function.allSettled':221 case 'Promise.allSettled':222 case 'Function.race':223 case 'Promise.race':224 case 'Function.try':225 case 'Promise.try':226 return true;227 default:228 return false;229 }230}231232function stripLeadingPromiseCreationFrames(233 stack: ReactStackTrace,234): ReactStackTrace {235 for (let i = 0; i < stack.length; i++) {236 const callsite = stack[i];237 const functionName = callsite[0];238 const url = callsite[1];239 if (!isPromiseCreationInternal(url, functionName)) {240 if (i > 0) {241 return stack.slice(i);242 } else {243 return stack;244 }245 }246 }247 return [];248}249250function findCalledFunctionNameFromStackTrace(251 request: Request,252 stack: ReactStackTrace,253): string {254 // Gets the name of the first function called from first party code.255 let bestMatch = '';256 const filterStackFrame = request.filterStackFrame;257 for (let i = 0; i < stack.length; i++) {258 const callsite = stack[i];259 const functionName = callsite[0];260 const url = devirtualizeURL(callsite[1]);261 const lineNumber = callsite[2];262 const columnNumber = callsite[3];263 if (264 filterStackFrame(url, functionName, lineNumber, columnNumber) &&265 // Don't consider anonymous code first party even if the filter wants to include them in the stack.266 url !== ''267 ) {268 if (bestMatch === '') {269 // If we had no good stack frames for internal calls, just use the last270 // first party function name.271 return functionName;272 }273 return bestMatch;274 } else {275 bestMatch = functionName;276 }277 }278 return '';279}280281function filterStackTrace(282 request: Request,283 stack: ReactStackTrace,284): ReactStackTrace {285 // Since stacks can be quite large and we pass a lot of them, we filter them out eagerly286 // to save bandwidth even in DEV. We'll also replay these stacks on the client so by287 // stripping them early we avoid that overhead. Otherwise we'd normally just rely on288 // the DevTools or framework's ignore lists to filter them out.289 const filterStackFrame = request.filterStackFrame;290 const filteredStack: ReactStackTrace = [];291 for (let i = 0; i < stack.length; i++) {292 const callsite = stack[i];293 const functionName = callsite[0];294 const url = devirtualizeURL(callsite[1]);295 const lineNumber = callsite[2];296 const columnNumber = callsite[3];297 if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {298 // Use a clone because the Flight protocol isn't yet resilient to deduping299 // objects in the debug info. TODO: Support deduping stacks.300 const clone: ReactCallSite = callsite.slice(0) as any;301 clone[1] = url;302 filteredStack.push(clone);303 }304 }305 return filteredStack;306}307308function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {309 const filterStackFrame = request.filterStackFrame;310 for (let i = 0; i < stack.length; i++) {311 const callsite = stack[i];312 const functionName = callsite[0];313 const url = devirtualizeURL(callsite[1]);314 const lineNumber = callsite[2];315 const columnNumber = callsite[3];316 // Ignore async stack frames because they're not "real". We'd expect to have at least317 // one non-async frame if we're actually executing inside a first party function.318 // Otherwise we might just be in the resume of a third party function that resumed319 // inside a first party stack.320 const isAsync = callsite[6];321 if (322 !isAsync &&323 filterStackFrame(url, functionName, lineNumber, columnNumber) &&324 // Ignore anonymous stack frames like internals. They are also not in first party325 // code even though it might be useful to include them in the final stack.326 url !== ''327 ) {328 return true;329 }330 }331 return false;332}333334function isPromiseAwaitInternal(url: string, functionName: string): boolean {335 // Various internals of the JS VM can await internally on a Promise. If those are at336 // the top of the stack then we don't want to consider them as internal frames. The337 // true "await" conceptually is the thing that called the helper.338 // Ideally we'd also include common third party helpers for this.339 if (url === 'node:internal/async_hooks') {340 // Ignore the stack frames from the async hooks themselves.341 return true;342 }343 if (url !== '') {344 return false;345 }346 // V8 used to name the frames of static methods on the Promise constructor347 // "Function.x" but newer versions name them "Promise.x". We match both.348 switch (functionName) {349 case 'Promise.then':350 case 'Promise.catch':351 case 'Promise.finally':352 case 'Function.reject':353 case 'Promise.reject':354 case 'Function.resolve':355 case 'Promise.resolve':356 case 'Function.all':357 case 'Promise.all':358 case 'Function.allSettled':359 case 'Promise.allSettled':360 case 'Function.any':361 case 'Promise.any':362 case 'Function.race':363 case 'Promise.race':364 case 'Function.try':365 case 'Promise.try':366 case 'Function.withResolvers':367 case 'Promise.withResolvers':368 return true;369 default:370 return false;371 }372}373374export function isAwaitInUserspace(375 request: Request,376 stack: ReactStackTrace,377): boolean {378 let firstFrame = 0;379 while (380 stack.length > firstFrame &&381 isPromiseAwaitInternal(stack[firstFrame][1], stack[firstFrame][0])382 ) {383 // Skip the internal frame that awaits itself.384 firstFrame++;385 }386 if (stack.length > firstFrame) {387 // Check if the very first stack frame that awaited this Promise was in user space.388 // TODO: This doesn't take into account wrapper functions such as our fake .then()389 // in FlightClient which will always be considered third party awaits if you call390 // .then directly.391 const filterStackFrame = request.filterStackFrame;392 const callsite = stack[firstFrame];393 const functionName = callsite[0];394 const url = devirtualizeURL(callsite[1]);395 const lineNumber = callsite[2];396 const columnNumber = callsite[3];397 return (398 filterStackFrame(url, functionName, lineNumber, columnNumber) &&399 url !== ''400 );401 }402 return false;403}404405initAsyncDebugInfo();406407function patchConsole(consoleInst: typeof console, methodName: string) {408 const descriptor = Object.getOwnPropertyDescriptor(consoleInst, methodName);409 if (410 descriptor &&411 (descriptor.configurable || descriptor.writable) &&412 typeof descriptor.value === 'function'413 ) {414 const originalMethod = descriptor.value;415 const originalName = Object.getOwnPropertyDescriptor(416 // $FlowFixMe[incompatible-type]: We should be able to get descriptors from any function.417 originalMethod,418 'name',419 );420 const wrapperMethod = function (this: typeof console) {421 const request = resolveRequest();422 if (methodName === 'assert' && arguments[0]) {423 // assert doesn't emit anything unless first argument is falsy so we can skip it.424 } else if (request !== null) {425 // Extract the stack. Not all console logs print the full stack but they have at426 // least the line it was called from. We could optimize transfer by keeping just427 // one stack frame but keeping it simple for now and include all frames.428 const stack = filterStackTrace(429 request,430 parseStackTracePrivate(new Error('react-stack-top-frame'), 1) || [],431 );432 request.pendingDebugChunks++;433 const owner: null | ReactComponentInfo = resolveOwner();434 const args = Array.from(arguments);435 // Extract the env if this is a console log that was replayed from another env.436 let env = unbadgeConsole(methodName, args);437 if (env === null) {438 // Otherwise add the current environment.439 env = (0, request.environmentName)();440 }441442 emitConsoleChunk(request, methodName, owner, env, stack, args);443 }444 // $FlowFixMe[incompatible-call]445 // $FlowFixMe[incompatible-type]446 return originalMethod.apply(this, arguments);447 };448 if (originalName) {449 Object.defineProperty(450 wrapperMethod,451 // $FlowFixMe[cannot-write] yes it is452 'name',453 originalName,454 );455 }456 Object.defineProperty(consoleInst, methodName, {457 value: wrapperMethod,458 });459 }460}461462// $FlowFixMe[invalid-compare]463if (__DEV__ && typeof console === 'object' && console !== null) {464 // Instrument console to capture logs for replaying on the client.465 patchConsole(console, 'assert');466 patchConsole(console, 'debug');467 patchConsole(console, 'dir');468 patchConsole(console, 'dirxml');469 patchConsole(console, 'error');470 patchConsole(console, 'group');471 patchConsole(console, 'groupCollapsed');472 patchConsole(console, 'groupEnd');473 patchConsole(console, 'info');474 patchConsole(console, 'log');475 patchConsole(console, 'table');476 patchConsole(console, 'trace');477 patchConsole(console, 'warn');478}479480function getCurrentStackInDEV(): string {481 if (__DEV__) {482 const owner: null | ReactComponentInfo = resolveOwner();483 if (owner === null) {484 return '';485 }486 return getOwnerStackByComponentInfoInDev(owner);487 }488 return '';489}490491const ObjectPrototype = Object.prototype;492493const stringify = JSON.stringify;494495type ReactJSONValue =496 | string497 | boolean498 | number499 | null500 | $ReadOnlyArray<ReactClientValue>501 | ReactClientObject;502503// Serializable values504export type ReactClientValue =505 // Server Elements and Lazy Components are unwrapped on the Server506 | React$Element<component(...props: any)>507 | LazyComponent<ReactClientValue, any>508 // References are passed by their value509 | ClientReference<any>510 | ServerReference<any>511 // The rest are passed as is. Sub-types can be passed in but lose their512 // subtype, so the receiver can only accept once of these.513 | React$Element<string>514 | React$Element<ClientReference<any> & any>515 | ReactComponentInfo516 | ReactErrorInfo517 | string518 | boolean519 | number520 | symbol521 | null522 | void523 | bigint524 | ReadableStream525 | $AsyncIterable<ReactClientValue, ReactClientValue, void>526 | $AsyncIterator<ReactClientValue, ReactClientValue, void>527 | Iterable<ReactClientValue>528 | Iterator<ReactClientValue>529 | Array<ReactClientValue>530 | Map<ReactClientValue, ReactClientValue>531 | Set<ReactClientValue>532 | FormData533 | $ArrayBufferView534 | ArrayBuffer535 | Date536 | ReactClientObject537 | Promise<ReactClientValue>; // Thenable<ReactClientValue>538539type ReactClientObject = {+[key: string]: ReactClientValue};540541// task status542const PENDING = 0;543const COMPLETED = 1;544const ABORTED = 3;545const ERRORED = 4;546const RENDERING = 5;547548type Task = {549 id: number,550 status: 0 | 1 | 3 | 4 | 5,551 model: ReactClientValue,552 ping: () => void,553 keyPath: ReactKey, // parent server component keys554 implicitSlot: boolean, // true if the root server component of this sequence had a null key555 formatContext: FormatContext, // an approximate parent context from host components556 thenableState: ThenableState | null,557 timed: boolean, // Profiling-only. Whether we need to track the completion time of this task.558 time: number, // Profiling-only. The last time stamp emitted for this task.559 environmentName: string, // DEV-only. Used to track if the environment for this task changed.560 debugOwner: null | ReactComponentInfo, // DEV-only561 debugStack: null | Error, // DEV-only562 debugTask: null | ConsoleTask, // DEV-only563};564565interface Reference {}566567type ReactClientReference = Reference & ReactClientValue;568569type DeferredDebugStore = {570 retained: Map<number, ReactClientReference | string>,571 existing: Map<ReactClientReference | string, number>,572};573574const __PROTO__ = '__proto__';575576const OPENING = 10;577const OPEN = 11;578const ABORTING = 12;579const CLOSING = 13;580const CLOSED = 14;581582const RENDER = 20;583const PRERENDER = 21;584585// Marker pushed before a [headerChunk, contentChunk] pair in586// completedRegularChunks / completedDebugChunks to signal that the next two587// entries must be written atomically — see emitTextChunk and588// emitTypedArrayChunk for why, and flushCompletedChunks for how it's read.589const NEXT_TWO_CHUNKS_ARE_ATOMIC: symbol = Symbol();590591export type Request = {592 status: 10 | 11 | 12 | 13 | 14,593 type: 20 | 21,594 flushScheduled: boolean,595 fatalError: mixed,596 destination: null | Destination,597 bundlerConfig: ClientManifest,598 cache: Map<Function, mixed>,599 cacheController: AbortController,600 nextChunkId: number,601 pendingChunks: number,602 hints: Hints,603 abortableTasks: Set<Task>,604 pingedTasks: Array<Task>,605 completedImportChunks: Array<Chunk>,606 completedHintChunks: Array<Chunk>,607 // Text and TypedArray rows are pushed as a NEXT_TWO_CHUNKS_ARE_ATOMIC608 // sentinel followed by their [headerChunk, contentChunk] pair, so that609 // flushCompletedChunks can write the pair atomically and never strand the610 // content chunk on a backpressure break.611 completedRegularChunks: Array<612 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,613 >,614 completedErrorChunks: Array<Chunk>,615 writtenSymbols: Map<symbol, number>,616 writtenClientReferences: Map<ClientReferenceKey, number>,617 writtenServerReferences: Map<ServerReference<any>, number>,618 writtenObjects: WeakMap<Reference, string>,619 temporaryReferences: void | TemporaryReferenceSet,620 identifierPrefix: string,621 identifierCount: number,622 taintCleanupQueue: Array<string | bigint>,623 onError: (error: mixed) => ?string,624 onAllReady: () => void,625 onFatalError: mixed => void,626 // Profiling-only627 timeOrigin: number,628 abortTime: number,629 // DEV-only630 pendingDebugChunks: number,631 // See completedRegularChunks for why some entries are preceded by the632 // NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel.633 completedDebugChunks: Array<634 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,635 >,636 debugDestination: null | Destination,637 environmentName: () => string,638 filterStackFrame: (639 url: string,640 functionName: string,641 lineNumber: number,642 columnNumber: number,643 ) => boolean,644 didWarnForKey: null | WeakSet<ReactComponentInfo>,645 writtenDebugObjects: WeakMap<Reference, string>,646 deferredDebugObjects: null | DeferredDebugStore,647};648649const {650 TaintRegistryObjects,651 TaintRegistryValues,652 TaintRegistryByteLengths,653 TaintRegistryPendingRequests,654} = ReactSharedInternals;655656function throwTaintViolation(message: string) {657 // eslint-disable-next-line react-internal/prod-error-codes658 throw new Error(message);659}660661function cleanupTaintQueue(request: Request): void {662 const cleanupQueue = request.taintCleanupQueue;663 TaintRegistryPendingRequests.delete(cleanupQueue);664 for (let i = 0; i < cleanupQueue.length; i++) {665 const entryValue = cleanupQueue[i];666 const entry = TaintRegistryValues.get(entryValue);667 if (entry !== undefined) {668 if (entry.count === 1) {669 TaintRegistryValues.delete(entryValue);670 } else {671 entry.count--;672 }673 }674 }675 cleanupQueue.length = 0;676}677678function defaultErrorHandler(error: mixed) {679 console['error'](error);680 // Don't transform to our wrapper681}682683function RequestInstance(684 this: $FlowFixMe,685 type: 20 | 21,686 model: ReactClientValue,687 bundlerConfig: ClientManifest,688 onError: void | ((error: mixed) => ?string),689 onAllReady: () => void,690 onFatalError: (error: mixed) => void,691 identifierPrefix?: string,692 temporaryReferences: void | TemporaryReferenceSet,693 debugStartTime: void | number, // Profiling-only694 environmentName: void | string | (() => string), // DEV-only695 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only696 keepDebugAlive: boolean, // DEV-only697) {698 if (699 ReactSharedInternals.A !== null &&700 ReactSharedInternals.A !== DefaultAsyncDispatcher701 ) {702 throw new Error(703 'Currently React only supports one RSC renderer at a time.',704 );705 }706 ReactSharedInternals.A = DefaultAsyncDispatcher;707 if (__DEV__) {708 // Unlike Fizz or Fiber, we don't reset this and just keep it on permanently.709 // This lets it act more like the AsyncDispatcher so that we can get the710 // stack asynchronously too.711 ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;712 }713714 const abortSet: Set<Task> = new Set();715 const pingedTasks: Array<Task> = [];716 const cleanupQueue: Array<string | bigint> = [];717 if (enableTaint) {718 TaintRegistryPendingRequests.add(cleanupQueue);719 }720 const hints = createHints();721 this.type = type;722 this.status = OPENING;723 this.flushScheduled = false;724 this.fatalError = null;725 this.destination = null;726 this.bundlerConfig = bundlerConfig;727 this.cache = new Map();728 this.cacheController = new AbortController();729 this.nextChunkId = 0;730 this.pendingChunks = 0;731 this.hints = hints;732 this.abortableTasks = abortSet;733 this.pingedTasks = pingedTasks;734 this.completedImportChunks = [] as Array<Chunk>;735 this.completedHintChunks = [] as Array<Chunk>;736 this.completedRegularChunks = [] as Array<737 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,738 >;739 this.completedErrorChunks = [] as Array<Chunk>;740 this.writtenSymbols = new Map();741 this.writtenClientReferences = new Map();742 this.writtenServerReferences = new Map();743 this.writtenObjects = new WeakMap();744 this.temporaryReferences = temporaryReferences;745 this.identifierPrefix = identifierPrefix || '';746 this.identifierCount = 1;747 this.taintCleanupQueue = cleanupQueue;748 this.onError = onError === undefined ? defaultErrorHandler : onError;749 this.onAllReady = onAllReady;750 this.onFatalError = onFatalError;751752 if (__DEV__) {753 this.pendingDebugChunks = 0;754 this.completedDebugChunks = [] as Array<755 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,756 >;757 this.debugDestination = null;758 this.environmentName =759 environmentName === undefined760 ? () => 'Server'761 : typeof environmentName !== 'function'762 ? () => environmentName763 : environmentName;764 this.filterStackFrame =765 filterStackFrame === undefined766 ? defaultFilterStackFrame767 : filterStackFrame;768 this.didWarnForKey = null;769 this.writtenDebugObjects = new WeakMap();770 this.deferredDebugObjects = keepDebugAlive771 ? {772 retained: new Map(),773 existing: new Map(),774 }775 : null;776 }777778 let timeOrigin: number;779 if (780 enableProfilerTimer &&781 (enableComponentPerformanceTrack || enableAsyncDebugInfo)782 ) {783 // We start by serializing the time origin. Any future timestamps will be784 // emitted relatively to this origin. Instead of using performance.timeOrigin785 // as this origin, we use the timestamp at the start of the request.786 // This avoids leaking unnecessary information like how long the server has787 // been running and allows for more compact representation of each timestamp.788 // The time origin is stored as an offset in the time space of this environment.789 if (typeof debugStartTime === 'number') {790 // We expect `startTime` to be an absolute timestamp, so relativize it to match the other case.791 timeOrigin = this.timeOrigin =792 debugStartTime -793 // $FlowFixMe[prop-missing]794 performance.timeOrigin;795 } else {796 timeOrigin = this.timeOrigin = performance.now();797 }798 emitTimeOriginChunk(799 this,800 timeOrigin +801 // $FlowFixMe[prop-missing]802 performance.timeOrigin,803 );804 this.abortTime = -0.0;805 } else {806 timeOrigin = 0;807 }808809 const rootTask = createTask(810 this,811 model,812 null,813 false,814 createRootFormatContext(),815 abortSet,816 timeOrigin,817 null,818 null,819 null,820 );821 pingedTasks.push(rootTask);822}823824export function createRequest(825 model: ReactClientValue,826 bundlerConfig: ClientManifest,827 onError: void | ((error: mixed) => ?string),828 identifierPrefix: void | string,829 temporaryReferences: void | TemporaryReferenceSet,830 debugStartTime: void | number, // Profiling-only831 environmentName: void | string | (() => string), // DEV-only832 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only833 keepDebugAlive: boolean, // DEV-only834): Request {835 if (__DEV__) {836 resetOwnerStackLimit();837 }838839 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors840 return new RequestInstance(841 RENDER,842 model,843 bundlerConfig,844 onError,845 noop,846 noop,847 identifierPrefix,848 temporaryReferences,849 debugStartTime,850 environmentName,851 filterStackFrame,852 keepDebugAlive,853 );854}855856export function createPrerenderRequest(857 model: ReactClientValue,858 bundlerConfig: ClientManifest,859 onAllReady: () => void,860 onFatalError: () => void,861 onError: void | ((error: mixed) => ?string),862 identifierPrefix: void | string,863 temporaryReferences: void | TemporaryReferenceSet,864 debugStartTime: void | number, // Profiling-only865 environmentName: void | string | (() => string), // DEV-only866 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only867 keepDebugAlive: boolean, // DEV-only868): Request {869 if (__DEV__) {870 resetOwnerStackLimit();871 }872873 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors874 return new RequestInstance(875 PRERENDER,876 model,877 bundlerConfig,878 onError,879 onAllReady,880 onFatalError,881 identifierPrefix,882 temporaryReferences,883 debugStartTime,884 environmentName,885 filterStackFrame,886 keepDebugAlive,887 );888}889890let currentRequest: null | Request = null;891892export function resolveRequest(): null | Request {893 if (currentRequest) return currentRequest;894 // $FlowFixMe[constant-condition]895 if (supportsRequestStorage) {896 const store = requestStorage.getStore();897 if (store) return store;898 }899 return null;900}901902function isTypedArray(value: any): boolean {903 if (value instanceof ArrayBuffer) {904 return true;905 }906 if (value instanceof Int8Array) {907 return true;908 }909 if (value instanceof Uint8Array) {910 return true;911 }912 if (value instanceof Uint8ClampedArray) {913 return true;914 }915 if (value instanceof Int16Array) {916 return true;917 }918 if (value instanceof Uint16Array) {919 return true;920 }921 if (value instanceof Int32Array) {922 return true;923 }924 if (value instanceof Uint32Array) {925 return true;926 }927 if (value instanceof Float32Array) {928 return true;929 }930 if (value instanceof Float64Array) {931 return true;932 }933 if (value instanceof BigInt64Array) {934 return true;935 }936 if (value instanceof BigUint64Array) {937 return true;938 }939 if (value instanceof DataView) {940 return true;941 }942 return false;943}944945function serializeDebugThenable(946 request: Request,947 counter: {objectLimit: number},948 thenable: Thenable<any>,949): string {950 // Like serializeThenable but for renderDebugModel951 request.pendingDebugChunks++;952 const id = request.nextChunkId++;953 const ref = serializePromiseID(id);954 request.writtenDebugObjects.set(thenable, ref);955956 switch (thenable.status) {957 case 'fulfilled': {958 emitOutlinedDebugModelChunk(request, id, counter, thenable.value);959 return ref;960 }961 case 'rejected': {962 const x = thenable.reason;963 // We don't log these errors since they didn't actually throw into Flight.964 const digest = '';965 emitErrorChunk(request, id, digest, x, true, null);966 return ref;967 }968 }969970 if (request.status === ABORTING) {971 // Ensure that we have time to emit the halt chunk if we're sync aborting.972 emitDebugHaltChunk(request, id);973 return ref;974 }975976 const deferredDebugObjects = request.deferredDebugObjects;977 if (deferredDebugObjects !== null) {978 // For Promises that are not yet resolved, we always defer them. They are async anyway so it's979 // safe to defer them. This also ensures that we don't eagerly call .then() on a Promise that980 // otherwise wouldn't have initialized. It also ensures that we don't "handle" a rejection981 // that otherwise would have triggered unhandled rejection.982 deferredDebugObjects.retained.set(id, thenable as any);983 const deferredRef = '$Y@' + id.toString(16);984 // We can now refer to the deferred object in the future.985 request.writtenDebugObjects.set(thenable, deferredRef);986 return deferredRef;987 }988989 let cancelled = false;990991 thenable.then(992 value => {993 if (cancelled) {994 return;995 }996 cancelled = true;997 if (request.status === ABORTING) {998 emitDebugHaltChunk(request, id);999 enqueueFlush(request);1000 return;1001 }1002 if (1003 (isArray(value) && value.length > 200) ||1004 (isTypedArray(value) && value.byteLength > 1000)1005 ) {1006 // If this should be deferred, but we don't have a debug channel installed1007 // it would get omitted. We can't omit outlined models but we can avoid1008 // resolving the Promise at all by halting it.1009 emitDebugHaltChunk(request, id);1010 enqueueFlush(request);1011 return;1012 }1013 emitOutlinedDebugModelChunk(request, id, counter, value);1014 enqueueFlush(request);1015 },1016 reason => {1017 if (cancelled) {1018 return;1019 }1020 cancelled = true;1021 if (request.status === ABORTING) {1022 emitDebugHaltChunk(request, id);1023 enqueueFlush(request);1024 return;1025 }1026 // We don't log these errors since they didn't actually throw into Flight.1027 const digest = '';1028 emitErrorChunk(request, id, digest, reason, true, null);1029 enqueueFlush(request);1030 },1031 );10321033 // We don't use scheduleMicrotask here because it doesn't actually schedule a microtask1034 // in all our configs which is annoying.1035 Promise.resolve().then(() => {1036 // If we don't resolve the Promise within a microtask. Leave it as hanging since we1037 // don't want to block the render forever on a Promise that might never resolve.1038 if (cancelled) {1039 return;1040 }1041 cancelled = true;1042 emitDebugHaltChunk(request, id);1043 enqueueFlush(request);1044 // Clean up the request so we don't leak this forever.1045 request = null as any;1046 counter = null as any;1047 });10481049 return ref;1050}10511052function emitRequestedDebugThenable(1053 request: Request,1054 id: number,1055 counter: {objectLimit: number},1056 thenable: Thenable<any>,1057): void {1058 thenable.then(1059 value => {1060 if (request.status === ABORTING) {1061 emitDebugHaltChunk(request, id);1062 enqueueFlush(request);1063 return;1064 }1065 emitOutlinedDebugModelChunk(request, id, counter, value);1066 enqueueFlush(request);1067 },1068 reason => {1069 if (request.status === ABORTING) {1070 emitDebugHaltChunk(request, id);1071 enqueueFlush(request);1072 return;1073 }1074 // We don't log these errors since they didn't actually throw into Flight.1075 const digest = '';1076 emitErrorChunk(request, id, digest, reason, true, null);1077 enqueueFlush(request);1078 },1079 );1080}10811082function createThenableTask(1083 request: Request,1084 task: Task,1085 thenable: Thenable<any>,1086): Task {1087 return createTask(1088 request,1089 thenable as any, // will be replaced by the value before we retry. used for debug info.1090 task.keyPath, // the server component sequence continues through Promise-as-a-child.1091 task.implicitSlot,1092 task.formatContext,1093 request.abortableTasks,1094 enableProfilerTimer &&1095 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1096 ? task.time1097 : 0,1098 __DEV__ ? task.debugOwner : null,1099 __DEV__ ? task.debugStack : null,1100 __DEV__ ? task.debugTask : null,1101 );1102}11031104function serializeThenable(1105 request: Request,1106 task: Task,1107 thenable: Thenable<any>,1108): number {1109 switch (thenable.status) {1110 case 'fulfilled': {1111 const newTask = createThenableTask(request, task, thenable);1112 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);1113 // We have the resolved value, we can go ahead and schedule it for serialization.1114 newTask.model = thenable.value;1115 pingTask(request, newTask);1116 return newTask.id;1117 }1118 case 'rejected': {1119 const newTask = createThenableTask(request, task, thenable);1120 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);1121 const x = thenable.reason;1122 erroredTask(request, newTask, x);1123 return newTask.id;1124 }1125 case 'pending_weak': {1126 if (enableFlightWeakThenables) {1127 // A weak-pending thenable doesn't block the stream from closing, so1128 // we don't create a task for it yet. We only reserve an id for its1129 // reference. If it settles while the stream is still open, we1130 // create the task at that point, the same as if we had serialized1131 // an already settled thenable.1132 //1133 // Delivery is driven by the thenable's notification. If the stream1134 // closes before the listeners are notified, the value is dropped1135 // and the reference is left unfulfilled. Since the stream may close1136 // synchronously when the last task completes, a thenable that1137 // notifies its listeners synchronously (unlike a native Promise,1138 // which notifies in a microtask) is guaranteed delivery of any1139 // value it settles with before the stream closes.1140 const id = request.nextChunkId++;1141 // The parent task is mutated as serialization continues, so we1142 // snapshot the context that the new task needs if it's created1143 // later.1144 const keyPath = task.keyPath;1145 const implicitSlot = task.implicitSlot;1146 const formatContext = task.formatContext;1147 const lastTimestamp =1148 enableProfilerTimer &&1149 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1150 ? task.time1151 : 0;1152 const debugOwner = __DEV__ ? task.debugOwner : null;1153 const debugStack = __DEV__ ? task.debugStack : null;1154 const debugTask = __DEV__ ? task.debugTask : null;1155 let settled = false;1156 thenable.then(1157 (value: any) => {1158 if (settled || request.status > OPEN) {1159 // Too late. The stream already closed (or the request was1160 // aborted), so the reference stays unfulfilled.1161 return;1162 }1163 settled = true;1164 const newTask = createTaskWithID(1165 request,1166 id,1167 value,1168 keyPath,1169 implicitSlot,1170 formatContext,1171 request.abortableTasks,1172 lastTimestamp,1173 debugOwner,1174 debugStack,1175 debugTask,1176 );1177 forwardDebugInfoFromCurrentContext(request, newTask, thenable);1178 pingTask(request, newTask);1179 },1180 (reason: mixed) => {1181 if (settled || request.status > OPEN) {1182 return;1183 }1184 settled = true;1185 const newTask = createTaskWithID(1186 request,1187 id,1188 thenable as any, // never rendered. used for debug info.1189 keyPath,1190 implicitSlot,1191 formatContext,1192 request.abortableTasks,1193 lastTimestamp,1194 debugOwner,1195 debugStack,1196 debugTask,1197 );1198 if (1199 enableProfilerTimer &&1200 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1201 ) {1202 // If this is async we need to time when this task finishes.1203 newTask.timed = true;1204 }1205 erroredTask(request, newTask, reason);1206 enqueueFlush(request);1207 },1208 );1209 return id;1210 }1211 // Fallthrough1212 }1213 default: {1214 const newTask = createThenableTask(request, task, thenable);1215 if (request.status === ABORTING) {1216 // We can no longer accept any resolved values1217 request.abortableTasks.delete(newTask);1218 if (request.type === PRERENDER) {1219 haltTask(newTask, request);1220 finishHaltedTask(newTask, request);1221 } else {1222 const errorId: number = request.fatalError as any;1223 abortTask(newTask, request, errorId);1224 finishAbortedTask(newTask, request, errorId);1225 }1226 return newTask.id;1227 }1228 if (typeof thenable.status !== 'string') {1229 // Only instrument the thenable if the status if not defined. If1230 // it's defined, but an unknown value, assume it's been instrumented by1231 // some custom userspace implementation. We treat it as "pending".1232 const pendingThenable: PendingThenable<mixed> = thenable as any;1233 pendingThenable.status = 'pending';1234 pendingThenable.then(1235 fulfilledValue => {1236 if (thenable.status === 'pending') {1237 const fulfilledThenable: FulfilledThenable<mixed> =1238 thenable as any;1239 fulfilledThenable.status = 'fulfilled';1240 fulfilledThenable.value = fulfilledValue;1241 }1242 },1243 (error: mixed) => {1244 if (thenable.status === 'pending') {1245 const rejectedThenable: RejectedThenable<mixed> = thenable as any;1246 rejectedThenable.status = 'rejected';1247 rejectedThenable.reason = error;1248 }1249 },1250 );1251 }1252 thenable.then(1253 value => {1254 forwardDebugInfoFromCurrentContext(request, newTask, thenable);1255 newTask.model = value;1256 pingTask(request, newTask);1257 },1258 reason => {1259 if (newTask.status === PENDING) {1260 if (1261 enableProfilerTimer &&1262 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1263 ) {1264 // If this is async we need to time when this task finishes.1265 newTask.timed = true;1266 }1267 // We expect that the only status it might be otherwise is ABORTED.1268 // When we abort we emit chunks in each pending task slot and don't need1269 // to do so again here.1270 erroredTask(request, newTask, reason);1271 enqueueFlush(request);1272 }1273 },1274 );1275 return newTask.id;1276 }1277 }1278}12791280function serializeReadableStream(1281 request: Request,1282 task: Task,1283 stream: ReadableStream,1284): string {1285 // Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the1286 // receiving side. It also implies that different chunks can be split up or merged as opposed1287 // to a readable stream that happens to have Uint8Array as the type which might expect it to be1288 // received in the same slices.1289 // $FlowFixMe[prop-missing]: This is a Node.js extension.1290 let supportsBYOB: void | boolean = stream.supportsBYOB;1291 if (supportsBYOB === undefined) {1292 try {1293 // $FlowFixMe[extra-arg]: This argument is accepted.1294 stream.getReader({mode: 'byob'}).releaseLock();1295 supportsBYOB = true;1296 } catch (x) {1297 supportsBYOB = false;1298 }1299 }1300 // At this point supportsBYOB is guaranteed to be a boolean.1301 const isByteStream: boolean = supportsBYOB;13021303 const reader = stream.getReader();13041305 // This task won't actually be retried. We just use it to attempt synchronous renders.1306 const streamTask = createTask(1307 request,1308 task.model,1309 task.keyPath,1310 task.implicitSlot,1311 task.formatContext,1312 request.abortableTasks,1313 enableProfilerTimer &&1314 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1315 ? task.time1316 : 0,1317 __DEV__ ? task.debugOwner : null,1318 __DEV__ ? task.debugStack : null,1319 __DEV__ ? task.debugTask : null,1320 );13211322 // The task represents the Stop row. This adds a Start row.1323 request.pendingChunks++;1324 const startStreamRow =1325 streamTask.id.toString(16) + ':' + (isByteStream ? 'r' : 'R') + '\n';1326 request.completedRegularChunks.push(stringToChunk(startStreamRow));13271328 function progress(entry: {done: boolean, value: ReactClientValue, ...}) {1329 if (streamTask.status !== PENDING) {1330 return;1331 }13321333 if (entry.done) {1334 streamTask.status = COMPLETED;1335 const endStreamRow = streamTask.id.toString(16) + ':C\n';1336 request.completedRegularChunks.push(stringToChunk(endStreamRow));1337 request.abortableTasks.delete(streamTask);1338 request.cacheController.signal.removeEventListener('abort', abortStream);1339 enqueueFlush(request);1340 callOnAllReadyIfReady(request);1341 } else {1342 try {1343 request.pendingChunks++;1344 streamTask.model = entry.value;1345 if (isByteStream) {1346 // Chunks of byte streams are always Uint8Array instances.1347 const chunk: Uint8Array = streamTask.model as any;1348 emitTypedArrayChunk(request, streamTask.id, 'b', chunk, false);1349 } else {1350 tryStreamTask(request, streamTask);1351 }1352 enqueueFlush(request);1353 reader.read().then(progress, error);1354 } catch (x) {1355 error(x);1356 }1357 }1358 }1359 function error(reason: mixed) {1360 if (streamTask.status !== PENDING) {1361 return;1362 }1363 request.cacheController.signal.removeEventListener('abort', abortStream);1364 erroredTask(request, streamTask, reason);1365 enqueueFlush(request);13661367 // $FlowFixMe[incompatible-type] should be able to pass mixed1368 // $FlowFixMe[incompatible-use]1369 reader.cancel(reason).then(error, error);1370 }1371 function abortStream() {1372 if (streamTask.status !== PENDING) {1373 return;1374 }1375 const signal = request.cacheController.signal;1376 signal.removeEventListener('abort', abortStream);1377 const reason = signal.reason;1378 if (request.type === PRERENDER) {1379 request.abortableTasks.delete(streamTask);1380 haltTask(streamTask, request);1381 finishHaltedTask(streamTask, request);1382 } else {1383 // TODO: Make this use abortTask() instead.1384 erroredTask(request, streamTask, reason);1385 enqueueFlush(request);1386 }1387 // $FlowFixMe[incompatible-use] should be able to pass mixed1388 reader.cancel(reason).then(error, error);1389 }13901391 request.cacheController.signal.addEventListener('abort', abortStream);1392 reader.read().then(progress, error);1393 return serializeByValueID(streamTask.id);1394}13951396function serializeAsyncIterable(1397 request: Request,1398 task: Task,1399 iterable: $AsyncIterable<ReactClientValue, ReactClientValue, void>,1400 iterator: $AsyncIterator<ReactClientValue, ReactClientValue, void>,1401): string {1402 // Generators/Iterators are Iterables but they're also their own iterator1403 // functions. If that's the case, we treat them as single-shot. Otherwise,1404 // we assume that this iterable might be a multi-shot and allow it to be1405 // iterated more than once on the client.1406 const isIterator = iterable === iterator;14071408 // This task won't actually be retried. We just use it to attempt synchronous renders.1409 const streamTask = createTask(1410 request,1411 task.model,1412 task.keyPath,1413 task.implicitSlot,1414 task.formatContext,1415 request.abortableTasks,1416 enableProfilerTimer &&1417 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1418 ? task.time1419 : 0,1420 __DEV__ ? task.debugOwner : null,1421 __DEV__ ? task.debugStack : null,1422 __DEV__ ? task.debugTask : null,1423 );14241425 if (__DEV__) {1426 const debugInfo: ?ReactDebugInfo = (iterable as any)._debugInfo;1427 if (debugInfo) {1428 forwardDebugInfo(request, streamTask, debugInfo);1429 }1430 }14311432 // The task represents the Stop row. This adds a Start row.1433 request.pendingChunks++;1434 const startStreamRow =1435 streamTask.id.toString(16) + ':' + (isIterator ? 'x' : 'X') + '\n';1436 request.completedRegularChunks.push(stringToChunk(startStreamRow));14371438 function progress(1439 entry:1440 | {done: false, +value: ReactClientValue, ...}1441 | {done: true, +value: ReactClientValue, ...},1442 ) {1443 if (streamTask.status !== PENDING) {1444 return;1445 }14461447 if (entry.done) {1448 streamTask.status = COMPLETED;1449 let endStreamRow;1450 if (entry.value === undefined) {1451 endStreamRow = streamTask.id.toString(16) + ':C\n';1452 } else {1453 // Unlike streams, the last value may not be undefined. If it's not1454 // we outline it and encode a reference to it in the closing instruction.1455 try {1456 const chunkId = outlineModel(request, entry.value);1457 endStreamRow =1458 streamTask.id.toString(16) +1459 ':C' +1460 stringify(serializeByValueID(chunkId)) +1461 '\n';1462 } catch (x) {1463 error(x);1464 return;1465 }1466 }1467 request.completedRegularChunks.push(stringToChunk(endStreamRow));1468 request.abortableTasks.delete(streamTask);1469 request.cacheController.signal.removeEventListener(1470 'abort',1471 abortIterable,1472 );1473 enqueueFlush(request);1474 callOnAllReadyIfReady(request);1475 } else {1476 try {1477 streamTask.model = entry.value;1478 request.pendingChunks++;1479 tryStreamTask(request, streamTask);1480 enqueueFlush(request);1481 if (__DEV__) {1482 callIteratorInDEV(iterator, progress, error);1483 } else {1484 iterator.next().then(progress, error);1485 }1486 } catch (x) {1487 error(x);1488 return;1489 }1490 }1491 }1492 function error(reason: mixed) {1493 if (streamTask.status !== PENDING) {1494 return;1495 }1496 request.cacheController.signal.removeEventListener('abort', abortIterable);1497 erroredTask(request, streamTask, reason);1498 enqueueFlush(request);1499 if (typeof (iterator as any).throw === 'function') {1500 // The iterator protocol doesn't necessarily include this but a generator do.1501 // $FlowFixMe[prop-missing] should be able to pass mixed1502 iterator.throw(reason).then(noop, noop);1503 }1504 }1505 function abortIterable() {1506 if (streamTask.status !== PENDING) {1507 return;1508 }1509 const signal = request.cacheController.signal;1510 signal.removeEventListener('abort', abortIterable);1511 const reason = signal.reason;1512 if (request.type === PRERENDER) {1513 request.abortableTasks.delete(streamTask);1514 haltTask(streamTask, request);1515 finishHaltedTask(streamTask, request);1516 } else {1517 // TODO: Make this use abortTask() instead.1518 erroredTask(request, streamTask, signal.reason);1519 enqueueFlush(request);1520 }1521 if (typeof (iterator as any).throw === 'function') {1522 // TODO: Premature exits should call return() on the iterator if it exists1523 // to allow cleanup. See https://tc39.es/ecma262/multipage/control-abstraction-objects.html#table-async-iterator-optional1524 // The iterator protocol doesn't necessarily include this but a generator do.1525 // $FlowFixMe[prop-missing] should be able to pass mixed1526 iterator.throw(reason).then(noop, noop);1527 }1528 }1529 request.cacheController.signal.addEventListener('abort', abortIterable);1530 if (__DEV__) {1531 callIteratorInDEV(iterator, progress, error);1532 } else {1533 iterator.next().then(progress, error);1534 }1535 return serializeByValueID(streamTask.id);1536}15371538export function emitHint<Code: HintCode>(1539 request: Request,1540 code: Code,1541 model: HintModel<Code>,1542): void {1543 emitHintChunk(request, code, model);1544 enqueueFlush(request);1545}15461547export function getHints(request: Request): Hints {1548 return request.hints;1549}15501551export function getCache(request: Request): Map<Function, mixed> {1552 return request.cache;1553}15541555function readThenable<T>(thenable: Thenable<T>): T {1556 if (thenable.status === 'fulfilled') {1557 return thenable.value;1558 } else if (thenable.status === 'rejected') {1559 throw thenable.reason;1560 }1561 throw thenable;1562}15631564function createLazyWrapperAroundWakeable(1565 request: Request,1566 task: Task,1567 wakeable: Wakeable,1568) {1569 // This is a temporary fork of the `use` implementation until we accept1570 // promises everywhere.1571 const thenable: Thenable<mixed> = wakeable as any;1572 switch (thenable.status) {1573 case 'fulfilled': {1574 forwardDebugInfoFromThenable(request, task, thenable, null, null);1575 return thenable.value;1576 }1577 case 'rejected':1578 forwardDebugInfoFromThenable(request, task, thenable, null, null);1579 break;1580 default: {1581 if (typeof thenable.status === 'string') {1582 // Only instrument the thenable if the status if not defined. If1583 // it's defined, but an unknown value, assume it's been instrumented by1584 // some custom userspace implementation. We treat it as "pending".1585 break;1586 }1587 const pendingThenable: PendingThenable<mixed> = thenable as any;1588 pendingThenable.status = 'pending';1589 pendingThenable.then(1590 fulfilledValue => {1591 forwardDebugInfoFromCurrentContext(request, task, thenable);1592 if (thenable.status === 'pending') {1593 const fulfilledThenable: FulfilledThenable<mixed> = thenable as any;1594 fulfilledThenable.status = 'fulfilled';1595 fulfilledThenable.value = fulfilledValue;1596 }1597 },1598 (error: mixed) => {1599 forwardDebugInfoFromCurrentContext(request, task, thenable);1600 if (thenable.status === 'pending') {1601 const rejectedThenable: RejectedThenable<mixed> = thenable as any;1602 rejectedThenable.status = 'rejected';1603 rejectedThenable.reason = error;1604 }1605 },1606 );1607 break;1608 }1609 }1610 const lazyType: LazyComponent<any, Thenable<any>> = {1611 $$typeof: REACT_LAZY_TYPE,1612 _payload: thenable,1613 _init: readThenable,1614 };1615 return lazyType;1616}16171618function callWithDebugContextInDEV<A, T>(1619 request: Request,1620 task: Task,1621 callback: A => T,1622 arg: A,1623): T {1624 // We don't have a Server Component instance associated with this callback and1625 // the nearest context is likely a Client Component being serialized. We create1626 // a fake owner during this callback so we can get the stack trace from it.1627 // This also gets sent to the client as the owner for the replaying log.1628 const componentDebugInfo: ReactComponentInfo = {1629 name: '',1630 env: task.environmentName,1631 key: null,1632 owner: task.debugOwner,1633 };1634 // $FlowFixMe[cannot-write]1635 componentDebugInfo.stack =1636 task.debugStack === null1637 ? null1638 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));1639 // $FlowFixMe[cannot-write]1640 componentDebugInfo.debugStack = task.debugStack;1641 // $FlowFixMe[cannot-write]1642 componentDebugInfo.debugTask = task.debugTask;1643 const debugTask = task.debugTask;1644 // We don't need the async component storage context here so we only set the1645 // synchronous tracking of owner.1646 setCurrentOwner(componentDebugInfo);1647 try {1648 if (debugTask) {1649 return debugTask.run(callback.bind(null, arg));1650 }1651 return callback(arg);1652 } finally {1653 setCurrentOwner(null);1654 }1655}16561657const voidHandler = () => {};16581659function processServerComponentReturnValue(1660 request: Request,1661 task: Task,1662 Component: any,1663 result: any,1664): any {1665 // A Server Component's return value has a few special properties due to being1666 // in the return position of a Component. We convert them here.1667 if (1668 typeof result !== 'object' ||1669 result === null ||1670 isClientReference(result)1671 ) {1672 return result;1673 }16741675 if (typeof result.then === 'function') {1676 // When the return value is in children position we can resolve it immediately,1677 // to its value without a wrapper if it's synchronously available.1678 const thenable: Thenable<any> = result;1679 if (__DEV__) {1680 // If the thenable resolves to an element, then it was in a static position,1681 // the return value of a Server Component. That doesn't need further validation1682 // of keys. The Server Component itself would have had a key.1683 thenable.then(resolvedValue => {1684 if (1685 typeof resolvedValue === 'object' &&1686 resolvedValue !== null &&1687 resolvedValue.$$typeof === REACT_ELEMENT_TYPE1688 ) {1689 resolvedValue._store.validated = 1;1690 }1691 }, voidHandler);1692 }1693 // TODO: Once we accept Promises as children on the client, we can just return1694 // the thenable here.1695 return createLazyWrapperAroundWakeable(request, task, result);1696 }16971698 if (__DEV__) {1699 if ((result as any).$$typeof === REACT_ELEMENT_TYPE) {1700 // If the server component renders to an element, then it was in a static position.1701 // That doesn't need further validation of keys. The Server Component itself would1702 // have had a key.1703 (result as any)._store.validated = 1;1704 }1705 }17061707 // Normally we'd serialize an Iterator/AsyncIterator as a single-shot which is not compatible1708 // to be rendered as a React Child. However, because we have the function to recreate1709 // an iterable from rendering the element again, we can effectively treat it as multi-1710 // shot. Therefore we treat this as an Iterable/AsyncIterable, whether it was one or not, by1711 // adding a wrapper so that this component effectively renders down to an AsyncIterable.1712 const iteratorFn = getIteratorFn(result);1713 if (iteratorFn) {1714 const iterableChild = result;1715 const multiShot = {1716 [Symbol.iterator]: function () {1717 const iterator = iteratorFn.call(iterableChild);1718 if (__DEV__) {1719 // If this was an Iterator but not a GeneratorFunction we warn because1720 // it might have been a mistake. Technically you can make this mistake with1721 // GeneratorFunctions and even single-shot Iterables too but it's extra1722 // tempting to try to return the value from a generator.1723 if (iterator === iterableChild) {1724 const isGeneratorComponent =1725 // $FlowFixMe[method-unbinding]1726 Object.prototype.toString.call(Component) ===1727 '[object GeneratorFunction]' &&1728 // $FlowFixMe[method-unbinding]1729 Object.prototype.toString.call(iterableChild) ===1730 '[object Generator]';1731 if (!isGeneratorComponent) {1732 callWithDebugContextInDEV(request, task, () => {1733 console.error(1734 'Returning an Iterator from a Server Component is not supported ' +1735 'since it cannot be looped over more than once. ',1736 );1737 });1738 }1739 }1740 }1741 return iterator as any;1742 },1743 };1744 if (__DEV__) {1745 (multiShot as any)._debugInfo = iterableChild._debugInfo;1746 }1747 return multiShot;1748 }1749 if (1750 typeof (result as any)[ASYNC_ITERATOR] === 'function' &&1751 (typeof ReadableStream !== 'function' ||1752 !(result instanceof ReadableStream))1753 ) {1754 const iterableChild = result;1755 const multishot = {1756 [ASYNC_ITERATOR]: function () {1757 const iterator = (iterableChild as any)[ASYNC_ITERATOR]();1758 if (__DEV__) {1759 // If this was an AsyncIterator but not an AsyncGeneratorFunction we warn because1760 // it might have been a mistake. Technically you can make this mistake with1761 // AsyncGeneratorFunctions and even single-shot AsyncIterables too but it's extra1762 // tempting to try to return the value from a generator.1763 if (iterator === iterableChild) {1764 const isGeneratorComponent =1765 // $FlowFixMe[method-unbinding]1766 Object.prototype.toString.call(Component) ===1767 '[object AsyncGeneratorFunction]' &&1768 // $FlowFixMe[method-unbinding]1769 Object.prototype.toString.call(iterableChild) ===1770 '[object AsyncGenerator]';1771 if (!isGeneratorComponent) {1772 callWithDebugContextInDEV(request, task, () => {1773 console.error(1774 'Returning an AsyncIterator from a Server Component is not supported ' +1775 'since it cannot be looped over more than once. ',1776 );1777 });1778 }1779 }1780 }1781 return iterator;1782 },1783 };1784 if (__DEV__) {1785 (multishot as any)._debugInfo = iterableChild._debugInfo;1786 }1787 return multishot;1788 }1789 return result;1790}17911792function renderFunctionComponent<Props>(1793 request: Request,1794 task: Task,1795 key: ReactKey,1796 Component: (p: Props, arg: void) => any,1797 props: Props,1798 validated: number, // DEV-only1799): ReactJSONValue {1800 // Reset the task's thenable state before continuing, so that if a later1801 // component suspends we can reuse the same task object. If the same1802 // component suspends again, the thenable state will be restored.1803 const prevThenableState = task.thenableState;1804 task.thenableState = null;18051806 let result;18071808 let componentDebugInfo: ReactComponentInfo;1809 if (__DEV__) {1810 if (!canEmitDebugInfo) {1811 // We don't have a chunk to assign debug info. We need to outline this1812 // component to assign it an ID.1813 return outlineTask(request, task);1814 } else if (prevThenableState !== null) {1815 // This is a replay and we've already emitted the debug info of this component1816 // in the first pass. We skip emitting a duplicate line.1817 // As a hack we stashed the previous component debug info on this object in DEV.1818 componentDebugInfo = (prevThenableState as any)._componentDebugInfo;1819 } else {1820 // This is a new component in the same task so we can emit more debug info.1821 const componentDebugID = task.id;1822 const componentName =1823 (Component as any).displayName || Component.name || '';1824 const componentEnv = (0, request.environmentName)();1825 request.pendingChunks++;1826 componentDebugInfo = {1827 name: componentName,1828 env: componentEnv,1829 key: key,1830 owner: task.debugOwner,1831 } as ReactComponentInfo;1832 // $FlowFixMe[cannot-write]1833 componentDebugInfo.stack =1834 task.debugStack === null1835 ? null1836 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));1837 // $FlowFixMe[cannot-write]1838 componentDebugInfo.props = props;1839 // $FlowFixMe[cannot-write]1840 componentDebugInfo.debugStack = task.debugStack;1841 // $FlowFixMe[cannot-write]1842 componentDebugInfo.debugTask = task.debugTask;18431844 // We outline this model eagerly so that we can refer to by reference as an owner.1845 // If we had a smarter way to dedupe we might not have to do this if there ends up1846 // being no references to this as an owner.18471848 outlineComponentInfo(request, componentDebugInfo);18491850 // Track when we started rendering this component.1851 if (1852 enableProfilerTimer &&1853 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1854 ) {1855 advanceTaskTime(request, task, performance.now());1856 }18571858 emitDebugChunk(request, componentDebugID, componentDebugInfo);18591860 // We've emitted the latest environment for this task so we track that.1861 task.environmentName = componentEnv;18621863 if (validated === 2) {1864 warnForMissingKey(request, key, componentDebugInfo, task.debugTask);1865 }1866 }1867 prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);1868 // $FlowFixMe[constant-condition]1869 if (supportsComponentStorage) {1870 // Run the component in an Async Context that tracks the current owner.1871 if (task.debugTask) {1872 result = task.debugTask.run(1873 // $FlowFixMe[method-unbinding]1874 componentStorage.run.bind(1875 componentStorage,1876 componentDebugInfo,1877 callComponentInDEV,1878 Component,1879 props,1880 componentDebugInfo,1881 ),1882 );1883 } else {1884 result = componentStorage.run(1885 componentDebugInfo,1886 callComponentInDEV,1887 Component,1888 props,1889 componentDebugInfo,1890 );1891 }1892 } else {1893 if (task.debugTask) {1894 result = task.debugTask.run(1895 callComponentInDEV.bind(null, Component, props, componentDebugInfo),1896 );1897 } else {1898 result = callComponentInDEV(Component, props, componentDebugInfo);1899 }1900 }1901 } else {1902 componentDebugInfo = null as any;1903 prepareToUseHooksForComponent(prevThenableState, null);1904 // The secondArg is always undefined in Server Components since refs error early.1905 const secondArg = undefined;1906 result = Component(props, secondArg);1907 }19081909 if (request.status === ABORTING) {1910 if (1911 typeof result === 'object' &&1912 // $FlowFixMe[invalid-compare]1913 result !== null &&1914 typeof result.then === 'function' &&1915 !isClientReference(result)1916 ) {1917 result.then(voidHandler, voidHandler);1918 }1919 // If we aborted during rendering we should interrupt the render but1920 // we don't need to provide an error because the renderer will encode1921 // the abort error as the reason.1922 // eslint-disable-next-line no-throw-literal1923 throw null;1924 }19251926 if (__DEV__ || (enableProfilerTimer && enableAsyncDebugInfo)) {1927 // Forward any debug information for any Promises that we use():ed during the render.1928 // We do this at the end so that we don't keep doing this for each retry.1929 const trackedThenables = getTrackedThenablesAfterRendering();1930 if (trackedThenables !== null) {1931 const stacks: Array<Error> =1932 __DEV__ && enableAsyncDebugInfo1933 ? (trackedThenables as any)._stacks ||1934 ((trackedThenables as any)._stacks = [])1935 : (null as any);1936 for (let i = 0; i < trackedThenables.length; i++) {1937 const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null;1938 forwardDebugInfoFromThenable(1939 request,1940 task,1941 trackedThenables[i],1942 __DEV__ ? componentDebugInfo : null,1943 stack,1944 );1945 }1946 }1947 }19481949 // Apply special cases.1950 result = processServerComponentReturnValue(request, task, Component, result);19511952 if (__DEV__) {1953 // From this point on, the parent is the component we just rendered until we1954 // hit another JSX element.1955 task.debugOwner = componentDebugInfo;1956 // Unfortunately, we don't have a stack frame for this position. Conceptually1957 // it would be the location of the `return` inside component that just rendered.1958 task.debugStack = null;1959 task.debugTask = null;1960 }19611962 // Track this element's key on the Server Component on the keyPath context..1963 const prevKeyPath = task.keyPath;1964 const prevImplicitSlot = task.implicitSlot;1965 if (key !== null) {1966 // Append the key to the path. Technically a null key should really add the child1967 // index. We don't do that to hold the payload small and implementation simple.1968 if (key === REACT_OPTIMISTIC_KEY || prevKeyPath === REACT_OPTIMISTIC_KEY) {1969 // The optimistic key is viral. It turns the whole key into optimistic if any part is.1970 task.keyPath = REACT_OPTIMISTIC_KEY;1971 } else {1972 task.keyPath = prevKeyPath === null ? key : prevKeyPath + ',' + key;1973 }1974 } else if (prevKeyPath === null) {1975 // This sequence of Server Components has no keys. This means that it was rendered1976 // in a slot that needs to assign an implicit key. Even if children below have1977 // explicit keys, they should not be used for the outer most key since it might1978 // collide with other slots in that set.1979 task.implicitSlot = true;1980 }1981 const json = renderModelDestructive(request, task, emptyRoot, '', result);1982 task.keyPath = prevKeyPath;1983 task.implicitSlot = prevImplicitSlot;1984 return json;1985}19861987function warnForMissingKey(1988 request: Request,1989 key: ReactKey,1990 componentDebugInfo: ReactComponentInfo,1991 debugTask: null | ConsoleTask,1992): void {1993 if (__DEV__) {1994 let didWarnForKey = request.didWarnForKey;1995 if (didWarnForKey == null) {1996 didWarnForKey = request.didWarnForKey = new WeakSet();1997 }1998 const parentOwner = componentDebugInfo.owner;1999 if (parentOwner != null) {2000 if (didWarnForKey.has(parentOwner)) {
Findings
✓ No findings reported for this file.