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} from 'shared/ReactFeatureFlags';2021import {22 scheduleWork,23 scheduleMicrotask,24 flushBuffered,25 beginWriting,26 writeChunk,27 writeChunkAndReturn,28 stringToChunk,29 typedArrayToBinaryChunk,30 byteLengthOfChunk,31 byteLengthOfBinaryChunk,32 completeWriting,33 close,34 closeWithError,35} from './ReactServerStreamConfig';3637export type {Destination, Chunk} from './ReactServerStreamConfig';3839import type {40 ClientManifest,41 ClientReferenceMetadata,42 ClientReference,43 ClientReferenceKey,44 ServerReference,45 ServerReferenceId,46 Hints,47 HintCode,48 HintModel,49 FormatContext,50} from './ReactFlightServerConfig';51import type {ThenableState} from './ReactFlightThenable';52import type {53 Wakeable,54 Thenable,55 PendingThenable,56 FulfilledThenable,57 RejectedThenable,58 ReactDebugInfo,59 ReactDebugInfoEntry,60 ReactComponentInfo,61 ReactIOInfo,62 ReactAsyncInfo,63 ReactStackTrace,64 ReactCallSite,65 ReactFunctionLocation,66 ReactErrorInfo,67 ReactErrorInfoDev,68 ReactKey,69} from 'shared/ReactTypes';70import type {ReactElement} from 'shared/ReactElementType';71import type {LazyComponent} from 'react/src/ReactLazy';72import type {73 AsyncSequence,74 IONode,75 PromiseNode,76 UnresolvedPromiseNode,77} from './ReactFlightAsyncSequence';7879import {80 resolveClientReferenceMetadata,81 getServerReferenceId,82 getServerReferenceBoundArguments,83 getServerReferenceLocation,84 getClientReferenceKey,85 isClientReference,86 isServerReference,87 supportsRequestStorage,88 requestStorage,89 createHints,90 createRootFormatContext,91 getChildFormatContext,92 initAsyncDebugInfo,93 markAsyncSequenceRootTask,94 getCurrentAsyncSequence,95 getAsyncSequenceFromPromise,96 parseStackTrace,97 parseStackTracePrivate,98 supportsComponentStorage,99 componentStorage,100 unbadgeConsole,101} from './ReactFlightServerConfig';102103import {104 resolveTemporaryReference,105 isOpaqueTemporaryReference,106} from './ReactFlightServerTemporaryReferences';107108import {109 HooksDispatcher,110 prepareToUseHooksForRequest,111 prepareToUseHooksForComponent,112 getThenableStateAfterSuspending,113 getTrackedThenablesAfterRendering,114 resetHooksForRequest,115} from './ReactFlightHooks';116import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';117118import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner';119120import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';121import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';122123import noop from 'shared/noop';124125import {126 callComponentInDEV,127 callLazyInitInDEV,128 callIteratorInDEV,129} from './ReactFlightCallUserSpace';130131import {132 getIteratorFn,133 REACT_ELEMENT_TYPE,134 REACT_LEGACY_ELEMENT_TYPE,135 REACT_FORWARD_REF_TYPE,136 REACT_FRAGMENT_TYPE,137 REACT_LAZY_TYPE,138 REACT_MEMO_TYPE,139 ASYNC_ITERATOR,140 REACT_OPTIMISTIC_KEY,141} from 'shared/ReactSymbols';142143import {144 describeObjectForErrorMessage,145 isGetter,146 isSimpleObject,147 jsxPropsParents,148 jsxChildrenParents,149 objectName,150} from 'shared/ReactSerializationErrors';151152import ReactSharedInternals from './ReactSharedInternalsServer';153import isArray from 'shared/isArray';154import getPrototypeOf from 'shared/getPrototypeOf';155import hasOwnProperty from 'shared/hasOwnProperty';156import binaryToComparableString from 'shared/binaryToComparableString';157158import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';159160import {161 IO_NODE,162 PROMISE_NODE,163 AWAIT_NODE,164 UNRESOLVED_AWAIT_NODE,165 UNRESOLVED_PROMISE_NODE,166} from './ReactFlightAsyncSequence';167168// DEV-only set containing internal objects that should not be limited and turned into getters.169const doNotLimit: WeakSet<Reference> = __DEV__ ? new WeakSet() : (null as any);170171function defaultFilterStackFrame(172 filename: string,173 functionName: string,174): boolean {175 return (176 filename !== '' &&177 !filename.startsWith('node:') &&178 !filename.includes('node_modules')179 );180}181182function devirtualizeURL(url: string): string {183 if (url.startsWith('about://React/')) {184 // This callsite is a virtual fake callsite that came from another Flight client.185 // We need to reverse it back into the original location by stripping its prefix186 // and suffix. We don't need the environment name because it's available on the187 // parent object that will contain the stack.188 const envIdx = url.indexOf('/', 'about://React/'.length);189 const suffixIdx = url.lastIndexOf('?');190 if (envIdx > -1 && suffixIdx > -1) {191 return decodeURI(url.slice(envIdx + 1, suffixIdx));192 }193 }194 return url;195}196197function isPromiseCreationInternal(url: string, functionName: string): boolean {198 // Various internals of the JS VM can create Promises but the call frame of the199 // internals are not very interesting for our purposes so we need to skip those.200 if (url === 'node:internal/async_hooks') {201 // Ignore the stack frames from the async hooks themselves.202 return true;203 }204 if (url !== '') {205 return false;206 }207 switch (functionName) {208 case 'new Promise':209 case 'Function.withResolvers':210 case 'Function.reject':211 case 'Function.resolve':212 case 'Function.all':213 case 'Function.allSettled':214 case 'Function.race':215 case 'Function.try':216 return true;217 default:218 return false;219 }220}221222function stripLeadingPromiseCreationFrames(223 stack: ReactStackTrace,224): ReactStackTrace {225 for (let i = 0; i < stack.length; i++) {226 const callsite = stack[i];227 const functionName = callsite[0];228 const url = callsite[1];229 if (!isPromiseCreationInternal(url, functionName)) {230 if (i > 0) {231 return stack.slice(i);232 } else {233 return stack;234 }235 }236 }237 return [];238}239240function findCalledFunctionNameFromStackTrace(241 request: Request,242 stack: ReactStackTrace,243): string {244 // Gets the name of the first function called from first party code.245 let bestMatch = '';246 const filterStackFrame = request.filterStackFrame;247 for (let i = 0; i < stack.length; i++) {248 const callsite = stack[i];249 const functionName = callsite[0];250 const url = devirtualizeURL(callsite[1]);251 const lineNumber = callsite[2];252 const columnNumber = callsite[3];253 if (254 filterStackFrame(url, functionName, lineNumber, columnNumber) &&255 // Don't consider anonymous code first party even if the filter wants to include them in the stack.256 url !== ''257 ) {258 if (bestMatch === '') {259 // If we had no good stack frames for internal calls, just use the last260 // first party function name.261 return functionName;262 }263 return bestMatch;264 } else {265 bestMatch = functionName;266 }267 }268 return '';269}270271function filterStackTrace(272 request: Request,273 stack: ReactStackTrace,274): ReactStackTrace {275 // Since stacks can be quite large and we pass a lot of them, we filter them out eagerly276 // to save bandwidth even in DEV. We'll also replay these stacks on the client so by277 // stripping them early we avoid that overhead. Otherwise we'd normally just rely on278 // the DevTools or framework's ignore lists to filter them out.279 const filterStackFrame = request.filterStackFrame;280 const filteredStack: ReactStackTrace = [];281 for (let i = 0; i < stack.length; i++) {282 const callsite = stack[i];283 const functionName = callsite[0];284 const url = devirtualizeURL(callsite[1]);285 const lineNumber = callsite[2];286 const columnNumber = callsite[3];287 if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {288 // Use a clone because the Flight protocol isn't yet resilient to deduping289 // objects in the debug info. TODO: Support deduping stacks.290 const clone: ReactCallSite = callsite.slice(0) as any;291 clone[1] = url;292 filteredStack.push(clone);293 }294 }295 return filteredStack;296}297298function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {299 const filterStackFrame = request.filterStackFrame;300 for (let i = 0; i < stack.length; i++) {301 const callsite = stack[i];302 const functionName = callsite[0];303 const url = devirtualizeURL(callsite[1]);304 const lineNumber = callsite[2];305 const columnNumber = callsite[3];306 // Ignore async stack frames because they're not "real". We'd expect to have at least307 // one non-async frame if we're actually executing inside a first party function.308 // Otherwise we might just be in the resume of a third party function that resumed309 // inside a first party stack.310 const isAsync = callsite[6];311 if (312 !isAsync &&313 filterStackFrame(url, functionName, lineNumber, columnNumber) &&314 // Ignore anonymous stack frames like internals. They are also not in first party315 // code even though it might be useful to include them in the final stack.316 url !== ''317 ) {318 return true;319 }320 }321 return false;322}323324function isPromiseAwaitInternal(url: string, functionName: string): boolean {325 // Various internals of the JS VM can await internally on a Promise. If those are at326 // the top of the stack then we don't want to consider them as internal frames. The327 // true "await" conceptually is the thing that called the helper.328 // Ideally we'd also include common third party helpers for this.329 if (url === 'node:internal/async_hooks') {330 // Ignore the stack frames from the async hooks themselves.331 return true;332 }333 if (url !== '') {334 return false;335 }336 switch (functionName) {337 case 'Promise.then':338 case 'Promise.catch':339 case 'Promise.finally':340 case 'Function.reject':341 case 'Function.resolve':342 case 'Function.all':343 case 'Function.allSettled':344 case 'Function.any':345 case 'Function.race':346 case 'Function.try':347 case 'Function.withResolvers':348 return true;349 default:350 return false;351 }352}353354export function isAwaitInUserspace(355 request: Request,356 stack: ReactStackTrace,357): boolean {358 let firstFrame = 0;359 while (360 stack.length > firstFrame &&361 isPromiseAwaitInternal(stack[firstFrame][1], stack[firstFrame][0])362 ) {363 // Skip the internal frame that awaits itself.364 firstFrame++;365 }366 if (stack.length > firstFrame) {367 // Check if the very first stack frame that awaited this Promise was in user space.368 // TODO: This doesn't take into account wrapper functions such as our fake .then()369 // in FlightClient which will always be considered third party awaits if you call370 // .then directly.371 const filterStackFrame = request.filterStackFrame;372 const callsite = stack[firstFrame];373 const functionName = callsite[0];374 const url = devirtualizeURL(callsite[1]);375 const lineNumber = callsite[2];376 const columnNumber = callsite[3];377 return (378 filterStackFrame(url, functionName, lineNumber, columnNumber) &&379 url !== ''380 );381 }382 return false;383}384385initAsyncDebugInfo();386387function patchConsole(consoleInst: typeof console, methodName: string) {388 const descriptor = Object.getOwnPropertyDescriptor(consoleInst, methodName);389 if (390 descriptor &&391 (descriptor.configurable || descriptor.writable) &&392 typeof descriptor.value === 'function'393 ) {394 const originalMethod = descriptor.value;395 const originalName = Object.getOwnPropertyDescriptor(396 // $FlowFixMe[incompatible-type]: We should be able to get descriptors from any function.397 originalMethod,398 'name',399 );400 const wrapperMethod = function (this: typeof console) {401 const request = resolveRequest();402 if (methodName === 'assert' && arguments[0]) {403 // assert doesn't emit anything unless first argument is falsy so we can skip it.404 } else if (request !== null) {405 // Extract the stack. Not all console logs print the full stack but they have at406 // least the line it was called from. We could optimize transfer by keeping just407 // one stack frame but keeping it simple for now and include all frames.408 const stack = filterStackTrace(409 request,410 parseStackTracePrivate(new Error('react-stack-top-frame'), 1) || [],411 );412 request.pendingDebugChunks++;413 const owner: null | ReactComponentInfo = resolveOwner();414 const args = Array.from(arguments);415 // Extract the env if this is a console log that was replayed from another env.416 let env = unbadgeConsole(methodName, args);417 if (env === null) {418 // Otherwise add the current environment.419 env = (0, request.environmentName)();420 }421422 emitConsoleChunk(request, methodName, owner, env, stack, args);423 }424 // $FlowFixMe[incompatible-call]425 // $FlowFixMe[incompatible-type]426 return originalMethod.apply(this, arguments);427 };428 if (originalName) {429 Object.defineProperty(430 wrapperMethod,431 // $FlowFixMe[cannot-write] yes it is432 'name',433 originalName,434 );435 }436 Object.defineProperty(consoleInst, methodName, {437 value: wrapperMethod,438 });439 }440}441442// $FlowFixMe[invalid-compare]443if (__DEV__ && typeof console === 'object' && console !== null) {444 // Instrument console to capture logs for replaying on the client.445 patchConsole(console, 'assert');446 patchConsole(console, 'debug');447 patchConsole(console, 'dir');448 patchConsole(console, 'dirxml');449 patchConsole(console, 'error');450 patchConsole(console, 'group');451 patchConsole(console, 'groupCollapsed');452 patchConsole(console, 'groupEnd');453 patchConsole(console, 'info');454 patchConsole(console, 'log');455 patchConsole(console, 'table');456 patchConsole(console, 'trace');457 patchConsole(console, 'warn');458}459460function getCurrentStackInDEV(): string {461 if (__DEV__) {462 const owner: null | ReactComponentInfo = resolveOwner();463 if (owner === null) {464 return '';465 }466 return getOwnerStackByComponentInfoInDev(owner);467 }468 return '';469}470471const ObjectPrototype = Object.prototype;472473const stringify = JSON.stringify;474475type ReactJSONValue =476 | string477 | boolean478 | number479 | null480 | $ReadOnlyArray<ReactClientValue>481 | ReactClientObject;482483// Serializable values484export type ReactClientValue =485 // Server Elements and Lazy Components are unwrapped on the Server486 | React$Element<component(...props: any)>487 | LazyComponent<ReactClientValue, any>488 // References are passed by their value489 | ClientReference<any>490 | ServerReference<any>491 // The rest are passed as is. Sub-types can be passed in but lose their492 // subtype, so the receiver can only accept once of these.493 | React$Element<string>494 | React$Element<ClientReference<any> & any>495 | ReactComponentInfo496 | ReactErrorInfo497 | string498 | boolean499 | number500 | symbol501 | null502 | void503 | bigint504 | ReadableStream505 | $AsyncIterable<ReactClientValue, ReactClientValue, void>506 | $AsyncIterator<ReactClientValue, ReactClientValue, void>507 | Iterable<ReactClientValue>508 | Iterator<ReactClientValue>509 | Array<ReactClientValue>510 | Map<ReactClientValue, ReactClientValue>511 | Set<ReactClientValue>512 | FormData513 | $ArrayBufferView514 | ArrayBuffer515 | Date516 | ReactClientObject517 | Promise<ReactClientValue>; // Thenable<ReactClientValue>518519type ReactClientObject = {+[key: string]: ReactClientValue};520521// task status522const PENDING = 0;523const COMPLETED = 1;524const ABORTED = 3;525const ERRORED = 4;526const RENDERING = 5;527528type Task = {529 id: number,530 status: 0 | 1 | 3 | 4 | 5,531 model: ReactClientValue,532 ping: () => void,533 keyPath: ReactKey, // parent server component keys534 implicitSlot: boolean, // true if the root server component of this sequence had a null key535 formatContext: FormatContext, // an approximate parent context from host components536 thenableState: ThenableState | null,537 timed: boolean, // Profiling-only. Whether we need to track the completion time of this task.538 time: number, // Profiling-only. The last time stamp emitted for this task.539 environmentName: string, // DEV-only. Used to track if the environment for this task changed.540 debugOwner: null | ReactComponentInfo, // DEV-only541 debugStack: null | Error, // DEV-only542 debugTask: null | ConsoleTask, // DEV-only543};544545interface Reference {}546547type ReactClientReference = Reference & ReactClientValue;548549type DeferredDebugStore = {550 retained: Map<number, ReactClientReference | string>,551 existing: Map<ReactClientReference | string, number>,552};553554const __PROTO__ = '__proto__';555556const OPENING = 10;557const OPEN = 11;558const ABORTING = 12;559const CLOSING = 13;560const CLOSED = 14;561562const RENDER = 20;563const PRERENDER = 21;564565// Marker pushed before a [headerChunk, contentChunk] pair in566// completedRegularChunks / completedDebugChunks to signal that the next two567// entries must be written atomically — see emitTextChunk and568// emitTypedArrayChunk for why, and flushCompletedChunks for how it's read.569const NEXT_TWO_CHUNKS_ARE_ATOMIC: symbol = Symbol();570571export type Request = {572 status: 10 | 11 | 12 | 13 | 14,573 type: 20 | 21,574 flushScheduled: boolean,575 fatalError: mixed,576 destination: null | Destination,577 bundlerConfig: ClientManifest,578 cache: Map<Function, mixed>,579 cacheController: AbortController,580 nextChunkId: number,581 pendingChunks: number,582 hints: Hints,583 abortableTasks: Set<Task>,584 pingedTasks: Array<Task>,585 completedImportChunks: Array<Chunk>,586 completedHintChunks: Array<Chunk>,587 // Text and TypedArray rows are pushed as a NEXT_TWO_CHUNKS_ARE_ATOMIC588 // sentinel followed by their [headerChunk, contentChunk] pair, so that589 // flushCompletedChunks can write the pair atomically and never strand the590 // content chunk on a backpressure break.591 completedRegularChunks: Array<592 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,593 >,594 completedErrorChunks: Array<Chunk>,595 writtenSymbols: Map<symbol, number>,596 writtenClientReferences: Map<ClientReferenceKey, number>,597 writtenServerReferences: Map<ServerReference<any>, number>,598 writtenObjects: WeakMap<Reference, string>,599 temporaryReferences: void | TemporaryReferenceSet,600 identifierPrefix: string,601 identifierCount: number,602 taintCleanupQueue: Array<string | bigint>,603 onError: (error: mixed) => ?string,604 onAllReady: () => void,605 onFatalError: mixed => void,606 // Profiling-only607 timeOrigin: number,608 abortTime: number,609 // DEV-only610 pendingDebugChunks: number,611 // See completedRegularChunks for why some entries are preceded by the612 // NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel.613 completedDebugChunks: Array<614 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,615 >,616 debugDestination: null | Destination,617 environmentName: () => string,618 filterStackFrame: (619 url: string,620 functionName: string,621 lineNumber: number,622 columnNumber: number,623 ) => boolean,624 didWarnForKey: null | WeakSet<ReactComponentInfo>,625 writtenDebugObjects: WeakMap<Reference, string>,626 deferredDebugObjects: null | DeferredDebugStore,627};628629const {630 TaintRegistryObjects,631 TaintRegistryValues,632 TaintRegistryByteLengths,633 TaintRegistryPendingRequests,634} = ReactSharedInternals;635636function throwTaintViolation(message: string) {637 // eslint-disable-next-line react-internal/prod-error-codes638 throw new Error(message);639}640641function cleanupTaintQueue(request: Request): void {642 const cleanupQueue = request.taintCleanupQueue;643 TaintRegistryPendingRequests.delete(cleanupQueue);644 for (let i = 0; i < cleanupQueue.length; i++) {645 const entryValue = cleanupQueue[i];646 const entry = TaintRegistryValues.get(entryValue);647 if (entry !== undefined) {648 if (entry.count === 1) {649 TaintRegistryValues.delete(entryValue);650 } else {651 entry.count--;652 }653 }654 }655 cleanupQueue.length = 0;656}657658function defaultErrorHandler(error: mixed) {659 console['error'](error);660 // Don't transform to our wrapper661}662663function RequestInstance(664 this: $FlowFixMe,665 type: 20 | 21,666 model: ReactClientValue,667 bundlerConfig: ClientManifest,668 onError: void | ((error: mixed) => ?string),669 onAllReady: () => void,670 onFatalError: (error: mixed) => void,671 identifierPrefix?: string,672 temporaryReferences: void | TemporaryReferenceSet,673 debugStartTime: void | number, // Profiling-only674 environmentName: void | string | (() => string), // DEV-only675 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only676 keepDebugAlive: boolean, // DEV-only677) {678 if (679 ReactSharedInternals.A !== null &&680 ReactSharedInternals.A !== DefaultAsyncDispatcher681 ) {682 throw new Error(683 'Currently React only supports one RSC renderer at a time.',684 );685 }686 ReactSharedInternals.A = DefaultAsyncDispatcher;687 if (__DEV__) {688 // Unlike Fizz or Fiber, we don't reset this and just keep it on permanently.689 // This lets it act more like the AsyncDispatcher so that we can get the690 // stack asynchronously too.691 ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;692 }693694 const abortSet: Set<Task> = new Set();695 const pingedTasks: Array<Task> = [];696 const cleanupQueue: Array<string | bigint> = [];697 if (enableTaint) {698 TaintRegistryPendingRequests.add(cleanupQueue);699 }700 const hints = createHints();701 this.type = type;702 this.status = OPENING;703 this.flushScheduled = false;704 this.fatalError = null;705 this.destination = null;706 this.bundlerConfig = bundlerConfig;707 this.cache = new Map();708 this.cacheController = new AbortController();709 this.nextChunkId = 0;710 this.pendingChunks = 0;711 this.hints = hints;712 this.abortableTasks = abortSet;713 this.pingedTasks = pingedTasks;714 this.completedImportChunks = [] as Array<Chunk>;715 this.completedHintChunks = [] as Array<Chunk>;716 this.completedRegularChunks = [] as Array<717 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,718 >;719 this.completedErrorChunks = [] as Array<Chunk>;720 this.writtenSymbols = new Map();721 this.writtenClientReferences = new Map();722 this.writtenServerReferences = new Map();723 this.writtenObjects = new WeakMap();724 this.temporaryReferences = temporaryReferences;725 this.identifierPrefix = identifierPrefix || '';726 this.identifierCount = 1;727 this.taintCleanupQueue = cleanupQueue;728 this.onError = onError === undefined ? defaultErrorHandler : onError;729 this.onAllReady = onAllReady;730 this.onFatalError = onFatalError;731732 if (__DEV__) {733 this.pendingDebugChunks = 0;734 this.completedDebugChunks = [] as Array<735 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,736 >;737 this.debugDestination = null;738 this.environmentName =739 environmentName === undefined740 ? () => 'Server'741 : typeof environmentName !== 'function'742 ? () => environmentName743 : environmentName;744 this.filterStackFrame =745 filterStackFrame === undefined746 ? defaultFilterStackFrame747 : filterStackFrame;748 this.didWarnForKey = null;749 this.writtenDebugObjects = new WeakMap();750 this.deferredDebugObjects = keepDebugAlive751 ? {752 retained: new Map(),753 existing: new Map(),754 }755 : null;756 }757758 let timeOrigin: number;759 if (760 enableProfilerTimer &&761 (enableComponentPerformanceTrack || enableAsyncDebugInfo)762 ) {763 // We start by serializing the time origin. Any future timestamps will be764 // emitted relatively to this origin. Instead of using performance.timeOrigin765 // as this origin, we use the timestamp at the start of the request.766 // This avoids leaking unnecessary information like how long the server has767 // been running and allows for more compact representation of each timestamp.768 // The time origin is stored as an offset in the time space of this environment.769 if (typeof debugStartTime === 'number') {770 // We expect `startTime` to be an absolute timestamp, so relativize it to match the other case.771 timeOrigin = this.timeOrigin =772 debugStartTime -773 // $FlowFixMe[prop-missing]774 performance.timeOrigin;775 } else {776 timeOrigin = this.timeOrigin = performance.now();777 }778 emitTimeOriginChunk(779 this,780 timeOrigin +781 // $FlowFixMe[prop-missing]782 performance.timeOrigin,783 );784 this.abortTime = -0.0;785 } else {786 timeOrigin = 0;787 }788789 const rootTask = createTask(790 this,791 model,792 null,793 false,794 createRootFormatContext(),795 abortSet,796 timeOrigin,797 null,798 null,799 null,800 );801 pingedTasks.push(rootTask);802}803804export function createRequest(805 model: ReactClientValue,806 bundlerConfig: ClientManifest,807 onError: void | ((error: mixed) => ?string),808 identifierPrefix: void | string,809 temporaryReferences: void | TemporaryReferenceSet,810 debugStartTime: void | number, // Profiling-only811 environmentName: void | string | (() => string), // DEV-only812 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only813 keepDebugAlive: boolean, // DEV-only814): Request {815 if (__DEV__) {816 resetOwnerStackLimit();817 }818819 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors820 return new RequestInstance(821 RENDER,822 model,823 bundlerConfig,824 onError,825 noop,826 noop,827 identifierPrefix,828 temporaryReferences,829 debugStartTime,830 environmentName,831 filterStackFrame,832 keepDebugAlive,833 );834}835836export function createPrerenderRequest(837 model: ReactClientValue,838 bundlerConfig: ClientManifest,839 onAllReady: () => void,840 onFatalError: () => void,841 onError: void | ((error: mixed) => ?string),842 identifierPrefix: void | string,843 temporaryReferences: void | TemporaryReferenceSet,844 debugStartTime: void | number, // Profiling-only845 environmentName: void | string | (() => string), // DEV-only846 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only847 keepDebugAlive: boolean, // DEV-only848): Request {849 if (__DEV__) {850 resetOwnerStackLimit();851 }852853 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors854 return new RequestInstance(855 PRERENDER,856 model,857 bundlerConfig,858 onError,859 onAllReady,860 onFatalError,861 identifierPrefix,862 temporaryReferences,863 debugStartTime,864 environmentName,865 filterStackFrame,866 keepDebugAlive,867 );868}869870let currentRequest: null | Request = null;871872export function resolveRequest(): null | Request {873 if (currentRequest) return currentRequest;874 // $FlowFixMe[constant-condition]875 if (supportsRequestStorage) {876 const store = requestStorage.getStore();877 if (store) return store;878 }879 return null;880}881882function isTypedArray(value: any): boolean {883 if (value instanceof ArrayBuffer) {884 return true;885 }886 if (value instanceof Int8Array) {887 return true;888 }889 if (value instanceof Uint8Array) {890 return true;891 }892 if (value instanceof Uint8ClampedArray) {893 return true;894 }895 if (value instanceof Int16Array) {896 return true;897 }898 if (value instanceof Uint16Array) {899 return true;900 }901 if (value instanceof Int32Array) {902 return true;903 }904 if (value instanceof Uint32Array) {905 return true;906 }907 if (value instanceof Float32Array) {908 return true;909 }910 if (value instanceof Float64Array) {911 return true;912 }913 if (value instanceof BigInt64Array) {914 return true;915 }916 if (value instanceof BigUint64Array) {917 return true;918 }919 if (value instanceof DataView) {920 return true;921 }922 return false;923}924925function serializeDebugThenable(926 request: Request,927 counter: {objectLimit: number},928 thenable: Thenable<any>,929): string {930 // Like serializeThenable but for renderDebugModel931 request.pendingDebugChunks++;932 const id = request.nextChunkId++;933 const ref = serializePromiseID(id);934 request.writtenDebugObjects.set(thenable, ref);935936 switch (thenable.status) {937 case 'fulfilled': {938 emitOutlinedDebugModelChunk(request, id, counter, thenable.value);939 return ref;940 }941 case 'rejected': {942 const x = thenable.reason;943 // We don't log these errors since they didn't actually throw into Flight.944 const digest = '';945 emitErrorChunk(request, id, digest, x, true, null);946 return ref;947 }948 }949950 if (request.status === ABORTING) {951 // Ensure that we have time to emit the halt chunk if we're sync aborting.952 emitDebugHaltChunk(request, id);953 return ref;954 }955956 const deferredDebugObjects = request.deferredDebugObjects;957 if (deferredDebugObjects !== null) {958 // For Promises that are not yet resolved, we always defer them. They are async anyway so it's959 // safe to defer them. This also ensures that we don't eagerly call .then() on a Promise that960 // otherwise wouldn't have initialized. It also ensures that we don't "handle" a rejection961 // that otherwise would have triggered unhandled rejection.962 deferredDebugObjects.retained.set(id, thenable as any);963 const deferredRef = '$Y@' + id.toString(16);964 // We can now refer to the deferred object in the future.965 request.writtenDebugObjects.set(thenable, deferredRef);966 return deferredRef;967 }968969 let cancelled = false;970971 thenable.then(972 value => {973 if (cancelled) {974 return;975 }976 cancelled = true;977 if (request.status === ABORTING) {978 emitDebugHaltChunk(request, id);979 enqueueFlush(request);980 return;981 }982 if (983 (isArray(value) && value.length > 200) ||984 (isTypedArray(value) && value.byteLength > 1000)985 ) {986 // If this should be deferred, but we don't have a debug channel installed987 // it would get omitted. We can't omit outlined models but we can avoid988 // resolving the Promise at all by halting it.989 emitDebugHaltChunk(request, id);990 enqueueFlush(request);991 return;992 }993 emitOutlinedDebugModelChunk(request, id, counter, value);994 enqueueFlush(request);995 },996 reason => {997 if (cancelled) {998 return;999 }1000 cancelled = true;1001 if (request.status === ABORTING) {1002 emitDebugHaltChunk(request, id);1003 enqueueFlush(request);1004 return;1005 }1006 // We don't log these errors since they didn't actually throw into Flight.1007 const digest = '';1008 emitErrorChunk(request, id, digest, reason, true, null);1009 enqueueFlush(request);1010 },1011 );10121013 // We don't use scheduleMicrotask here because it doesn't actually schedule a microtask1014 // in all our configs which is annoying.1015 Promise.resolve().then(() => {1016 // If we don't resolve the Promise within a microtask. Leave it as hanging since we1017 // don't want to block the render forever on a Promise that might never resolve.1018 if (cancelled) {1019 return;1020 }1021 cancelled = true;1022 emitDebugHaltChunk(request, id);1023 enqueueFlush(request);1024 // Clean up the request so we don't leak this forever.1025 request = null as any;1026 counter = null as any;1027 });10281029 return ref;1030}10311032function emitRequestedDebugThenable(1033 request: Request,1034 id: number,1035 counter: {objectLimit: number},1036 thenable: Thenable<any>,1037): void {1038 thenable.then(1039 value => {1040 if (request.status === ABORTING) {1041 emitDebugHaltChunk(request, id);1042 enqueueFlush(request);1043 return;1044 }1045 emitOutlinedDebugModelChunk(request, id, counter, value);1046 enqueueFlush(request);1047 },1048 reason => {1049 if (request.status === ABORTING) {1050 emitDebugHaltChunk(request, id);1051 enqueueFlush(request);1052 return;1053 }1054 // We don't log these errors since they didn't actually throw into Flight.1055 const digest = '';1056 emitErrorChunk(request, id, digest, reason, true, null);1057 enqueueFlush(request);1058 },1059 );1060}10611062function serializeThenable(1063 request: Request,1064 task: Task,1065 thenable: Thenable<any>,1066): number {1067 const newTask = createTask(1068 request,1069 thenable as any, // will be replaced by the value before we retry. used for debug info.1070 task.keyPath, // the server component sequence continues through Promise-as-a-child.1071 task.implicitSlot,1072 task.formatContext,1073 request.abortableTasks,1074 enableProfilerTimer &&1075 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1076 ? task.time1077 : 0,1078 __DEV__ ? task.debugOwner : null,1079 __DEV__ ? task.debugStack : null,1080 __DEV__ ? task.debugTask : null,1081 );10821083 switch (thenable.status) {1084 case 'fulfilled': {1085 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);1086 // We have the resolved value, we can go ahead and schedule it for serialization.1087 newTask.model = thenable.value;1088 pingTask(request, newTask);1089 return newTask.id;1090 }1091 case 'rejected': {1092 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);1093 const x = thenable.reason;1094 erroredTask(request, newTask, x);1095 return newTask.id;1096 }1097 default: {1098 if (request.status === ABORTING) {1099 // We can no longer accept any resolved values1100 request.abortableTasks.delete(newTask);1101 if (request.type === PRERENDER) {1102 haltTask(newTask, request);1103 finishHaltedTask(newTask, request);1104 } else {1105 const errorId: number = request.fatalError as any;1106 abortTask(newTask, request, errorId);1107 finishAbortedTask(newTask, request, errorId);1108 }1109 return newTask.id;1110 }1111 if (typeof thenable.status === 'string') {1112 // Only instrument the thenable if the status if not defined. If1113 // it's defined, but an unknown value, assume it's been instrumented by1114 // some custom userspace implementation. We treat it as "pending".1115 break;1116 }1117 const pendingThenable: PendingThenable<mixed> = thenable as any;1118 pendingThenable.status = 'pending';1119 pendingThenable.then(1120 fulfilledValue => {1121 if (thenable.status === 'pending') {1122 const fulfilledThenable: FulfilledThenable<mixed> = thenable as any;1123 fulfilledThenable.status = 'fulfilled';1124 fulfilledThenable.value = fulfilledValue;1125 }1126 },1127 (error: mixed) => {1128 if (thenable.status === 'pending') {1129 const rejectedThenable: RejectedThenable<mixed> = thenable as any;1130 rejectedThenable.status = 'rejected';1131 rejectedThenable.reason = error;1132 }1133 },1134 );1135 break;1136 }1137 }11381139 thenable.then(1140 value => {1141 forwardDebugInfoFromCurrentContext(request, newTask, thenable);1142 newTask.model = value;1143 pingTask(request, newTask);1144 },1145 reason => {1146 if (newTask.status === PENDING) {1147 if (1148 enableProfilerTimer &&1149 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1150 ) {1151 // If this is async we need to time when this task finishes.1152 newTask.timed = true;1153 }1154 // We expect that the only status it might be otherwise is ABORTED.1155 // When we abort we emit chunks in each pending task slot and don't need1156 // to do so again here.1157 erroredTask(request, newTask, reason);1158 enqueueFlush(request);1159 }1160 },1161 );11621163 return newTask.id;1164}11651166function serializeReadableStream(1167 request: Request,1168 task: Task,1169 stream: ReadableStream,1170): string {1171 // Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the1172 // receiving side. It also implies that different chunks can be split up or merged as opposed1173 // to a readable stream that happens to have Uint8Array as the type which might expect it to be1174 // received in the same slices.1175 // $FlowFixMe[prop-missing]: This is a Node.js extension.1176 let supportsBYOB: void | boolean = stream.supportsBYOB;1177 if (supportsBYOB === undefined) {1178 try {1179 // $FlowFixMe[extra-arg]: This argument is accepted.1180 stream.getReader({mode: 'byob'}).releaseLock();1181 supportsBYOB = true;1182 } catch (x) {1183 supportsBYOB = false;1184 }1185 }1186 // At this point supportsBYOB is guaranteed to be a boolean.1187 const isByteStream: boolean = supportsBYOB;11881189 const reader = stream.getReader();11901191 // This task won't actually be retried. We just use it to attempt synchronous renders.1192 const streamTask = createTask(1193 request,1194 task.model,1195 task.keyPath,1196 task.implicitSlot,1197 task.formatContext,1198 request.abortableTasks,1199 enableProfilerTimer &&1200 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1201 ? task.time1202 : 0,1203 __DEV__ ? task.debugOwner : null,1204 __DEV__ ? task.debugStack : null,1205 __DEV__ ? task.debugTask : null,1206 );12071208 // The task represents the Stop row. This adds a Start row.1209 request.pendingChunks++;1210 const startStreamRow =1211 streamTask.id.toString(16) + ':' + (isByteStream ? 'r' : 'R') + '\n';1212 request.completedRegularChunks.push(stringToChunk(startStreamRow));12131214 function progress(entry: {done: boolean, value: ReactClientValue, ...}) {1215 if (streamTask.status !== PENDING) {1216 return;1217 }12181219 if (entry.done) {1220 streamTask.status = COMPLETED;1221 const endStreamRow = streamTask.id.toString(16) + ':C\n';1222 request.completedRegularChunks.push(stringToChunk(endStreamRow));1223 request.abortableTasks.delete(streamTask);1224 request.cacheController.signal.removeEventListener('abort', abortStream);1225 enqueueFlush(request);1226 callOnAllReadyIfReady(request);1227 } else {1228 try {1229 request.pendingChunks++;1230 streamTask.model = entry.value;1231 if (isByteStream) {1232 // Chunks of byte streams are always Uint8Array instances.1233 const chunk: Uint8Array = streamTask.model as any;1234 emitTypedArrayChunk(request, streamTask.id, 'b', chunk, false);1235 } else {1236 tryStreamTask(request, streamTask);1237 }1238 enqueueFlush(request);1239 reader.read().then(progress, error);1240 } catch (x) {1241 error(x);1242 }1243 }1244 }1245 function error(reason: mixed) {1246 if (streamTask.status !== PENDING) {1247 return;1248 }1249 request.cacheController.signal.removeEventListener('abort', abortStream);1250 erroredTask(request, streamTask, reason);1251 enqueueFlush(request);12521253 // $FlowFixMe[incompatible-type] should be able to pass mixed1254 // $FlowFixMe[incompatible-use]1255 reader.cancel(reason).then(error, error);1256 }1257 function abortStream() {1258 if (streamTask.status !== PENDING) {1259 return;1260 }1261 const signal = request.cacheController.signal;1262 signal.removeEventListener('abort', abortStream);1263 const reason = signal.reason;1264 if (request.type === PRERENDER) {1265 request.abortableTasks.delete(streamTask);1266 haltTask(streamTask, request);1267 finishHaltedTask(streamTask, request);1268 } else {1269 // TODO: Make this use abortTask() instead.1270 erroredTask(request, streamTask, reason);1271 enqueueFlush(request);1272 }1273 // $FlowFixMe[incompatible-use] should be able to pass mixed1274 reader.cancel(reason).then(error, error);1275 }12761277 request.cacheController.signal.addEventListener('abort', abortStream);1278 reader.read().then(progress, error);1279 return serializeByValueID(streamTask.id);1280}12811282function serializeAsyncIterable(1283 request: Request,1284 task: Task,1285 iterable: $AsyncIterable<ReactClientValue, ReactClientValue, void>,1286 iterator: $AsyncIterator<ReactClientValue, ReactClientValue, void>,1287): string {1288 // Generators/Iterators are Iterables but they're also their own iterator1289 // functions. If that's the case, we treat them as single-shot. Otherwise,1290 // we assume that this iterable might be a multi-shot and allow it to be1291 // iterated more than once on the client.1292 const isIterator = iterable === iterator;12931294 // This task won't actually be retried. We just use it to attempt synchronous renders.1295 const streamTask = createTask(1296 request,1297 task.model,1298 task.keyPath,1299 task.implicitSlot,1300 task.formatContext,1301 request.abortableTasks,1302 enableProfilerTimer &&1303 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1304 ? task.time1305 : 0,1306 __DEV__ ? task.debugOwner : null,1307 __DEV__ ? task.debugStack : null,1308 __DEV__ ? task.debugTask : null,1309 );13101311 if (__DEV__) {1312 const debugInfo: ?ReactDebugInfo = (iterable as any)._debugInfo;1313 if (debugInfo) {1314 forwardDebugInfo(request, streamTask, debugInfo);1315 }1316 }13171318 // The task represents the Stop row. This adds a Start row.1319 request.pendingChunks++;1320 const startStreamRow =1321 streamTask.id.toString(16) + ':' + (isIterator ? 'x' : 'X') + '\n';1322 request.completedRegularChunks.push(stringToChunk(startStreamRow));13231324 function progress(1325 entry:1326 | {done: false, +value: ReactClientValue, ...}1327 | {done: true, +value: ReactClientValue, ...},1328 ) {1329 if (streamTask.status !== PENDING) {1330 return;1331 }13321333 if (entry.done) {1334 streamTask.status = COMPLETED;1335 let endStreamRow;1336 if (entry.value === undefined) {1337 endStreamRow = streamTask.id.toString(16) + ':C\n';1338 } else {1339 // Unlike streams, the last value may not be undefined. If it's not1340 // we outline it and encode a reference to it in the closing instruction.1341 try {1342 const chunkId = outlineModel(request, entry.value);1343 endStreamRow =1344 streamTask.id.toString(16) +1345 ':C' +1346 stringify(serializeByValueID(chunkId)) +1347 '\n';1348 } catch (x) {1349 error(x);1350 return;1351 }1352 }1353 request.completedRegularChunks.push(stringToChunk(endStreamRow));1354 request.abortableTasks.delete(streamTask);1355 request.cacheController.signal.removeEventListener(1356 'abort',1357 abortIterable,1358 );1359 enqueueFlush(request);1360 callOnAllReadyIfReady(request);1361 } else {1362 try {1363 streamTask.model = entry.value;1364 request.pendingChunks++;1365 tryStreamTask(request, streamTask);1366 enqueueFlush(request);1367 if (__DEV__) {1368 callIteratorInDEV(iterator, progress, error);1369 } else {1370 iterator.next().then(progress, error);1371 }1372 } catch (x) {1373 error(x);1374 return;1375 }1376 }1377 }1378 function error(reason: mixed) {1379 if (streamTask.status !== PENDING) {1380 return;1381 }1382 request.cacheController.signal.removeEventListener('abort', abortIterable);1383 erroredTask(request, streamTask, reason);1384 enqueueFlush(request);1385 if (typeof (iterator as any).throw === 'function') {1386 // The iterator protocol doesn't necessarily include this but a generator do.1387 // $FlowFixMe[prop-missing] should be able to pass mixed1388 iterator.throw(reason).then(error, error);1389 }1390 }1391 function abortIterable() {1392 if (streamTask.status !== PENDING) {1393 return;1394 }1395 const signal = request.cacheController.signal;1396 signal.removeEventListener('abort', abortIterable);1397 const reason = signal.reason;1398 if (request.type === PRERENDER) {1399 request.abortableTasks.delete(streamTask);1400 haltTask(streamTask, request);1401 finishHaltedTask(streamTask, request);1402 } else {1403 // TODO: Make this use abortTask() instead.1404 erroredTask(request, streamTask, signal.reason);1405 enqueueFlush(request);1406 }1407 if (typeof (iterator as any).throw === 'function') {1408 // The iterator protocol doesn't necessarily include this but a generator do.1409 // $FlowFixMe[prop-missing] should be able to pass mixed1410 iterator.throw(reason).then(error, error);1411 }1412 }1413 request.cacheController.signal.addEventListener('abort', abortIterable);1414 if (__DEV__) {1415 callIteratorInDEV(iterator, progress, error);1416 } else {1417 iterator.next().then(progress, error);1418 }1419 return serializeByValueID(streamTask.id);1420}14211422export function emitHint<Code: HintCode>(1423 request: Request,1424 code: Code,1425 model: HintModel<Code>,1426): void {1427 emitHintChunk(request, code, model);1428 enqueueFlush(request);1429}14301431export function getHints(request: Request): Hints {1432 return request.hints;1433}14341435export function getCache(request: Request): Map<Function, mixed> {1436 return request.cache;1437}14381439function readThenable<T>(thenable: Thenable<T>): T {1440 if (thenable.status === 'fulfilled') {1441 return thenable.value;1442 } else if (thenable.status === 'rejected') {1443 throw thenable.reason;1444 }1445 throw thenable;1446}14471448function createLazyWrapperAroundWakeable(1449 request: Request,1450 task: Task,1451 wakeable: Wakeable,1452) {1453 // This is a temporary fork of the `use` implementation until we accept1454 // promises everywhere.1455 const thenable: Thenable<mixed> = wakeable as any;1456 switch (thenable.status) {1457 case 'fulfilled': {1458 forwardDebugInfoFromThenable(request, task, thenable, null, null);1459 return thenable.value;1460 }1461 case 'rejected':1462 forwardDebugInfoFromThenable(request, task, thenable, null, null);1463 break;1464 default: {1465 if (typeof thenable.status === 'string') {1466 // Only instrument the thenable if the status if not defined. If1467 // it's defined, but an unknown value, assume it's been instrumented by1468 // some custom userspace implementation. We treat it as "pending".1469 break;1470 }1471 const pendingThenable: PendingThenable<mixed> = thenable as any;1472 pendingThenable.status = 'pending';1473 pendingThenable.then(1474 fulfilledValue => {1475 forwardDebugInfoFromCurrentContext(request, task, thenable);1476 if (thenable.status === 'pending') {1477 const fulfilledThenable: FulfilledThenable<mixed> = thenable as any;1478 fulfilledThenable.status = 'fulfilled';1479 fulfilledThenable.value = fulfilledValue;1480 }1481 },1482 (error: mixed) => {1483 forwardDebugInfoFromCurrentContext(request, task, thenable);1484 if (thenable.status === 'pending') {1485 const rejectedThenable: RejectedThenable<mixed> = thenable as any;1486 rejectedThenable.status = 'rejected';1487 rejectedThenable.reason = error;1488 }1489 },1490 );1491 break;1492 }1493 }1494 const lazyType: LazyComponent<any, Thenable<any>> = {1495 $$typeof: REACT_LAZY_TYPE,1496 _payload: thenable,1497 _init: readThenable,1498 };1499 return lazyType;1500}15011502function callWithDebugContextInDEV<A, T>(1503 request: Request,1504 task: Task,1505 callback: A => T,1506 arg: A,1507): T {1508 // We don't have a Server Component instance associated with this callback and1509 // the nearest context is likely a Client Component being serialized. We create1510 // a fake owner during this callback so we can get the stack trace from it.1511 // This also gets sent to the client as the owner for the replaying log.1512 const componentDebugInfo: ReactComponentInfo = {1513 name: '',1514 env: task.environmentName,1515 key: null,1516 owner: task.debugOwner,1517 };1518 // $FlowFixMe[cannot-write]1519 componentDebugInfo.stack =1520 task.debugStack === null1521 ? null1522 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));1523 // $FlowFixMe[cannot-write]1524 componentDebugInfo.debugStack = task.debugStack;1525 // $FlowFixMe[cannot-write]1526 componentDebugInfo.debugTask = task.debugTask;1527 const debugTask = task.debugTask;1528 // We don't need the async component storage context here so we only set the1529 // synchronous tracking of owner.1530 setCurrentOwner(componentDebugInfo);1531 try {1532 if (debugTask) {1533 return debugTask.run(callback.bind(null, arg));1534 }1535 return callback(arg);1536 } finally {1537 setCurrentOwner(null);1538 }1539}15401541const voidHandler = () => {};15421543function processServerComponentReturnValue(1544 request: Request,1545 task: Task,1546 Component: any,1547 result: any,1548): any {1549 // A Server Component's return value has a few special properties due to being1550 // in the return position of a Component. We convert them here.1551 if (1552 typeof result !== 'object' ||1553 result === null ||1554 isClientReference(result)1555 ) {1556 return result;1557 }15581559 if (typeof result.then === 'function') {1560 // When the return value is in children position we can resolve it immediately,1561 // to its value without a wrapper if it's synchronously available.1562 const thenable: Thenable<any> = result;1563 if (__DEV__) {1564 // If the thenable resolves to an element, then it was in a static position,1565 // the return value of a Server Component. That doesn't need further validation1566 // of keys. The Server Component itself would have had a key.1567 thenable.then(resolvedValue => {1568 if (1569 typeof resolvedValue === 'object' &&1570 resolvedValue !== null &&1571 resolvedValue.$$typeof === REACT_ELEMENT_TYPE1572 ) {1573 resolvedValue._store.validated = 1;1574 }1575 }, voidHandler);1576 }1577 // TODO: Once we accept Promises as children on the client, we can just return1578 // the thenable here.1579 return createLazyWrapperAroundWakeable(request, task, result);1580 }15811582 if (__DEV__) {1583 if ((result as any).$$typeof === REACT_ELEMENT_TYPE) {1584 // If the server component renders to an element, then it was in a static position.1585 // That doesn't need further validation of keys. The Server Component itself would1586 // have had a key.1587 (result as any)._store.validated = 1;1588 }1589 }15901591 // Normally we'd serialize an Iterator/AsyncIterator as a single-shot which is not compatible1592 // to be rendered as a React Child. However, because we have the function to recreate1593 // an iterable from rendering the element again, we can effectively treat it as multi-1594 // shot. Therefore we treat this as an Iterable/AsyncIterable, whether it was one or not, by1595 // adding a wrapper so that this component effectively renders down to an AsyncIterable.1596 const iteratorFn = getIteratorFn(result);1597 if (iteratorFn) {1598 const iterableChild = result;1599 const multiShot = {1600 [Symbol.iterator]: function () {1601 const iterator = iteratorFn.call(iterableChild);1602 if (__DEV__) {1603 // If this was an Iterator but not a GeneratorFunction we warn because1604 // it might have been a mistake. Technically you can make this mistake with1605 // GeneratorFunctions and even single-shot Iterables too but it's extra1606 // tempting to try to return the value from a generator.1607 if (iterator === iterableChild) {1608 const isGeneratorComponent =1609 // $FlowFixMe[method-unbinding]1610 Object.prototype.toString.call(Component) ===1611 '[object GeneratorFunction]' &&1612 // $FlowFixMe[method-unbinding]1613 Object.prototype.toString.call(iterableChild) ===1614 '[object Generator]';1615 if (!isGeneratorComponent) {1616 callWithDebugContextInDEV(request, task, () => {1617 console.error(1618 'Returning an Iterator from a Server Component is not supported ' +1619 'since it cannot be looped over more than once. ',1620 );1621 });1622 }1623 }1624 }1625 return iterator as any;1626 },1627 };1628 if (__DEV__) {1629 (multiShot as any)._debugInfo = iterableChild._debugInfo;1630 }1631 return multiShot;1632 }1633 if (1634 typeof (result as any)[ASYNC_ITERATOR] === 'function' &&1635 (typeof ReadableStream !== 'function' ||1636 !(result instanceof ReadableStream))1637 ) {1638 const iterableChild = result;1639 const multishot = {1640 [ASYNC_ITERATOR]: function () {1641 const iterator = (iterableChild as any)[ASYNC_ITERATOR]();1642 if (__DEV__) {1643 // If this was an AsyncIterator but not an AsyncGeneratorFunction we warn because1644 // it might have been a mistake. Technically you can make this mistake with1645 // AsyncGeneratorFunctions and even single-shot AsyncIterables too but it's extra1646 // tempting to try to return the value from a generator.1647 if (iterator === iterableChild) {1648 const isGeneratorComponent =1649 // $FlowFixMe[method-unbinding]1650 Object.prototype.toString.call(Component) ===1651 '[object AsyncGeneratorFunction]' &&1652 // $FlowFixMe[method-unbinding]1653 Object.prototype.toString.call(iterableChild) ===1654 '[object AsyncGenerator]';1655 if (!isGeneratorComponent) {1656 callWithDebugContextInDEV(request, task, () => {1657 console.error(1658 'Returning an AsyncIterator from a Server Component is not supported ' +1659 'since it cannot be looped over more than once. ',1660 );1661 });1662 }1663 }1664 }1665 return iterator;1666 },1667 };1668 if (__DEV__) {1669 (multishot as any)._debugInfo = iterableChild._debugInfo;1670 }1671 return multishot;1672 }1673 return result;1674}16751676function renderFunctionComponent<Props>(1677 request: Request,1678 task: Task,1679 key: ReactKey,1680 Component: (p: Props, arg: void) => any,1681 props: Props,1682 validated: number, // DEV-only1683): ReactJSONValue {1684 // Reset the task's thenable state before continuing, so that if a later1685 // component suspends we can reuse the same task object. If the same1686 // component suspends again, the thenable state will be restored.1687 const prevThenableState = task.thenableState;1688 task.thenableState = null;16891690 let result;16911692 let componentDebugInfo: ReactComponentInfo;1693 if (__DEV__) {1694 if (!canEmitDebugInfo) {1695 // We don't have a chunk to assign debug info. We need to outline this1696 // component to assign it an ID.1697 return outlineTask(request, task);1698 } else if (prevThenableState !== null) {1699 // This is a replay and we've already emitted the debug info of this component1700 // in the first pass. We skip emitting a duplicate line.1701 // As a hack we stashed the previous component debug info on this object in DEV.1702 componentDebugInfo = (prevThenableState as any)._componentDebugInfo;1703 } else {1704 // This is a new component in the same task so we can emit more debug info.1705 const componentDebugID = task.id;1706 const componentName =1707 (Component as any).displayName || Component.name || '';1708 const componentEnv = (0, request.environmentName)();1709 request.pendingChunks++;1710 componentDebugInfo = {1711 name: componentName,1712 env: componentEnv,1713 key: key,1714 owner: task.debugOwner,1715 } as ReactComponentInfo;1716 // $FlowFixMe[cannot-write]1717 componentDebugInfo.stack =1718 task.debugStack === null1719 ? null1720 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));1721 // $FlowFixMe[cannot-write]1722 componentDebugInfo.props = props;1723 // $FlowFixMe[cannot-write]1724 componentDebugInfo.debugStack = task.debugStack;1725 // $FlowFixMe[cannot-write]1726 componentDebugInfo.debugTask = task.debugTask;17271728 // We outline this model eagerly so that we can refer to by reference as an owner.1729 // If we had a smarter way to dedupe we might not have to do this if there ends up1730 // being no references to this as an owner.17311732 outlineComponentInfo(request, componentDebugInfo);17331734 // Track when we started rendering this component.1735 if (1736 enableProfilerTimer &&1737 (enableComponentPerformanceTrack || enableAsyncDebugInfo)1738 ) {1739 advanceTaskTime(request, task, performance.now());1740 }17411742 emitDebugChunk(request, componentDebugID, componentDebugInfo);17431744 // We've emitted the latest environment for this task so we track that.1745 task.environmentName = componentEnv;17461747 if (validated === 2) {1748 warnForMissingKey(request, key, componentDebugInfo, task.debugTask);1749 }1750 }1751 prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);1752 // $FlowFixMe[constant-condition]1753 if (supportsComponentStorage) {1754 // Run the component in an Async Context that tracks the current owner.1755 if (task.debugTask) {1756 result = task.debugTask.run(1757 // $FlowFixMe[method-unbinding]1758 componentStorage.run.bind(1759 componentStorage,1760 componentDebugInfo,1761 callComponentInDEV,1762 Component,1763 props,1764 componentDebugInfo,1765 ),1766 );1767 } else {1768 result = componentStorage.run(1769 componentDebugInfo,1770 callComponentInDEV,1771 Component,1772 props,1773 componentDebugInfo,1774 );1775 }1776 } else {1777 if (task.debugTask) {1778 result = task.debugTask.run(1779 callComponentInDEV.bind(null, Component, props, componentDebugInfo),1780 );1781 } else {1782 result = callComponentInDEV(Component, props, componentDebugInfo);1783 }1784 }1785 } else {1786 componentDebugInfo = null as any;1787 prepareToUseHooksForComponent(prevThenableState, null);1788 // The secondArg is always undefined in Server Components since refs error early.1789 const secondArg = undefined;1790 result = Component(props, secondArg);1791 }17921793 if (request.status === ABORTING) {1794 if (1795 typeof result === 'object' &&1796 // $FlowFixMe[invalid-compare]1797 result !== null &&1798 typeof result.then === 'function' &&1799 !isClientReference(result)1800 ) {1801 result.then(voidHandler, voidHandler);1802 }1803 // If we aborted during rendering we should interrupt the render but1804 // we don't need to provide an error because the renderer will encode1805 // the abort error as the reason.1806 // eslint-disable-next-line no-throw-literal1807 throw null;1808 }18091810 if (__DEV__ || (enableProfilerTimer && enableAsyncDebugInfo)) {1811 // Forward any debug information for any Promises that we use():ed during the render.1812 // We do this at the end so that we don't keep doing this for each retry.1813 const trackedThenables = getTrackedThenablesAfterRendering();1814 if (trackedThenables !== null) {1815 const stacks: Array<Error> =1816 __DEV__ && enableAsyncDebugInfo1817 ? (trackedThenables as any)._stacks ||1818 ((trackedThenables as any)._stacks = [])1819 : (null as any);1820 for (let i = 0; i < trackedThenables.length; i++) {1821 const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null;1822 forwardDebugInfoFromThenable(1823 request,1824 task,1825 trackedThenables[i],1826 __DEV__ ? componentDebugInfo : null,1827 stack,1828 );1829 }1830 }1831 }18321833 // Apply special cases.1834 result = processServerComponentReturnValue(request, task, Component, result);18351836 if (__DEV__) {1837 // From this point on, the parent is the component we just rendered until we1838 // hit another JSX element.1839 task.debugOwner = componentDebugInfo;1840 // Unfortunately, we don't have a stack frame for this position. Conceptually1841 // it would be the location of the `return` inside component that just rendered.1842 task.debugStack = null;1843 task.debugTask = null;1844 }18451846 // Track this element's key on the Server Component on the keyPath context..1847 const prevKeyPath = task.keyPath;1848 const prevImplicitSlot = task.implicitSlot;1849 if (key !== null) {1850 // Append the key to the path. Technically a null key should really add the child1851 // index. We don't do that to hold the payload small and implementation simple.1852 if (key === REACT_OPTIMISTIC_KEY || prevKeyPath === REACT_OPTIMISTIC_KEY) {1853 // The optimistic key is viral. It turns the whole key into optimistic if any part is.1854 task.keyPath = REACT_OPTIMISTIC_KEY;1855 } else {1856 task.keyPath = prevKeyPath === null ? key : prevKeyPath + ',' + key;1857 }1858 } else if (prevKeyPath === null) {1859 // This sequence of Server Components has no keys. This means that it was rendered1860 // in a slot that needs to assign an implicit key. Even if children below have1861 // explicit keys, they should not be used for the outer most key since it might1862 // collide with other slots in that set.1863 task.implicitSlot = true;1864 }1865 const json = renderModelDestructive(request, task, emptyRoot, '', result);1866 task.keyPath = prevKeyPath;1867 task.implicitSlot = prevImplicitSlot;1868 return json;1869}18701871function warnForMissingKey(1872 request: Request,1873 key: ReactKey,1874 componentDebugInfo: ReactComponentInfo,1875 debugTask: null | ConsoleTask,1876): void {1877 if (__DEV__) {1878 let didWarnForKey = request.didWarnForKey;1879 if (didWarnForKey == null) {1880 didWarnForKey = request.didWarnForKey = new WeakSet();1881 }1882 const parentOwner = componentDebugInfo.owner;1883 if (parentOwner != null) {1884 if (didWarnForKey.has(parentOwner)) {1885 // We already warned for other children in this parent.1886 return;1887 }1888 didWarnForKey.add(parentOwner);1889 }18901891 // Call with the server component as the currently rendering component1892 // for context.1893 const logKeyError = () => {1894 console.error(1895 'Each child in a list should have a unique "key" prop.' +1896 '%s%s See https://react.dev/link/warning-keys for more information.',1897 '',1898 '',1899 );1900 };19011902 // $FlowFixMe[constant-condition]1903 if (supportsComponentStorage) {1904 // Run the component in an Async Context that tracks the current owner.1905 if (debugTask) {1906 debugTask.run(1907 // $FlowFixMe[method-unbinding]1908 componentStorage.run.bind(1909 componentStorage,1910 componentDebugInfo,1911 callComponentInDEV,1912 logKeyError,1913 null,1914 componentDebugInfo,1915 ),1916 );1917 } else {1918 componentStorage.run(1919 componentDebugInfo,1920 callComponentInDEV,1921 logKeyError,1922 null,1923 componentDebugInfo,1924 );1925 }1926 } else {1927 if (debugTask) {1928 debugTask.run(1929 callComponentInDEV.bind(null, logKeyError, null, componentDebugInfo),1930 );1931 } else {1932 callComponentInDEV(logKeyError, null, componentDebugInfo);1933 }1934 }1935 }1936}19371938function renderFragment(1939 request: Request,1940 task: Task,1941 children: $ReadOnlyArray<ReactClientValue>,1942): ReactJSONValue {1943 if (__DEV__) {1944 for (let i = 0; i < children.length; i++) {1945 const child = children[i];1946 if (1947 child !== null &&1948 typeof child === 'object' &&1949 child.$$typeof === REACT_ELEMENT_TYPE1950 ) {1951 const element: ReactElement = child as any;1952 if (element.key === null && !element._store.validated) {1953 element._store.validated = 2;1954 }1955 }1956 }1957 }19581959 if (task.keyPath !== null) {1960 // We have a Server Component that specifies a key but we're now splitting1961 // the tree using a fragment.1962 const fragment = __DEV__1963 ? [1964 REACT_ELEMENT_TYPE,1965 REACT_FRAGMENT_TYPE,1966 task.keyPath,1967 {children},1968 null,1969 null,1970 0,1971 ]1972 : [REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE, task.keyPath, {children}];1973 if (!task.implicitSlot) {1974 // If this was keyed inside a set. I.e. the outer Server Component was keyed1975 // then we need to handle reorders of the whole set. To do this we need to wrap1976 // this array in a keyed Fragment.1977 return fragment;1978 }1979 // If the outer Server Component was implicit but then an inner one had a key1980 // we don't actually need to be able to move the whole set around. It'll always be1981 // in an implicit slot. The key only exists to be able to reset the state of the1982 // children. We could achieve the same effect by passing on the keyPath to the next1983 // set of components inside the fragment. This would also allow a keyless fragment1984 // reconcile against a single child.1985 // Unfortunately because of JSON.stringify, we can't call the recursive loop for1986 // each child within this context because we can't return a set with already resolved1987 // values. E.g. a string would get double encoded. Returning would pop the context.1988 // So instead, we wrap it with an unkeyed fragment and inner keyed fragment.1989 return [fragment];1990 }1991 // Since we're yielding here, that implicitly resets the keyPath context on the1992 // way up. Which is what we want since we've consumed it. If this changes to1993 // be recursive serialization, we need to reset the keyPath and implicitSlot,1994 // before recursing here.1995 if (__DEV__) {1996 const debugInfo: ?ReactDebugInfo = (children as any)._debugInfo;1997 if (debugInfo) {1998 // If this came from Flight, forward any debug info into this new row.1999 if (!canEmitDebugInfo) {2000 // We don't have a chunk to assign debug info. We need to outline this
Findings
✓ No findings reported for this file.