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 enableFlightWeakThenables,19 enableFlightObjectReferences,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 writtenImportStrings: Map<string, string>,620 // The combined length of the keys in writtenImportStrings.621 writtenImportStringsSize: number,622 temporaryReferences: void | TemporaryReferenceSet,623 identifierPrefix: string,624 identifierCount: number,625 taintCleanupQueue: Array<string | bigint>,626 onError: (error: mixed) => ?string,627 onAllReady: () => void,628 onFatalError: mixed => void,629 // Profiling-only630 timeOrigin: number,631 abortTime: number,632 // DEV-only633 pendingDebugChunks: number,634 // See completedRegularChunks for why some entries are preceded by the635 // NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel.636 completedDebugChunks: Array<637 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,638 >,639 debugDestination: null | Destination,640 environmentName: () => string,641 filterStackFrame: (642 url: string,643 functionName: string,644 lineNumber: number,645 columnNumber: number,646 ) => boolean,647 didWarnForKey: null | WeakSet<ReactComponentInfo>,648 writtenDebugObjects: WeakMap<Reference, string>,649 deferredDebugObjects: null | DeferredDebugStore,650};651652const {653 TaintRegistryObjects,654 TaintRegistryValues,655 TaintRegistryByteLengths,656 TaintRegistryPendingRequests,657} = ReactSharedInternals;658659function throwTaintViolation(message: string) {660 // eslint-disable-next-line react-internal/prod-error-codes661 throw new Error(message);662}663664function cleanupTaintQueue(request: Request): void {665 const cleanupQueue = request.taintCleanupQueue;666 TaintRegistryPendingRequests.delete(cleanupQueue);667 for (let i = 0; i < cleanupQueue.length; i++) {668 const entryValue = cleanupQueue[i];669 const entry = TaintRegistryValues.get(entryValue);670 if (entry !== undefined) {671 if (entry.count === 1) {672 TaintRegistryValues.delete(entryValue);673 } else {674 entry.count--;675 }676 }677 }678 cleanupQueue.length = 0;679}680681function defaultErrorHandler(error: mixed) {682 console['error'](error);683 // Don't transform to our wrapper684}685686function RequestInstance(687 this: $FlowFixMe,688 type: 20 | 21,689 model: ReactClientValue,690 bundlerConfig: ClientManifest,691 onError: void | ((error: mixed) => ?string),692 onAllReady: () => void,693 onFatalError: (error: mixed) => void,694 identifierPrefix?: string,695 temporaryReferences: void | TemporaryReferenceSet,696 debugStartTime: void | number, // Profiling-only697 environmentName: void | string | (() => string), // DEV-only698 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only699 keepDebugAlive: boolean, // DEV-only700) {701 if (702 ReactSharedInternals.A !== null &&703 ReactSharedInternals.A !== DefaultAsyncDispatcher704 ) {705 throw new Error(706 'Currently React only supports one RSC renderer at a time.',707 );708 }709 ReactSharedInternals.A = DefaultAsyncDispatcher;710 if (__DEV__) {711 // Unlike Fizz or Fiber, we don't reset this and just keep it on permanently.712 // This lets it act more like the AsyncDispatcher so that we can get the713 // stack asynchronously too.714 ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;715 }716717 const abortSet: Set<Task> = new Set();718 const pingedTasks: Array<Task> = [];719 const cleanupQueue: Array<string | bigint> = [];720 if (enableTaint) {721 TaintRegistryPendingRequests.add(cleanupQueue);722 }723 const hints = createHints();724 this.type = type;725 this.status = OPENING;726 this.flushScheduled = false;727 this.fatalError = null;728 this.destination = null;729 this.bundlerConfig = bundlerConfig;730 this.cache = new Map();731 this.cacheController = new AbortController();732 this.nextChunkId = 0;733 this.pendingChunks = 0;734 this.hints = hints;735 this.abortableTasks = abortSet;736 this.pingedTasks = pingedTasks;737 this.completedImportChunks = [] as Array<Chunk>;738 this.completedHintChunks = [] as Array<Chunk>;739 this.completedRegularChunks = [] as Array<740 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,741 >;742 this.completedErrorChunks = [] as Array<Chunk>;743 this.writtenSymbols = new Map();744 this.writtenClientReferences = new Map();745 this.writtenServerReferences = new Map();746 this.writtenObjects = new WeakMap();747 this.writtenImportStrings = new Map();748 this.writtenImportStringsSize = 0;749 this.temporaryReferences = temporaryReferences;750 this.identifierPrefix = identifierPrefix || '';751 this.identifierCount = 1;752 this.taintCleanupQueue = cleanupQueue;753 this.onError = onError === undefined ? defaultErrorHandler : onError;754 this.onAllReady = onAllReady;755 this.onFatalError = onFatalError;756757 if (__DEV__) {758 this.pendingDebugChunks = 0;759 this.completedDebugChunks = [] as Array<760 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,761 >;762 this.debugDestination = null;763 this.environmentName =764 environmentName === undefined765 ? () => 'Server'766 : typeof environmentName !== 'function'767 ? () => environmentName768 : environmentName;769 this.filterStackFrame =770 filterStackFrame === undefined771 ? defaultFilterStackFrame772 : filterStackFrame;773 this.didWarnForKey = null;774 this.writtenDebugObjects = new WeakMap();775 this.deferredDebugObjects = keepDebugAlive776 ? {777 retained: new Map(),778 existing: new Map(),779 }780 : null;781 }782783 let timeOrigin: number;784 if (enableProfilerTimer) {785 // We start by serializing the time origin. Any future timestamps will be786 // emitted relatively to this origin. Instead of using performance.timeOrigin787 // as this origin, we use the timestamp at the start of the request.788 // This avoids leaking unnecessary information like how long the server has789 // been running and allows for more compact representation of each timestamp.790 // The time origin is stored as an offset in the time space of this environment.791 if (typeof debugStartTime === 'number') {792 // We expect `startTime` to be an absolute timestamp, so relativize it to match the other case.793 timeOrigin = this.timeOrigin =794 debugStartTime -795 // $FlowFixMe[prop-missing]796 performance.timeOrigin;797 } else {798 timeOrigin = this.timeOrigin = performance.now();799 }800 emitTimeOriginChunk(801 this,802 timeOrigin +803 // $FlowFixMe[prop-missing]804 performance.timeOrigin,805 );806 this.abortTime = -0.0;807 } else {808 timeOrigin = 0;809 }810811 const rootTask = createTask(812 this,813 model,814 null,815 false,816 createRootFormatContext(),817 abortSet,818 timeOrigin,819 null,820 null,821 null,822 );823 pingedTasks.push(rootTask);824}825826export function createRequest(827 model: ReactClientValue,828 bundlerConfig: ClientManifest,829 onError: void | ((error: mixed) => ?string),830 identifierPrefix: void | string,831 temporaryReferences: void | TemporaryReferenceSet,832 debugStartTime: void | number, // Profiling-only833 environmentName: void | string | (() => string), // DEV-only834 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only835 keepDebugAlive: boolean, // DEV-only836): Request {837 if (__DEV__) {838 resetOwnerStackLimit();839 }840841 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors842 return new RequestInstance(843 RENDER,844 model,845 bundlerConfig,846 onError,847 noop,848 noop,849 identifierPrefix,850 temporaryReferences,851 debugStartTime,852 environmentName,853 filterStackFrame,854 keepDebugAlive,855 );856}857858export function createPrerenderRequest(859 model: ReactClientValue,860 bundlerConfig: ClientManifest,861 onAllReady: () => void,862 onFatalError: () => void,863 onError: void | ((error: mixed) => ?string),864 identifierPrefix: void | string,865 temporaryReferences: void | TemporaryReferenceSet,866 debugStartTime: void | number, // Profiling-only867 environmentName: void | string | (() => string), // DEV-only868 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only869 keepDebugAlive: boolean, // DEV-only870): Request {871 if (__DEV__) {872 resetOwnerStackLimit();873 }874875 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors876 return new RequestInstance(877 PRERENDER,878 model,879 bundlerConfig,880 onError,881 onAllReady,882 onFatalError,883 identifierPrefix,884 temporaryReferences,885 debugStartTime,886 environmentName,887 filterStackFrame,888 keepDebugAlive,889 );890}891892let currentRequest: null | Request = null;893894export function resolveRequest(): null | Request {895 if (currentRequest) return currentRequest;896 // $FlowFixMe[constant-condition]897 if (supportsRequestStorage) {898 const store = requestStorage.getStore();899 if (store) return store;900 }901 return null;902}903904function isTypedArray(value: any): boolean {905 if (value instanceof ArrayBuffer) {906 return true;907 }908 if (value instanceof Int8Array) {909 return true;910 }911 if (value instanceof Uint8Array) {912 return true;913 }914 if (value instanceof Uint8ClampedArray) {915 return true;916 }917 if (value instanceof Int16Array) {918 return true;919 }920 if (value instanceof Uint16Array) {921 return true;922 }923 if (value instanceof Int32Array) {924 return true;925 }926 if (value instanceof Uint32Array) {927 return true;928 }929 if (value instanceof Float32Array) {930 return true;931 }932 if (value instanceof Float64Array) {933 return true;934 }935 if (value instanceof BigInt64Array) {936 return true;937 }938 if (value instanceof BigUint64Array) {939 return true;940 }941 if (value instanceof DataView) {942 return true;943 }944 return false;945}946947function serializeDebugThenable(948 request: Request,949 counter: {objectLimit: number},950 thenable: Thenable<any>,951): string {952 // Like serializeThenable but for renderDebugModel953 request.pendingDebugChunks++;954 const id = request.nextChunkId++;955 const ref = serializePromiseID(id);956 request.writtenDebugObjects.set(thenable, ref);957958 switch (thenable.status) {959 case 'fulfilled': {960 emitOutlinedDebugModelChunk(request, id, counter, thenable.value);961 return ref;962 }963 case 'rejected': {964 const x = thenable.reason;965 // We don't log these errors since they didn't actually throw into Flight.966 const digest = '';967 emitErrorChunk(request, id, digest, x, true, null);968 return ref;969 }970 }971972 if (request.status === ABORTING) {973 // Ensure that we have time to emit the halt chunk if we're sync aborting.974 emitDebugHaltChunk(request, id);975 return ref;976 }977978 const deferredDebugObjects = request.deferredDebugObjects;979 if (deferredDebugObjects !== null) {980 // For Promises that are not yet resolved, we always defer them. They are async anyway so it's981 // safe to defer them. This also ensures that we don't eagerly call .then() on a Promise that982 // otherwise wouldn't have initialized. It also ensures that we don't "handle" a rejection983 // that otherwise would have triggered unhandled rejection.984 deferredDebugObjects.retained.set(id, thenable as any);985 const deferredRef = '$Y@' + id.toString(16);986 // We can now refer to the deferred object in the future.987 request.writtenDebugObjects.set(thenable, deferredRef);988 return deferredRef;989 }990991 let cancelled = false;992993 thenable.then(994 value => {995 if (cancelled) {996 return;997 }998 cancelled = true;999 if (request.status === ABORTING) {1000 emitDebugHaltChunk(request, id);1001 enqueueFlush(request);1002 return;1003 }1004 if (1005 (isArray(value) && value.length > 200) ||1006 (isTypedArray(value) && value.byteLength > 1000)1007 ) {1008 // If this should be deferred, but we don't have a debug channel installed1009 // it would get omitted. We can't omit outlined models but we can avoid1010 // resolving the Promise at all by halting it.1011 emitDebugHaltChunk(request, id);1012 enqueueFlush(request);1013 return;1014 }1015 emitOutlinedDebugModelChunk(request, id, counter, value);1016 enqueueFlush(request);1017 },1018 reason => {1019 if (cancelled) {1020 return;1021 }1022 cancelled = true;1023 if (request.status === ABORTING) {1024 emitDebugHaltChunk(request, id);1025 enqueueFlush(request);1026 return;1027 }1028 // We don't log these errors since they didn't actually throw into Flight.1029 const digest = '';1030 emitErrorChunk(request, id, digest, reason, true, null);1031 enqueueFlush(request);1032 },1033 );10341035 // We don't use scheduleMicrotask here because it doesn't actually schedule a microtask1036 // in all our configs which is annoying.1037 Promise.resolve().then(() => {1038 // If we don't resolve the Promise within a microtask. Leave it as hanging since we1039 // don't want to block the render forever on a Promise that might never resolve.1040 if (cancelled) {1041 return;1042 }1043 cancelled = true;1044 emitDebugHaltChunk(request, id);1045 enqueueFlush(request);1046 // Clean up the request so we don't leak this forever.1047 request = null as any;1048 counter = null as any;1049 });10501051 return ref;1052}10531054function emitRequestedDebugThenable(1055 request: Request,1056 id: number,1057 counter: {objectLimit: number},1058 thenable: Thenable<any>,1059): void {1060 thenable.then(1061 value => {1062 if (request.status === ABORTING) {1063 emitDebugHaltChunk(request, id);1064 enqueueFlush(request);1065 return;1066 }1067 emitOutlinedDebugModelChunk(request, id, counter, value);1068 enqueueFlush(request);1069 },1070 reason => {1071 if (request.status === ABORTING) {1072 emitDebugHaltChunk(request, id);1073 enqueueFlush(request);1074 return;1075 }1076 // We don't log these errors since they didn't actually throw into Flight.1077 const digest = '';1078 emitErrorChunk(request, id, digest, reason, true, null);1079 enqueueFlush(request);1080 },1081 );1082}10831084function createThenableTask(1085 request: Request,1086 task: Task,1087 thenable: Thenable<any>,1088): Task {1089 return createTask(1090 request,1091 thenable as any, // will be replaced by the value before we retry. used for debug info.1092 task.keyPath, // the server component sequence continues through Promise-as-a-child.1093 task.implicitSlot,1094 task.formatContext,1095 request.abortableTasks,1096 enableProfilerTimer ? task.time : 0,1097 __DEV__ ? task.debugOwner : null,1098 __DEV__ ? task.debugStack : null,1099 __DEV__ ? task.debugTask : null,1100 );1101}11021103function serializeThenable(1104 request: Request,1105 task: Task,1106 thenable: Thenable<any>,1107): number {1108 switch (thenable.status) {1109 case 'fulfilled': {1110 const newTask = createThenableTask(request, task, thenable);1111 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);1112 // We have the resolved value, we can go ahead and schedule it for serialization.1113 newTask.model = thenable.value;1114 pingTask(request, newTask);1115 return newTask.id;1116 }1117 case 'rejected': {1118 const newTask = createThenableTask(request, task, thenable);1119 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);1120 const x = thenable.reason;1121 erroredTask(request, newTask, x);1122 return newTask.id;1123 }1124 case 'pending_weak': {1125 if (enableFlightWeakThenables) {1126 // A weak-pending thenable doesn't block the stream from closing, so1127 // we don't create a task for it yet. We only reserve an id for its1128 // reference. If it settles while the stream is still open, we1129 // create the task at that point, the same as if we had serialized1130 // an already settled thenable.1131 //1132 // Delivery is driven by the thenable's notification. If the stream1133 // closes before the listeners are notified, the value is dropped1134 // and the reference is left unfulfilled. Since the stream may close1135 // synchronously when the last task completes, a thenable that1136 // notifies its listeners synchronously (unlike a native Promise,1137 // which notifies in a microtask) is guaranteed delivery of any1138 // value it settles with before the stream closes.1139 const id = request.nextChunkId++;1140 // The parent task is mutated as serialization continues, so we1141 // snapshot the context that the new task needs if it's created1142 // later.1143 const keyPath = task.keyPath;1144 const implicitSlot = task.implicitSlot;1145 const formatContext = task.formatContext;1146 const lastTimestamp = enableProfilerTimer ? task.time : 0;1147 const debugOwner = __DEV__ ? task.debugOwner : null;1148 const debugStack = __DEV__ ? task.debugStack : null;1149 const debugTask = __DEV__ ? task.debugTask : null;1150 let settled = false;1151 thenable.then(1152 (value: any) => {1153 if (settled || request.status > OPEN) {1154 // Too late. The stream already closed (or the request was1155 // aborted), so the reference stays unfulfilled.1156 return;1157 }1158 settled = true;1159 const newTask = createTaskWithID(1160 request,1161 id,1162 value,1163 keyPath,1164 implicitSlot,1165 formatContext,1166 request.abortableTasks,1167 lastTimestamp,1168 debugOwner,1169 debugStack,1170 debugTask,1171 );1172 forwardDebugInfoFromCurrentContext(request, newTask, thenable);1173 pingTask(request, newTask);1174 },1175 (reason: mixed) => {1176 if (settled || request.status > OPEN) {1177 return;1178 }1179 settled = true;1180 const newTask = createTaskWithID(1181 request,1182 id,1183 thenable as any, // never rendered. used for debug info.1184 keyPath,1185 implicitSlot,1186 formatContext,1187 request.abortableTasks,1188 lastTimestamp,1189 debugOwner,1190 debugStack,1191 debugTask,1192 );1193 if (enableProfilerTimer) {1194 // If this is async we need to time when this task finishes.1195 newTask.timed = true;1196 }1197 erroredTask(request, newTask, reason);1198 enqueueFlush(request);1199 },1200 );1201 return id;1202 }1203 // Fallthrough1204 }1205 default: {1206 const newTask = createThenableTask(request, task, thenable);1207 if (request.status === ABORTING) {1208 // We can no longer accept any resolved values1209 request.abortableTasks.delete(newTask);1210 if (request.type === PRERENDER) {1211 haltTask(newTask, request);1212 finishHaltedTask(newTask, request);1213 } else {1214 const errorId: number = request.fatalError as any;1215 abortTask(newTask, request, errorId);1216 finishAbortedTask(newTask, request, errorId);1217 }1218 return newTask.id;1219 }1220 if (typeof thenable.status !== 'string') {1221 // Only instrument the thenable if the status if not defined. If1222 // it's defined, but an unknown value, assume it's been instrumented by1223 // some custom userspace implementation. We treat it as "pending".1224 const pendingThenable: PendingThenable<mixed> = thenable as any;1225 pendingThenable.status = 'pending';1226 pendingThenable.then(1227 fulfilledValue => {1228 if (thenable.status === 'pending') {1229 const fulfilledThenable: FulfilledThenable<mixed> =1230 thenable as any;1231 fulfilledThenable.status = 'fulfilled';1232 fulfilledThenable.value = fulfilledValue;1233 }1234 },1235 (error: mixed) => {1236 if (thenable.status === 'pending') {1237 const rejectedThenable: RejectedThenable<mixed> = thenable as any;1238 rejectedThenable.status = 'rejected';1239 rejectedThenable.reason = error;1240 }1241 },1242 );1243 }1244 thenable.then(1245 value => {1246 forwardDebugInfoFromCurrentContext(request, newTask, thenable);1247 newTask.model = value;1248 pingTask(request, newTask);1249 },1250 reason => {1251 if (newTask.status === PENDING) {1252 if (enableProfilerTimer) {1253 // If this is async we need to time when this task finishes.1254 newTask.timed = true;1255 }1256 // We expect that the only status it might be otherwise is ABORTED.1257 // When we abort we emit chunks in each pending task slot and don't need1258 // to do so again here.1259 erroredTask(request, newTask, reason);1260 enqueueFlush(request);1261 }1262 },1263 );1264 return newTask.id;1265 }1266 }1267}12681269function serializeReadableStream(1270 request: Request,1271 task: Task,1272 stream: ReadableStream,1273): string {1274 // Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the1275 // receiving side. It also implies that different chunks can be split up or merged as opposed1276 // to a readable stream that happens to have Uint8Array as the type which might expect it to be1277 // received in the same slices.1278 // $FlowFixMe[prop-missing]: This is a Node.js extension.1279 let supportsBYOB: void | boolean = stream.supportsBYOB;1280 if (supportsBYOB === undefined) {1281 try {1282 // $FlowFixMe[extra-arg]: This argument is accepted.1283 stream.getReader({mode: 'byob'}).releaseLock();1284 supportsBYOB = true;1285 } catch (x) {1286 supportsBYOB = false;1287 }1288 }1289 // At this point supportsBYOB is guaranteed to be a boolean.1290 const isByteStream: boolean = supportsBYOB;12911292 const reader = stream.getReader();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 ? task.time : 0,1303 __DEV__ ? task.debugOwner : null,1304 __DEV__ ? task.debugStack : null,1305 __DEV__ ? task.debugTask : null,1306 );13071308 // The task represents the Stop row. This adds a Start row.1309 request.pendingChunks++;1310 const startStreamRow =1311 streamTask.id.toString(16) + ':' + (isByteStream ? 'r' : 'R') + '\n';1312 request.completedRegularChunks.push(stringToChunk(startStreamRow));13131314 function progress(entry: {done: boolean, value: ReactClientValue, ...}) {1315 if (streamTask.status !== PENDING) {1316 return;1317 }13181319 if (entry.done) {1320 streamTask.status = COMPLETED;1321 const endStreamRow = streamTask.id.toString(16) + ':C\n';1322 request.completedRegularChunks.push(stringToChunk(endStreamRow));1323 request.abortableTasks.delete(streamTask);1324 request.cacheController.signal.removeEventListener('abort', abortStream);1325 enqueueFlush(request);1326 callOnAllReadyIfReady(request);1327 } else {1328 try {1329 request.pendingChunks++;1330 streamTask.model = entry.value;1331 if (isByteStream) {1332 // Chunks of byte streams are always Uint8Array instances.1333 const chunk: Uint8Array = streamTask.model as any;1334 emitTypedArrayChunk(request, streamTask.id, 'b', chunk, false);1335 } else {1336 tryStreamTask(request, streamTask);1337 }1338 enqueueFlush(request);1339 reader.read().then(progress, error);1340 } catch (x) {1341 error(x);1342 }1343 }1344 }1345 function error(reason: mixed) {1346 if (streamTask.status !== PENDING) {1347 return;1348 }1349 request.cacheController.signal.removeEventListener('abort', abortStream);1350 erroredTask(request, streamTask, reason);1351 enqueueFlush(request);13521353 // $FlowFixMe[incompatible-type] should be able to pass mixed1354 // $FlowFixMe[incompatible-use]1355 reader.cancel(reason).then(error, error);1356 }1357 function abortStream() {1358 if (streamTask.status !== PENDING) {1359 return;1360 }1361 const signal = request.cacheController.signal;1362 signal.removeEventListener('abort', abortStream);1363 const reason = signal.reason;1364 if (request.type === PRERENDER) {1365 request.abortableTasks.delete(streamTask);1366 haltTask(streamTask, request);1367 finishHaltedTask(streamTask, request);1368 } else {1369 // TODO: Make this use abortTask() instead.1370 erroredTask(request, streamTask, reason);1371 enqueueFlush(request);1372 }1373 // $FlowFixMe[incompatible-use] should be able to pass mixed1374 reader.cancel(reason).then(error, error);1375 }13761377 request.cacheController.signal.addEventListener('abort', abortStream);1378 reader.read().then(progress, error);1379 return serializeByValueID(streamTask.id);1380}13811382function serializeAsyncIterable(1383 request: Request,1384 task: Task,1385 iterable: $AsyncIterable<ReactClientValue, ReactClientValue, void>,1386 iterator: $AsyncIterator<ReactClientValue, ReactClientValue, void>,1387): string {1388 // Generators/Iterators are Iterables but they're also their own iterator1389 // functions. If that's the case, we treat them as single-shot. Otherwise,1390 // we assume that this iterable might be a multi-shot and allow it to be1391 // iterated more than once on the client.1392 const isIterator = iterable === iterator;13931394 // This task won't actually be retried. We just use it to attempt synchronous renders.1395 const streamTask = createTask(1396 request,1397 task.model,1398 task.keyPath,1399 task.implicitSlot,1400 task.formatContext,1401 request.abortableTasks,1402 enableProfilerTimer ? task.time : 0,1403 __DEV__ ? task.debugOwner : null,1404 __DEV__ ? task.debugStack : null,1405 __DEV__ ? task.debugTask : null,1406 );14071408 if (__DEV__) {1409 const debugInfo: ?ReactDebugInfo = (iterable as any)._debugInfo;1410 if (debugInfo) {1411 forwardDebugInfo(request, streamTask, debugInfo);1412 }1413 }14141415 // The task represents the Stop row. This adds a Start row.1416 request.pendingChunks++;1417 const startStreamRow =1418 streamTask.id.toString(16) + ':' + (isIterator ? 'x' : 'X') + '\n';1419 request.completedRegularChunks.push(stringToChunk(startStreamRow));14201421 function progress(1422 entry:1423 | {done: false, +value: ReactClientValue, ...}1424 | {done: true, +value: ReactClientValue, ...},1425 ) {1426 if (streamTask.status !== PENDING) {1427 return;1428 }14291430 if (entry.done) {1431 streamTask.status = COMPLETED;1432 let endStreamRow;1433 if (entry.value === undefined) {1434 endStreamRow = streamTask.id.toString(16) + ':C\n';1435 } else {1436 // Unlike streams, the last value may not be undefined. If it's not1437 // we outline it and encode a reference to it in the closing instruction.1438 try {1439 const chunkId = outlineModel(request, entry.value);1440 endStreamRow =1441 streamTask.id.toString(16) +1442 ':C' +1443 stringify(serializeByValueID(chunkId)) +1444 '\n';1445 } catch (x) {1446 error(x);1447 return;1448 }1449 }1450 request.completedRegularChunks.push(stringToChunk(endStreamRow));1451 request.abortableTasks.delete(streamTask);1452 request.cacheController.signal.removeEventListener(1453 'abort',1454 abortIterable,1455 );1456 enqueueFlush(request);1457 callOnAllReadyIfReady(request);1458 } else {1459 try {1460 streamTask.model = entry.value;1461 request.pendingChunks++;1462 tryStreamTask(request, streamTask);1463 enqueueFlush(request);1464 if (__DEV__) {1465 callIteratorInDEV(iterator, progress, error);1466 } else {1467 iterator.next().then(progress, error);1468 }1469 } catch (x) {1470 error(x);1471 return;1472 }1473 }1474 }1475 function error(reason: mixed) {1476 if (streamTask.status !== PENDING) {1477 return;1478 }1479 request.cacheController.signal.removeEventListener('abort', abortIterable);1480 erroredTask(request, streamTask, reason);1481 enqueueFlush(request);1482 if (typeof (iterator as any).throw === 'function') {1483 // The iterator protocol doesn't necessarily include this but a generator do.1484 // $FlowFixMe[prop-missing] should be able to pass mixed1485 iterator.throw(reason).then(noop, noop);1486 }1487 }1488 function abortIterable() {1489 if (streamTask.status !== PENDING) {1490 return;1491 }1492 const signal = request.cacheController.signal;1493 signal.removeEventListener('abort', abortIterable);1494 const reason = signal.reason;1495 if (request.type === PRERENDER) {1496 request.abortableTasks.delete(streamTask);1497 haltTask(streamTask, request);1498 finishHaltedTask(streamTask, request);1499 } else {1500 // TODO: Make this use abortTask() instead.1501 erroredTask(request, streamTask, signal.reason);1502 enqueueFlush(request);1503 }1504 if (typeof (iterator as any).throw === 'function') {1505 // TODO: Premature exits should call return() on the iterator if it exists1506 // to allow cleanup. See https://tc39.es/ecma262/multipage/control-abstraction-objects.html#table-async-iterator-optional1507 // The iterator protocol doesn't necessarily include this but a generator do.1508 // $FlowFixMe[prop-missing] should be able to pass mixed1509 iterator.throw(reason).then(noop, noop);1510 }1511 }1512 request.cacheController.signal.addEventListener('abort', abortIterable);1513 if (__DEV__) {1514 callIteratorInDEV(iterator, progress, error);1515 } else {1516 iterator.next().then(progress, error);1517 }1518 return serializeByValueID(streamTask.id);1519}15201521export function emitHint<Code: HintCode>(1522 request: Request,1523 code: Code,1524 model: HintModel<Code>,1525): void {1526 emitHintChunk(request, code, model);1527 enqueueFlush(request);1528}15291530export function getHints(request: Request): Hints {1531 return request.hints;1532}15331534export function getCache(request: Request): Map<Function, mixed> {1535 return request.cache;1536}15371538function readThenable<T>(thenable: Thenable<T>): T {1539 if (thenable.status === 'fulfilled') {1540 return thenable.value;1541 } else if (thenable.status === 'rejected') {1542 throw thenable.reason;1543 }1544 throw thenable;1545}15461547function createLazyWrapperAroundWakeable(1548 request: Request,1549 task: Task,1550 wakeable: Wakeable,1551) {1552 // This is a temporary fork of the `use` implementation until we accept1553 // promises everywhere.1554 const thenable: Thenable<mixed> = wakeable as any;1555 switch (thenable.status) {1556 case 'fulfilled': {1557 forwardDebugInfoFromThenable(request, task, thenable, null, null);1558 return thenable.value;1559 }1560 case 'rejected':1561 forwardDebugInfoFromThenable(request, task, thenable, null, null);1562 break;1563 default: {1564 if (typeof thenable.status === 'string') {1565 // Only instrument the thenable if the status if not defined. If1566 // it's defined, but an unknown value, assume it's been instrumented by1567 // some custom userspace implementation. We treat it as "pending".1568 break;1569 }1570 const pendingThenable: PendingThenable<mixed> = thenable as any;1571 pendingThenable.status = 'pending';1572 pendingThenable.then(1573 fulfilledValue => {1574 forwardDebugInfoFromCurrentContext(request, task, thenable);1575 if (thenable.status === 'pending') {1576 const fulfilledThenable: FulfilledThenable<mixed> = thenable as any;1577 fulfilledThenable.status = 'fulfilled';1578 fulfilledThenable.value = fulfilledValue;1579 }1580 },1581 (error: mixed) => {1582 forwardDebugInfoFromCurrentContext(request, task, thenable);1583 if (thenable.status === 'pending') {1584 const rejectedThenable: RejectedThenable<mixed> = thenable as any;1585 rejectedThenable.status = 'rejected';1586 rejectedThenable.reason = error;1587 }1588 },1589 );1590 break;1591 }1592 }1593 const lazyType: LazyComponent<any, Thenable<any>> = {1594 $$typeof: REACT_LAZY_TYPE,1595 _payload: thenable,1596 _init: readThenable,1597 };1598 return lazyType;1599}16001601function callWithDebugContextInDEV<A, T>(1602 request: Request,1603 task: Task,1604 callback: A => T,1605 arg: A,1606): T {1607 // We don't have a Server Component instance associated with this callback and1608 // the nearest context is likely a Client Component being serialized. We create1609 // a fake owner during this callback so we can get the stack trace from it.1610 // This also gets sent to the client as the owner for the replaying log.1611 const componentDebugInfo: ReactComponentInfo = {1612 name: '',1613 env: task.environmentName,1614 key: null,1615 owner: task.debugOwner,1616 };1617 // $FlowFixMe[cannot-write]1618 componentDebugInfo.stack =1619 task.debugStack === null1620 ? null1621 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));1622 // $FlowFixMe[cannot-write]1623 componentDebugInfo.debugStack = task.debugStack;1624 // $FlowFixMe[cannot-write]1625 componentDebugInfo.debugTask = task.debugTask;1626 const debugTask = task.debugTask;1627 // We don't need the async component storage context here so we only set the1628 // synchronous tracking of owner.1629 setCurrentOwner(componentDebugInfo);1630 try {1631 if (debugTask) {1632 return debugTask.run(callback.bind(null, arg));1633 }1634 return callback(arg);1635 } finally {1636 setCurrentOwner(null);1637 }1638}16391640const voidHandler = () => {};16411642function processServerComponentReturnValue(1643 request: Request,1644 task: Task,1645 Component: any,1646 result: any,1647): any {1648 // A Server Component's return value has a few special properties due to being1649 // in the return position of a Component. We convert them here.1650 if (1651 typeof result !== 'object' ||1652 result === null ||1653 isClientReference(result)1654 ) {1655 return result;1656 }16571658 if (typeof result.then === 'function') {1659 // When the return value is in children position we can resolve it immediately,1660 // to its value without a wrapper if it's synchronously available.1661 const thenable: Thenable<any> = result;1662 if (__DEV__) {1663 // If the thenable resolves to an element, then it was in a static position,1664 // the return value of a Server Component. That doesn't need further validation1665 // of keys. The Server Component itself would have had a key.1666 thenable.then(resolvedValue => {1667 if (1668 typeof resolvedValue === 'object' &&1669 resolvedValue !== null &&1670 resolvedValue.$$typeof === REACT_ELEMENT_TYPE1671 ) {1672 resolvedValue._store.validated = 1;1673 }1674 }, voidHandler);1675 }1676 // TODO: Once we accept Promises as children on the client, we can just return1677 // the thenable here.1678 return createLazyWrapperAroundWakeable(request, task, result);1679 }16801681 if (__DEV__) {1682 if ((result as any).$$typeof === REACT_ELEMENT_TYPE) {1683 // If the server component renders to an element, then it was in a static position.1684 // That doesn't need further validation of keys. The Server Component itself would1685 // have had a key.1686 (result as any)._store.validated = 1;1687 }1688 }16891690 // Normally we'd serialize an Iterator/AsyncIterator as a single-shot which is not compatible1691 // to be rendered as a React Child. However, because we have the function to recreate1692 // an iterable from rendering the element again, we can effectively treat it as multi-1693 // shot. Therefore we treat this as an Iterable/AsyncIterable, whether it was one or not, by1694 // adding a wrapper so that this component effectively renders down to an AsyncIterable.1695 const iteratorFn = getIteratorFn(result);1696 if (iteratorFn) {1697 const iterableChild = result;1698 const multiShot = {1699 [Symbol.iterator]: function () {1700 const iterator = iteratorFn.call(iterableChild);1701 if (__DEV__) {1702 // If this was an Iterator but not a GeneratorFunction we warn because1703 // it might have been a mistake. Technically you can make this mistake with1704 // GeneratorFunctions and even single-shot Iterables too but it's extra1705 // tempting to try to return the value from a generator.1706 if (iterator === iterableChild) {1707 const isGeneratorComponent =1708 // $FlowFixMe[method-unbinding]1709 Object.prototype.toString.call(Component) ===1710 '[object GeneratorFunction]' &&1711 // $FlowFixMe[method-unbinding]1712 Object.prototype.toString.call(iterableChild) ===1713 '[object Generator]';1714 if (!isGeneratorComponent) {1715 callWithDebugContextInDEV(request, task, () => {1716 console.error(1717 'Returning an Iterator from a Server Component is not supported ' +1718 'since it cannot be looped over more than once. ',1719 );1720 });1721 }1722 }1723 }1724 return iterator as any;1725 },1726 };1727 if (__DEV__) {1728 (multiShot as any)._debugInfo = iterableChild._debugInfo;1729 }1730 return multiShot;1731 }1732 if (1733 typeof (result as any)[ASYNC_ITERATOR] === 'function' &&1734 (typeof ReadableStream !== 'function' ||1735 !(result instanceof ReadableStream))1736 ) {1737 const iterableChild = result;1738 const multishot = {1739 [ASYNC_ITERATOR]: function () {1740 const iterator = (iterableChild as any)[ASYNC_ITERATOR]();1741 if (__DEV__) {1742 // If this was an AsyncIterator but not an AsyncGeneratorFunction we warn because1743 // it might have been a mistake. Technically you can make this mistake with1744 // AsyncGeneratorFunctions and even single-shot AsyncIterables too but it's extra1745 // tempting to try to return the value from a generator.1746 if (iterator === iterableChild) {1747 const isGeneratorComponent =1748 // $FlowFixMe[method-unbinding]1749 Object.prototype.toString.call(Component) ===1750 '[object AsyncGeneratorFunction]' &&1751 // $FlowFixMe[method-unbinding]1752 Object.prototype.toString.call(iterableChild) ===1753 '[object AsyncGenerator]';1754 if (!isGeneratorComponent) {1755 callWithDebugContextInDEV(request, task, () => {1756 console.error(1757 'Returning an AsyncIterator from a Server Component is not supported ' +1758 'since it cannot be looped over more than once. ',1759 );1760 });1761 }1762 }1763 }1764 return iterator;1765 },1766 };1767 if (__DEV__) {1768 (multishot as any)._debugInfo = iterableChild._debugInfo;1769 }1770 return multishot;1771 }1772 return result;1773}17741775function renderFunctionComponent<Props>(1776 request: Request,1777 task: Task,1778 key: ReactKey,1779 Component: (p: Props, arg: void) => any,1780 props: Props,1781 validated: number, // DEV-only1782): ReactJSONValue {1783 // Reset the task's thenable state before continuing, so that if a later1784 // component suspends we can reuse the same task object. If the same1785 // component suspends again, the thenable state will be restored.1786 const prevThenableState = task.thenableState;1787 task.thenableState = null;17881789 let result;17901791 let componentDebugInfo: ReactComponentInfo;1792 if (__DEV__) {1793 if (!canEmitDebugInfo) {1794 // We don't have a chunk to assign debug info. We need to outline this1795 // component to assign it an ID.1796 return outlineTask(request, task);1797 } else if (prevThenableState !== null) {1798 // This is a replay and we've already emitted the debug info of this component1799 // in the first pass. We skip emitting a duplicate line.1800 // As a hack we stashed the previous component debug info on this object in DEV.1801 componentDebugInfo = (prevThenableState as any)._componentDebugInfo;1802 } else {1803 // This is a new component in the same task so we can emit more debug info.1804 const componentDebugID = task.id;1805 const componentName =1806 (Component as any).displayName || Component.name || '';1807 const componentEnv = (0, request.environmentName)();1808 request.pendingChunks++;1809 componentDebugInfo = {1810 name: componentName,1811 env: componentEnv,1812 key: key,1813 owner: task.debugOwner,1814 } as ReactComponentInfo;1815 // $FlowFixMe[cannot-write]1816 componentDebugInfo.stack =1817 task.debugStack === null1818 ? null1819 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));1820 // $FlowFixMe[cannot-write]1821 componentDebugInfo.props = props;1822 // $FlowFixMe[cannot-write]1823 componentDebugInfo.debugStack = task.debugStack;1824 // $FlowFixMe[cannot-write]1825 componentDebugInfo.debugTask = task.debugTask;18261827 // We outline this model eagerly so that we can refer to by reference as an owner.1828 // If we had a smarter way to dedupe we might not have to do this if there ends up1829 // being no references to this as an owner.18301831 outlineComponentInfo(request, componentDebugInfo);18321833 // Track when we started rendering this component.1834 if (enableProfilerTimer) {1835 advanceTaskTime(request, task, performance.now());1836 }18371838 emitDebugChunk(request, componentDebugID, componentDebugInfo);18391840 // We've emitted the latest environment for this task so we track that.1841 task.environmentName = componentEnv;18421843 if (validated === 2) {1844 warnForMissingKey(request, key, componentDebugInfo, task.debugTask);1845 }1846 }1847 prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);1848 // $FlowFixMe[constant-condition]1849 if (supportsComponentStorage) {1850 // Run the component in an Async Context that tracks the current owner.1851 if (task.debugTask) {1852 result = task.debugTask.run(1853 // $FlowFixMe[method-unbinding]1854 componentStorage.run.bind(1855 componentStorage,1856 componentDebugInfo,1857 callComponentInDEV,1858 Component,1859 props,1860 componentDebugInfo,1861 ),1862 );1863 } else {1864 result = componentStorage.run(1865 componentDebugInfo,1866 callComponentInDEV,1867 Component,1868 props,1869 componentDebugInfo,1870 );1871 }1872 } else {1873 if (task.debugTask) {1874 result = task.debugTask.run(1875 callComponentInDEV.bind(null, Component, props, componentDebugInfo),1876 );1877 } else {1878 result = callComponentInDEV(Component, props, componentDebugInfo);1879 }1880 }1881 } else {1882 componentDebugInfo = null as any;1883 prepareToUseHooksForComponent(prevThenableState, null);1884 // The secondArg is always undefined in Server Components since refs error early.1885 const secondArg = undefined;1886 result = Component(props, secondArg);1887 }18881889 if (request.status === ABORTING) {1890 if (1891 typeof result === 'object' &&1892 // $FlowFixMe[invalid-compare]1893 result !== null &&1894 typeof result.then === 'function' &&1895 !isClientReference(result)1896 ) {1897 result.then(voidHandler, voidHandler);1898 }1899 // If we aborted during rendering we should interrupt the render but1900 // we don't need to provide an error because the renderer will encode1901 // the abort error as the reason.1902 // eslint-disable-next-line no-throw-literal1903 throw null;1904 }19051906 if (__DEV__ || enableProfilerTimer) {1907 // Forward any debug information for any Promises that we use():ed during the render.1908 // We do this at the end so that we don't keep doing this for each retry.1909 const trackedThenables = getTrackedThenablesAfterRendering();1910 if (trackedThenables !== null) {1911 const stacks: Array<Error> = __DEV__1912 ? (trackedThenables as any)._stacks ||1913 ((trackedThenables as any)._stacks = [])1914 : (null as any);1915 for (let i = 0; i < trackedThenables.length; i++) {1916 const stack = __DEV__ ? stacks[i] : null;1917 forwardDebugInfoFromThenable(1918 request,1919 task,1920 trackedThenables[i],1921 __DEV__ ? componentDebugInfo : null,1922 stack,1923 );1924 }1925 }1926 }19271928 // Apply special cases.1929 result = processServerComponentReturnValue(request, task, Component, result);19301931 if (__DEV__) {1932 // From this point on, the parent is the component we just rendered until we1933 // hit another JSX element.1934 task.debugOwner = componentDebugInfo;1935 // Unfortunately, we don't have a stack frame for this position. Conceptually1936 // it would be the location of the `return` inside component that just rendered.1937 task.debugStack = null;1938 task.debugTask = null;1939 }19401941 // Track this element's key on the Server Component on the keyPath context..1942 const prevKeyPath = task.keyPath;1943 const prevImplicitSlot = task.implicitSlot;1944 if (key !== null) {1945 // Append the key to the path. Technically a null key should really add the child1946 // index. We don't do that to hold the payload small and implementation simple.1947 if (key === REACT_OPTIMISTIC_KEY || prevKeyPath === REACT_OPTIMISTIC_KEY) {1948 // The optimistic key is viral. It turns the whole key into optimistic if any part is.1949 task.keyPath = REACT_OPTIMISTIC_KEY;1950 } else {1951 task.keyPath = prevKeyPath === null ? key : prevKeyPath + ',' + key;1952 }1953 } else if (prevKeyPath === null) {1954 // This sequence of Server Components has no keys. This means that it was rendered1955 // in a slot that needs to assign an implicit key. Even if children below have1956 // explicit keys, they should not be used for the outer most key since it might1957 // collide with other slots in that set.1958 task.implicitSlot = true;1959 }1960 const json = renderModelDestructive(request, task, emptyRoot, '', result);1961 task.keyPath = prevKeyPath;1962 task.implicitSlot = prevImplicitSlot;1963 return json;1964}19651966function warnForMissingKey(1967 request: Request,1968 key: ReactKey,1969 componentDebugInfo: ReactComponentInfo,1970 debugTask: null | ConsoleTask,1971): void {1972 if (__DEV__) {1973 let didWarnForKey = request.didWarnForKey;1974 if (didWarnForKey == null) {1975 didWarnForKey = request.didWarnForKey = new WeakSet();1976 }1977 const parentOwner = componentDebugInfo.owner;1978 if (parentOwner != null) {1979 if (didWarnForKey.has(parentOwner)) {1980 // We already warned for other children in this parent.1981 return;1982 }1983 didWarnForKey.add(parentOwner);1984 }19851986 // Call with the server component as the currently rendering component1987 // for context.1988 const logKeyError = () => {1989 console.error(1990 'Each child in a list should have a unique "key" prop.' +1991 '%s%s See https://react.dev/link/warning-keys for more information.',1992 '',1993 '',1994 );1995 };19961997 // $FlowFixMe[constant-condition]1998 if (supportsComponentStorage) {1999 // Run the component in an Async Context that tracks the current owner.2000 if (debugTask) {
Findings
✓ No findings reported for this file.