1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {11 JSONValue,12 Thenable,13 ReactDebugInfo,14 ReactDebugInfoEntry,15 ReactComponentInfo,16 ReactAsyncInfo,17 ReactIOInfo,18 ReactStackTrace,19 ReactFunctionLocation,20 ReactErrorInfoDev,21} from 'shared/ReactTypes';22import type {LazyComponent} from 'react/src/ReactLazy';2324import type {25 ClientReference,26 ClientReferenceMetadata,27 ServerConsumerModuleMap,28 ServerManifest,29 StringDecoder,30 ModuleLoading,31} from './ReactFlightClientConfig';3233import type {34 HintCode,35 HintModel,36} from 'react-server/src/ReactFlightServerConfig';3738import type {39 CallServerCallback,40 EncodeFormActionCallback,41} from './ReactFlightReplyClient';4243import type {TemporaryReferenceSet} from './ReactFlightTemporaryReferences';4445import {46 enableProfilerTimer,47 enableComponentPerformanceTrack,48 enableFlightWeakThenables,49 enableFlightObjectReferences,50} from 'shared/ReactFeatureFlags';5152import {53 resolveClientReference,54 resolveServerReference,55 preloadModule,56 requireModule,57 getModuleDebugInfo,58 dispatchHint,59 readPartialStringChunk,60 readFinalStringChunk,61 createStringDecoder,62 prepareDestinationForModule,63 bindToConsole,64 rendererVersion,65 rendererPackageName,66 checkEvalAvailabilityOnceDev,67} from './ReactFlightClientConfig';6869import {70 createBoundServerReference,71 registerBoundServerReference,72 createServerObjectReference,73} from './ReactFlightReplyClient';7475import {readTemporaryReference} from './ReactFlightTemporaryReferences';7677import {78 markAllTracksInOrder,79 logComponentRender,80 logDedupedComponentRender,81 logComponentAborted,82 logComponentErrored,83 logIOInfo,84 logIOInfoErrored,85 logComponentAwait,86 logComponentAwaitAborted,87 logComponentAwaitErrored,88} from './ReactFlightPerformanceTrack';8990import {91 REACT_LAZY_TYPE,92 REACT_ELEMENT_TYPE,93 ASYNC_ITERATOR,94 REACT_FRAGMENT_TYPE,95} from 'shared/ReactSymbols';9697import getComponentNameFromType from 'shared/getComponentNameFromType';9899import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';100101import hasOwnProperty from 'shared/hasOwnProperty';102103import getPrototypeOf from 'shared/getPrototypeOf';104105import {injectInternals} from './ReactFlightClientDevToolsHook';106107import {OMITTED_PROP_ERROR} from 'shared/ReactFlightPropertyAccess';108109import ReactVersion from 'shared/ReactVersion';110111import isArray from 'shared/isArray';112113import * as React from 'react';114115import type {SharedStateServer} from 'react/src/ReactSharedInternalsServer';116import type {SharedStateClient} from 'react/src/ReactSharedInternalsClient';117118// TODO: This is an unfortunate hack. We shouldn't feature detect the internals119// like this. It's just that for now we support the same build of the Flight120// client both in the RSC environment, in the SSR environments as well as the121// browser client. We should probably have a separate RSC build. This is DEV122// only though.123const ReactSharedInteralsServer: void | SharedStateServer = (React as any)124 .__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;125const ReactSharedInternals: SharedStateServer | SharedStateClient =126 React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ||127 ReactSharedInteralsServer;128129export type {CallServerCallback, EncodeFormActionCallback};130131interface FlightStreamController {132 enqueueValue(value: any): void;133 enqueueModel(json: UninitializedModel): void;134 close(json: UninitializedModel): void;135 error(error: Error): void;136}137138type UninitializedModel = string;139140type ProfilingResult = {141 track: number,142 endTime: number,143 component: null | ReactComponentInfo,144};145146const ROW_ID = 0;147const ROW_TAG = 1;148const ROW_LENGTH = 2;149const ROW_CHUNK_BY_NEWLINE = 3;150const ROW_CHUNK_BY_LENGTH = 4;151152type RowParserState = 0 | 1 | 2 | 3 | 4;153154const PENDING = 'pending';155// A weak Promise reference. Behaves like PENDING except that when the stream156// closes it transitions to HALTED instead of erroring, because the server157// may intentionally never emit it. Only used when enableFlightWeakThenables158// is on.159const PENDING_WEAK = 'pending_weak';160const BLOCKED = 'blocked';161const RESOLVED_MODEL = 'resolved_model';162const RESOLVED_MODULE = 'resolved_module';163const INITIALIZED = 'fulfilled';164const ERRORED = 'rejected';165// Means it never resolves, even when the connection closes. The shared166// terminal state of a weak chunk that didn't settle before close, of any167// pending chunk at close when partial streams are allowed, and of DEV-only168// debug halts.169const HALTED = 'halted';170171const __PROTO__ = '__proto__';172173const ObjectPrototype = Object.prototype;174const ArrayPrototype = Array.prototype;175176type RESPONSE_SYMBOL_TYPE = 'RESPONSE_SYMBOL'; // Fake symbol type.177const RESPONSE_SYMBOL: RESPONSE_SYMBOL_TYPE = Symbol() as any;178179type PendingChunk<T> = {180 status: 'pending',181 value: null | Array<InitializationReference | (T => mixed)>,182 reason: null | Array<InitializationReference | (mixed => mixed)>,183 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only184 _debugChunk: null | SomeChunk<ReactDebugInfoEntry>, // DEV-only185 _debugInfo: ReactDebugInfo, // DEV-only186 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,187};188type PendingWeakChunk<T> = {189 status: 'pending_weak',190 value: null | Array<InitializationReference | (T => mixed)>,191 reason: null | Array<InitializationReference | (mixed => mixed)>,192 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only193 _debugChunk: null | SomeChunk<ReactDebugInfoEntry>, // DEV-only194 _debugInfo: ReactDebugInfo, // DEV-only195 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,196};197type BlockedChunk<T> = {198 status: 'blocked',199 value: null | Array<InitializationReference | (T => mixed)>,200 reason: null | Array<InitializationReference | (mixed => mixed)>,201 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only202 _debugChunk: null, // DEV-only203 _debugInfo: ReactDebugInfo, // DEV-only204 _receivedDebugInfo: null | Set<ReactDebugInfoEntry>, // DEV-only205 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,206};207type ResolvedModelChunk<T> = {208 status: 'resolved_model',209 value: UninitializedModel,210 reason: {[RESPONSE_SYMBOL_TYPE]: Response},211 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only212 _debugChunk: null | SomeChunk<ReactDebugInfoEntry>, // DEV-only213 _debugInfo: ReactDebugInfo, // DEV-only214 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,215};216type ResolvedModuleChunk<T> = {217 status: 'resolved_module',218 value: ClientReference<T>,219 reason: null,220 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only221 _debugChunk: null, // DEV-only222 _debugInfo: ReactDebugInfo, // DEV-only223 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,224};225type InitializedChunk<T> = {226 status: 'fulfilled',227 value: T,228 reason: null | FlightStreamController,229 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only230 _debugChunk: null, // DEV-only231 _debugInfo: ReactDebugInfo, // DEV-only232 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,233};234type InitializedStreamChunk<235 T: ReadableStream | $AsyncIterable<any, any, void>,236> = {237 status: 'fulfilled',238 value: T,239 reason: FlightStreamController,240 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only241 _debugChunk: null, // DEV-only242 _debugInfo: ReactDebugInfo, // DEV-only243 then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,244};245type ErroredChunk<T> = {246 status: 'rejected',247 value: null,248 reason: mixed,249 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only250 _debugChunk: null, // DEV-only251 _debugInfo: ReactDebugInfo, // DEV-only252 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,253};254type HaltedChunk<T> = {255 status: 'halted',256 value: null,257 reason: null,258 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only259 _debugChunk: null, // DEV-only260 _debugInfo: ReactDebugInfo, // DEV-only261 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,262};263type SomeChunk<T> =264 | PendingChunk<T>265 | PendingWeakChunk<T>266 | BlockedChunk<T>267 | ResolvedModelChunk<T>268 | ResolvedModuleChunk<T>269 | InitializedChunk<T>270 | ErroredChunk<T>271 | HaltedChunk<T>;272273// $FlowFixMe[missing-this-annot]274function ReactPromise(status: any, value: any, reason: any) {275 this.status = status;276 this.value = value;277 this.reason = reason;278 if (enableProfilerTimer && enableComponentPerformanceTrack) {279 this._children = [];280 }281 if (__DEV__) {282 this._debugChunk = null;283 this._debugInfo = [];284 this._receivedDebugInfo = null;285 }286}287// We subclass Promise.prototype so that we get other methods like .catch288ReactPromise.prototype = Object.create(Promise.prototype) as any;289// TODO: This doesn't return a new Promise chain unlike the real .then290function reactPromiseThen<T>(291 this: SomeChunk<T>,292 resolve: (value: T) => mixed,293 reject?: (reason: mixed) => mixed,294) {295 const chunk: SomeChunk<T> = this;296 // If we have resolved content, we try to initialize it first which297 // might put us back into one of the other states.298 switch (chunk.status) {299 case RESOLVED_MODEL:300 initializeModelChunk(chunk);301 break;302 case RESOLVED_MODULE:303 initializeModuleChunk(chunk);304 break;305 }306 if (__DEV__) {307 // Because only native Promises get picked up when we're awaiting we need to wrap308 // this in a native Promise in DEV. This means that these callbacks are no longer sync309 // but the lazy initialization is still sync and the .value can be inspected after,310 // allowing it to be read synchronously anyway.311 const resolveCallback = resolve;312 const rejectCallback = reject;313 const wrapperPromise: Promise<T> = new Promise((res, rej) => {314 resolve = value => {315 // $FlowFixMe[prop-missing]316 wrapperPromise._debugInfo = this._debugInfo;317 res(value);318 };319 reject = reason => {320 // $FlowFixMe[prop-missing]321 wrapperPromise._debugInfo = this._debugInfo;322 rej(reason);323 };324 });325 wrapperPromise.then(resolveCallback, rejectCallback);326 }327 // The status might have changed after initialization.328 switch (chunk.status) {329 case INITIALIZED:330 if (typeof resolve === 'function') {331 resolve(chunk.value);332 }333 break;334 case PENDING:335 case PENDING_WEAK:336 case BLOCKED:337 if (typeof resolve === 'function') {338 if (chunk.value === null) {339 chunk.value = [] as Array<InitializationReference | (T => mixed)>;340 }341 chunk.value.push(resolve);342 }343 if (typeof reject === 'function') {344 if (chunk.reason === null) {345 chunk.reason = [] as Array<346 InitializationReference | (mixed => mixed),347 >;348 }349 chunk.reason.push(reject);350 }351 break;352 case HALTED: {353 break;354 }355 default:356 if (typeof reject === 'function') {357 reject(chunk.reason);358 }359 break;360 }361}362// The shadowing `then` must be defined with `Object.defineProperty` instead of363// assignment. Assignment would throw when `Promise.prototype` is frozen (e.g.364// by SES lockdown) because assigning over an inherited non-writable property365// is rejected.366Object.defineProperty(ReactPromise.prototype, 'then', {367 writable: true,368 enumerable: true,369 configurable: true,370 value: reactPromiseThen,371});372373export type FindSourceMapURLCallback = (374 fileName: string,375 environmentName: string,376) => null | string;377378export type DebugChannelCallback = (message: string) => void;379380export type DebugChannel = {381 hasReadable: boolean,382 callback: DebugChannelCallback | null,383};384385type Response = {386 _bundlerConfig: ServerConsumerModuleMap,387 _serverReferenceConfig: null | ServerManifest,388 _moduleLoading: ModuleLoading,389 _callServer: CallServerCallback,390 _encodeFormAction: void | EncodeFormActionCallback,391 _nonce: ?string,392 _chunks: Map<number, SomeChunk<any>>,393 _stringDecoder: StringDecoder,394 _closed: boolean,395 _closedReason: mixed,396 _allowPartialStream: boolean,397 _tempRefs: void | TemporaryReferenceSet, // the set temporary references can be resolved from398 _timeOrigin: number, // Profiling-only399 _pendingInitialRender: null | TimeoutID, // Profiling-only,400 _pendingChunks: number, // DEV-only401 _weakResponse: WeakResponse, // DEV-only402 _debugRootOwner?: null | ReactComponentInfo, // DEV-only403 _debugRootStack?: null | Error, // DEV-only404 _debugRootTask?: null | ConsoleTask, // DEV-only405 _debugStartTime: number, // DEV-only406 _debugEndTime: null | number, // DEV-only407 _debugIOStarted: boolean, // DEV-only408 _debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only409 _debugChannel?: void | DebugChannel, // DEV-only410 _blockedConsole?: null | SomeChunk<ConsoleEntry>, // DEV-only411 _replayConsole: boolean, // DEV-only412 _rootEnvironmentName: string, // DEV-only, the requested environment name.413};414415// This indirection exists only to clean up DebugChannel when all Lazy References are GC:ed.416// Therefore we only use the indirection in DEV.417type WeakResponse = {418 weak: WeakRef<Response>,419 response: null | Response, // This is null when there are no pending chunks.420};421422export type {WeakResponse as Response};423424function hasGCedResponse(weakResponse: WeakResponse): boolean {425 return __DEV__ && weakResponse.weak.deref() === undefined;426}427428function unwrapWeakResponse(weakResponse: WeakResponse): Response {429 if (__DEV__) {430 const response = weakResponse.weak.deref();431 if (response === undefined) {432 // eslint-disable-next-line react-internal/prod-error-codes433 throw new Error(434 'We did not expect to receive new data after GC:ing the response.',435 );436 }437 return response;438 } else {439 return weakResponse as any; // In prod we just use the real Response directly.440 }441}442443function getWeakResponse(response: Response): WeakResponse {444 if (__DEV__) {445 return response._weakResponse;446 } else {447 return response as any; // In prod we just use the real Response directly.448 }449}450451function closeDebugChannel(debugChannel: DebugChannel): void {452 if (debugChannel.callback) {453 debugChannel.callback('');454 }455}456457// If FinalizationRegistry doesn't exist, we cannot use the debugChannel.458const debugChannelRegistry =459 __DEV__ && typeof FinalizationRegistry === 'function'460 ? new FinalizationRegistry(closeDebugChannel)461 : null;462463function readChunk<T>(chunk: SomeChunk<T>): T {464 // If we have resolved content, we try to initialize it first which465 // might put us back into one of the other states.466 switch (chunk.status) {467 case RESOLVED_MODEL:468 initializeModelChunk(chunk);469 break;470 case RESOLVED_MODULE:471 initializeModuleChunk(chunk);472 break;473 }474 // The status might have changed after initialization.475 switch (chunk.status) {476 case INITIALIZED:477 return chunk.value;478 case PENDING:479 case PENDING_WEAK:480 case BLOCKED:481 case HALTED:482 // eslint-disable-next-line no-throw-literal483 throw chunk as any as Thenable<T>;484 default:485 throw chunk.reason;486 }487}488489export function getRoot<T>(weakResponse: WeakResponse): Thenable<T> {490 const response = unwrapWeakResponse(weakResponse);491 const chunk = getChunk(response, 0);492 return chunk as any;493}494495function createPendingChunk<T>(response: Response): PendingChunk<T> {496 if (__DEV__) {497 // Retain a strong reference to the Response while we wait for the result.498 if (response._pendingChunks++ === 0) {499 response._weakResponse.response = response;500 if (response._pendingInitialRender !== null) {501 clearTimeout(response._pendingInitialRender);502 response._pendingInitialRender = null;503 }504 }505 }506 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors507 return new ReactPromise(PENDING, null, null);508}509510function createPendingWeakChunk<T>(response: Response): PendingWeakChunk<T> {511 // Unlike a regular pending chunk, a weak chunk may never settle, so it512 // doesn't retain a strong reference to the Response while it waits.513 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors514 return new ReactPromise(PENDING_WEAK, null, null);515}516517function releasePendingChunk(response: Response, chunk: SomeChunk<any>): void {518 if (__DEV__ && chunk.status === PENDING) {519 if (--response._pendingChunks === 0) {520 // We're no longer waiting for any more chunks. We can release the strong reference521 // to the response. We'll regain it if we ask for any more data later on.522 response._weakResponse.response = null;523 // Wait a short period to see if any more chunks get asked for. E.g. by a React render.524 // These chunks might discover more pending chunks.525 // If we don't ask for more then we assume that those chunks weren't blocking initial526 // render and are excluded from the performance track.527 response._pendingInitialRender = setTimeout(528 flushInitialRenderPerformance.bind(null, response),529 100,530 );531 }532 }533}534535function createHaltedChunk<T>(response: Response): HaltedChunk<T> {536 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors537 return new ReactPromise(HALTED, null, null);538}539540// Transition a chunk to HALTED: it will never resolve, even when the541// connection closes. Clears any listeners to release their closures. Future542// .then() calls on HALTED chunks are no-ops.543function haltChunk<T>(response: Response, chunk: SomeChunk<T>): void {544 releasePendingChunk(response, chunk);545 const haltedChunk: HaltedChunk<T> = chunk as any;546 haltedChunk.status = HALTED;547 haltedChunk.value = null;548 haltedChunk.reason = null;549}550551function createBlockedChunk<T>(response: Response): BlockedChunk<T> {552 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors553 return new ReactPromise(BLOCKED, null, null);554}555556function createErrorChunk<T>(557 response: Response,558 error: mixed,559): ErroredChunk<T> {560 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors561 return new ReactPromise(ERRORED, null, error);562}563564function filterDebugInfo(565 response: Response,566 value: {_debugInfo: ReactDebugInfo, ...},567) {568 if (response._debugEndTime === null) {569 // No end time was defined, so we keep all debug info entries.570 return;571 }572573 // Remove any debug info entries after the defined end time. For async info574 // that means we're including anything that was awaited before the end time,575 // but it doesn't need to be resolved before the end time.576 const relativeEndTime =577 response._debugEndTime -578 // $FlowFixMe[prop-missing]579 performance.timeOrigin;580 const debugInfo = [];581 for (let i = 0; i < value._debugInfo.length; i++) {582 const info = value._debugInfo[i];583 if (typeof info.time === 'number' && info.time > relativeEndTime) {584 break;585 }586 debugInfo.push(info);587 }588 value._debugInfo = debugInfo;589}590591function pruneDebugInfoAfterError(592 response: Response,593 chunk: ErroredChunk<any>,594): void {595 if (response._debugEndTime === null) {596 return;597 }598599 const relativeEndTime =600 response._debugEndTime -601 // $FlowFixMe[prop-missing]602 performance.timeOrigin;603 const debugInfo = chunk._debugInfo;604 for (let i = 0; i < debugInfo.length; i++) {605 const info = debugInfo[i];606 if (typeof info.time === 'number' && info.time > relativeEndTime) {607 // This array may already be attached to the Lazy suspended in Fizz.608 debugInfo.length = i;609 return;610 }611 }612}613614function moveDebugInfoFromChunkToInnerValue<T>(615 chunk: InitializedChunk<T> | InitializedStreamChunk<any>,616 value: T,617): void {618 // Remove the debug info from the initialized chunk, and add it to the inner619 // value instead. This can be a React element, an array, or an uninitialized620 // Lazy.621 const resolvedValue = resolveLazy(value);622 if (623 typeof resolvedValue === 'object' &&624 resolvedValue !== null &&625 (isArray(resolvedValue) ||626 typeof resolvedValue[ASYNC_ITERATOR] === 'function' ||627 resolvedValue.$$typeof === REACT_ELEMENT_TYPE ||628 resolvedValue.$$typeof === REACT_LAZY_TYPE)629 ) {630 const debugInfo = chunk._debugInfo.splice(0);631 if (isArray(resolvedValue._debugInfo)) {632 // $FlowFixMe[method-unbinding]633 resolvedValue._debugInfo.unshift.apply(634 resolvedValue._debugInfo,635 debugInfo,636 );637 } else if (!Object.isFrozen(resolvedValue)) {638 Object.defineProperty(resolvedValue as any, '_debugInfo', {639 configurable: false,640 enumerable: false,641 writable: true,642 value: debugInfo,643 });644 }645 // TODO: If the resolved value is a frozen element (e.g. a client-created646 // element from a temporary reference, or a JSX element exported as a client647 // reference), server debug info is currently dropped because the element648 // can't be mutated. We should probably clone the element so each rendering649 // context gets its own mutable copy with the correct debug info.650 }651}652653function processChunkDebugInfo<T>(654 response: Response,655 chunk: InitializedChunk<T> | InitializedStreamChunk<any>,656 value: T,657): void {658 filterDebugInfo(response, chunk);659 moveDebugInfoFromChunkToInnerValue(chunk, value);660}661662function wakeChunk<T>(663 response: Response,664 listeners: Array<InitializationReference | (T => mixed)>,665 value: T,666 chunk: InitializedChunk<T>,667): void {668 for (let i = 0; i < listeners.length; i++) {669 const listener = listeners[i];670 if (typeof listener === 'function') {671 listener(value);672 } else {673 fulfillReference(response, listener, value, chunk);674 }675 }676677 if (__DEV__) {678 processChunkDebugInfo(response, chunk, value);679 }680}681682function rejectChunk(683 response: Response,684 listeners: Array<InitializationReference | (mixed => mixed)>,685 error: mixed,686): void {687 for (let i = 0; i < listeners.length; i++) {688 const listener = listeners[i];689 if (typeof listener === 'function') {690 listener(error);691 } else {692 rejectReference(response, listener.handler, error);693 }694 }695}696697function resolveBlockedCycle<T>(698 resolvedChunk: SomeChunk<T>,699 reference: InitializationReference,700): null | InitializationHandler {701 const referencedChunk = reference.handler.chunk;702 if (referencedChunk === null) {703 return null;704 }705 if (referencedChunk === resolvedChunk) {706 // We found the cycle. We can resolve the blocked cycle now.707 return reference.handler;708 }709 const resolveListeners = referencedChunk.value;710 if (resolveListeners !== null) {711 for (let i = 0; i < resolveListeners.length; i++) {712 const listener = resolveListeners[i];713 if (typeof listener !== 'function') {714 const foundHandler = resolveBlockedCycle(resolvedChunk, listener);715 if (foundHandler !== null) {716 return foundHandler;717 }718 }719 }720 }721 return null;722}723724function wakeChunkIfInitialized<T>(725 response: Response,726 chunk: SomeChunk<T>,727 resolveListeners: Array<InitializationReference | (T => mixed)>,728 rejectListeners: null | Array<InitializationReference | (mixed => mixed)>,729): void {730 switch (chunk.status) {731 case INITIALIZED:732 wakeChunk(response, resolveListeners, chunk.value, chunk);733 break;734 case BLOCKED:735 // It is possible that we're blocked on our own chunk if it's a cycle.736 // Before adding back the listeners to the chunk, let's check if it would737 // result in a cycle.738 for (let i = 0; i < resolveListeners.length; i++) {739 const listener = resolveListeners[i];740 if (typeof listener !== 'function') {741 const reference: InitializationReference = listener;742 const cyclicHandler = resolveBlockedCycle(chunk, reference);743 if (cyclicHandler !== null) {744 // This reference points back to this chunk. We can resolve the cycle by745 // using the value from that handler.746 fulfillReference(response, reference, cyclicHandler.value, chunk);747 resolveListeners.splice(i, 1);748 i--;749 if (rejectListeners !== null) {750 const rejectionIdx = rejectListeners.indexOf(reference);751 if (rejectionIdx !== -1) {752 rejectListeners.splice(rejectionIdx, 1);753 }754 }755 // The status might have changed after fulfilling the reference.756 switch ((chunk as SomeChunk<T>).status) {757 case INITIALIZED:758 const initializedChunk: InitializedChunk<T> = chunk as any;759 wakeChunk(760 response,761 resolveListeners,762 initializedChunk.value,763 initializedChunk,764 );765 return;766 case ERRORED:767 if (rejectListeners !== null) {768 rejectChunk(response, rejectListeners, chunk.reason);769 }770 return;771 }772 }773 }774 }775 // Fallthrough776 case PENDING:777 if (chunk.value) {778 for (let i = 0; i < resolveListeners.length; i++) {779 chunk.value.push(resolveListeners[i]);780 }781 } else {782 chunk.value = resolveListeners;783 }784785 if (chunk.reason) {786 if (rejectListeners) {787 for (let i = 0; i < rejectListeners.length; i++) {788 chunk.reason.push(rejectListeners[i]);789 }790 }791 } else {792 chunk.reason = rejectListeners;793 }794795 break;796 case ERRORED:797 if (rejectListeners) {798 rejectChunk(response, rejectListeners, chunk.reason);799 }800 break;801 }802}803804function triggerErrorOnChunk<T>(805 response: Response,806 chunk: SomeChunk<T>,807 error: mixed,808): void {809 if (810 chunk.status !== PENDING &&811 chunk.status !== PENDING_WEAK &&812 chunk.status !== BLOCKED813 ) {814 // If we get more data to an already resolved ID, we assume that it's815 // a stream chunk since any other row shouldn't have more than one entry.816 const streamChunk: InitializedStreamChunk<any> = chunk as any;817 const controller = streamChunk.reason;818 // $FlowFixMe[incompatible-type]: The error method should accept mixed.819 controller.error(error);820 return;821 }822 releasePendingChunk(response, chunk);823 const listeners = chunk.reason;824825 if (__DEV__ && (chunk.status === PENDING || chunk.status === PENDING_WEAK)) {826 // Lazily initialize any debug info and block the initializing chunk on any unresolved entries.827 if (chunk._debugChunk != null) {828 const prevHandler = initializingHandler;829 const prevChunk = initializingChunk;830 initializingHandler = null;831 const cyclicChunk: BlockedChunk<T> = chunk as any;832 cyclicChunk.status = BLOCKED;833 cyclicChunk.value = null;834 cyclicChunk.reason = null;835 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {836 initializingChunk = cyclicChunk;837 }838 try {839 initializeDebugChunk(response, chunk);840 if (initializingHandler !== null) {841 if (initializingHandler.errored) {842 // Ignore error parsing debug info, we'll report the original error instead.843 } else if (initializingHandler.deps > 0) {844 // TODO: Block the resolution of the error until all the debug info has loaded.845 // We currently don't have a way to throw an error after all dependencies have846 // loaded because we currently treat errors as immediately cancelling the handler.847 }848 }849 } finally {850 initializingHandler = prevHandler;851 initializingChunk = prevChunk;852 }853 }854 }855856 const erroredChunk: ErroredChunk<T> = chunk as any;857 erroredChunk.status = ERRORED;858 erroredChunk.reason = error;859 if (__DEV__) {860 pruneDebugInfoAfterError(response, erroredChunk);861 }862 if (listeners !== null) {863 rejectChunk(response, listeners, error);864 }865}866867function createResolvedModelChunk<T>(868 response: Response,869 value: UninitializedModel,870): ResolvedModelChunk<T> {871 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors872 return new ReactPromise(RESOLVED_MODEL, value, {[RESPONSE_SYMBOL]: response});873}874875function createResolvedModuleChunk<T>(876 response: Response,877 value: ClientReference<T>,878): ResolvedModuleChunk<T> {879 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors880 return new ReactPromise(RESOLVED_MODULE, value, null);881}882883function createInitializedTextChunk(884 response: Response,885 value: string,886): InitializedChunk<string> {887 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors888 return new ReactPromise(INITIALIZED, value, null);889}890891function createInitializedBufferChunk(892 response: Response,893 value: $ArrayBufferView | ArrayBuffer,894): InitializedChunk<Uint8Array> {895 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors896 return new ReactPromise(INITIALIZED, value, null);897}898899function createInitializedIteratorResultChunk<T>(900 response: Response,901 value: T,902 done: boolean,903): InitializedChunk<IteratorResult<T, T>> {904 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors905 return new ReactPromise(INITIALIZED, {done: done, value: value}, null);906}907908function createInitializedStreamChunk<909 T: ReadableStream | $AsyncIterable<any, any, void>,910>(911 response: Response,912 value: T,913 controller: FlightStreamController,914): InitializedChunk<T> {915 if (__DEV__) {916 // Retain a strong reference to the Response while we wait for chunks.917 if (response._pendingChunks++ === 0) {918 response._weakResponse.response = response;919 }920 }921 // We use the reason field to stash the controller since we already have that922 // field. It's a bit of a hack but efficient.923 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors924 return new ReactPromise(INITIALIZED, value, controller);925}926927function createResolvedIteratorResultChunk<T>(928 response: Response,929 value: UninitializedModel,930 done: boolean,931): ResolvedModelChunk<IteratorResult<T, T>> {932 // To reuse code as much code as possible we add the wrapper element as part of the JSON.933 const iteratorResultJSON =934 (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';935 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors936 return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, {937 [RESPONSE_SYMBOL]: response,938 });939}940941function resolveIteratorResultChunk<T>(942 response: Response,943 chunk: SomeChunk<IteratorResult<T, T>>,944 value: UninitializedModel,945 done: boolean,946): void {947 // To reuse code as much code as possible we add the wrapper element as part of the JSON.948 const iteratorResultJSON =949 (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';950 resolveModelChunk(response, chunk, iteratorResultJSON);951}952953function resolveModelChunk<T>(954 response: Response,955 chunk: SomeChunk<T>,956 value: UninitializedModel,957): void {958 if (chunk.status !== PENDING && chunk.status !== PENDING_WEAK) {959 // If we get more data to an already resolved ID, we assume that it's960 // a stream chunk since any other row shouldn't have more than one entry.961 const streamChunk: InitializedStreamChunk<any> = chunk as any;962 const controller = streamChunk.reason;963 controller.enqueueModel(value);964 return;965 }966 releasePendingChunk(response, chunk);967 const resolveListeners = chunk.value;968 const rejectListeners = chunk.reason;969 const resolvedChunk: ResolvedModelChunk<T> = chunk as any;970 resolvedChunk.status = RESOLVED_MODEL;971 resolvedChunk.value = value;972 resolvedChunk.reason = {[RESPONSE_SYMBOL]: response};973 if (resolveListeners !== null) {974 // This is unfortunate that we're reading this eagerly if975 // we already have listeners attached since they might no976 // longer be rendered or might not be the highest pri.977 initializeModelChunk(resolvedChunk);978 // The status might have changed after initialization.979 wakeChunkIfInitialized(response, chunk, resolveListeners, rejectListeners);980 }981}982983function resolveModuleChunk<T>(984 response: Response,985 chunk: SomeChunk<T>,986 value: ClientReference<T>,987): void {988 if (989 chunk.status !== PENDING &&990 chunk.status !== PENDING_WEAK &&991 chunk.status !== BLOCKED992 ) {993 // We already resolved. We didn't expect to see this.994 return;995 }996 releasePendingChunk(response, chunk);997 const resolveListeners = chunk.value;998 const rejectListeners = chunk.reason;999 const resolvedChunk: ResolvedModuleChunk<T> = chunk as any;1000 resolvedChunk.status = RESOLVED_MODULE;1001 resolvedChunk.value = value;1002 resolvedChunk.reason = null;1003 if (__DEV__) {1004 const debugInfo = getModuleDebugInfo(value);1005 if (debugInfo !== null) {1006 // Add to the live set if it was already initialized.1007 // $FlowFixMe[method-unbinding]1008 resolvedChunk._debugInfo.push.apply(resolvedChunk._debugInfo, debugInfo);1009 }1010 }1011 if (resolveListeners !== null) {1012 initializeModuleChunk(resolvedChunk);1013 wakeChunkIfInitialized(response, chunk, resolveListeners, rejectListeners);1014 }1015}10161017type InitializationReference = {1018 handler: InitializationHandler,1019 parentObject: Object,1020 key: string,1021 map: (1022 response: Response,1023 model: any,1024 parentObject: Object,1025 key: string,1026 ) => any,1027 path: Array<string>,1028 isDebug?: boolean, // DEV-only1029};1030type PendingInitializationHandler = {1031 parent: null | InitializationHandler,1032 chunk: null | BlockedChunk<any>,1033 value: any,1034 // The controller of a stream whose chunk is blocked on its debug info. Null1035 // for every other model.1036 reason: null | FlightStreamController,1037 deps: number,1038 errored: false,1039};1040type ErroredInitializationHandler = {1041 parent: null | InitializationHandler,1042 chunk: null | BlockedChunk<any>,1043 value: null,1044 // The error the handler was rejected with.1045 reason: mixed,1046 deps: number,1047 errored: true,1048};1049type InitializationHandler =1050 | PendingInitializationHandler1051 | ErroredInitializationHandler;1052let initializingHandler: null | InitializationHandler = null;1053let initializingChunk: null | BlockedChunk<any> = null;1054let isInitializingDebugInfo: boolean = false;10551056function initializeDebugChunk(1057 response: Response,1058 chunk: ResolvedModelChunk<any> | PendingChunk<any> | PendingWeakChunk<any>,1059): void {1060 const debugChunk = chunk._debugChunk;1061 if (debugChunk !== null) {1062 const debugInfo = chunk._debugInfo;1063 const prevIsInitializingDebugInfo = isInitializingDebugInfo;1064 isInitializingDebugInfo = true;1065 try {1066 if (debugChunk.status === RESOLVED_MODEL) {1067 // Find the index of this debug info by walking the linked list.1068 let idx = debugInfo.length;1069 let c = debugChunk._debugChunk;1070 while (c !== null) {1071 if (c.status !== INITIALIZED) {1072 idx++;1073 }1074 c = c._debugChunk;1075 }1076 // Initializing the model for the first time.1077 initializeModelChunk(debugChunk);1078 const initializedChunk = debugChunk as any as SomeChunk<any>;1079 switch (initializedChunk.status) {1080 case INITIALIZED: {1081 debugInfo[idx] = initializeDebugInfo(1082 response,1083 initializedChunk.value,1084 );1085 break;1086 }1087 case BLOCKED:1088 case PENDING:1089 case PENDING_WEAK: {1090 waitForReference(1091 initializedChunk,1092 debugInfo,1093 '' + idx,1094 response,1095 initializeDebugInfo,1096 [''], // path1097 true,1098 );1099 break;1100 }1101 default:1102 throw initializedChunk.reason;1103 }1104 } else {1105 switch (debugChunk.status) {1106 case INITIALIZED: {1107 // Already done.1108 break;1109 }1110 case BLOCKED:1111 case PENDING:1112 case PENDING_WEAK: {1113 // Signal to the caller that we need to wait.1114 waitForReference(1115 debugChunk,1116 {}, // noop, since we'll have already added an entry to debug info1117 'debug', // noop, but we need it to not be empty string since that indicates the root object1118 response,1119 initializeDebugInfo,1120 [''], // path1121 true,1122 );1123 break;1124 }1125 default:1126 throw debugChunk.reason;1127 }1128 }1129 } catch (error) {1130 triggerErrorOnChunk(response, chunk, error);1131 } finally {1132 isInitializingDebugInfo = prevIsInitializingDebugInfo;1133 }1134 }1135}11361137function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {1138 const prevHandler = initializingHandler;1139 const prevChunk = initializingChunk;1140 initializingHandler = null;11411142 const resolvedModel = chunk.value;1143 const response = chunk.reason[RESPONSE_SYMBOL];11441145 // We go to the BLOCKED state until we've fully resolved this.1146 // We do this before parsing in case we try to initialize the same chunk1147 // while parsing the model. Such as in a cyclic reference.1148 const cyclicChunk: BlockedChunk<T> = chunk as any;1149 cyclicChunk.status = BLOCKED;1150 cyclicChunk.value = null;1151 cyclicChunk.reason = null;11521153 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {1154 initializingChunk = cyclicChunk;1155 }11561157 if (__DEV__) {1158 // Initialize any debug info and block the initializing chunk on any1159 // unresolved entries.1160 initializeDebugChunk(response, chunk);1161 // TODO: The chunk might have transitioned to ERRORED now.1162 // Should we return early if that happens?1163 }11641165 try {1166 const value: T = parseModel(response, resolvedModel);1167 const handler = initializingHandler;1168 if (handler !== null) {1169 if (handler.errored) {1170 // The handler errored during the parse, so `cyclicChunk` errors with1171 // the same reason. `triggerErrorOnChunk` also rejects the references1172 // that nested parses queued on `cyclicChunk` during the parse. No value1173 // will arrive for them. `initializeDebugChunk` can have errored1174 // `cyclicChunk` before the parse. That error stays.1175 if ((cyclicChunk as any).status === BLOCKED) {1176 triggerErrorOnChunk(response, cyclicChunk, handler.reason);1177 }1178 return;1179 }1180 // Record on the handler which chunk it completes, and with what value,1181 // before the listeners below are examined. `resolveBlockedCycle` walks1182 // from a reference to its handler and on to that handler's chunk. A cycle1183 // that closes through `cyclicChunk` is only found once `handler.chunk` is1184 // `cyclicChunk`.1185 handler.value = value;1186 handler.chunk = cyclicChunk;1187 }11881189 const listeners = cyclicChunk.value;1190 const rejectListeners = cyclicChunk.reason;1191 cyclicChunk.value = null;1192 cyclicChunk.reason = null;1193 if (listeners !== null) {1194 // These listeners were added to `cyclicChunk` while its model was1195 // parsing. They come from models that were parsed nested inside that1196 // parse and that pointed back at `cyclicChunk`. They fall in two groups.1197 //1198 // A reference whose handler is transitively waiting on `cyclicChunk` is a1199 // cycle. Neither side can complete before the other, so one of them has1200 // to accept the value as it is now. The reference receives the current1201 // object, and the outstanding references fill it in place.1202 //1203 // Every other listener waits. Its handler is not waiting on1204 // `cyclicChunk`, so it can receive the value once the model has resolved1205 // its own references, like any reference into a blocked chunk does.1206 // Handing it the value now would complete that handler with an object1207 // that still has references outstanding. Function listeners have no1208 // handler and always wait.1209 let cyclic: null | Array<InitializationReference> = null;1210 let deferred: null | Array<InitializationReference | (T => mixed)> = null;1211 for (let i = 0; i < listeners.length; i++) {1212 const listener = listeners[i];1213 if (1214 typeof listener !== 'function' &&1215 resolveBlockedCycle(cyclicChunk, listener) !== null1216 ) {1217 if (cyclic === null) {1218 cyclic = [];1219 }1220 cyclic.push(listener);1221 // A reference is queued for rejection as the same object that is1222 // queued for resolution. It is fulfilled below, so its rejection1223 // entry goes.1224 if (rejectListeners !== null) {1225 const rejectionIdx = rejectListeners.indexOf(listener);1226 if (rejectionIdx !== -1) {1227 rejectListeners.splice(rejectionIdx, 1);1228 }1229 }1230 } else {1231 if (deferred === null) {1232 deferred = [];1233 }1234 deferred.push(listener);1235 }1236 }1237 // Put the deferred listeners back on `cyclicChunk` before any cyclic1238 // reference is fulfilled. Fulfilling one can complete `cyclicChunk`, and1239 // completion wakes what is queued on it at that moment.1240 if (deferred !== null) {1241 cyclicChunk.value = deferred;1242 cyclicChunk.reason = rejectListeners;1243 }1244 if (cyclic !== null) {1245 for (let i = 0; i < cyclic.length; i++) {1246 fulfillReference(response, cyclic[i], value, cyclicChunk);1247 }1248 }1249 }12501251 if ((cyclicChunk as any).status !== BLOCKED) {1252 // Fulfilling a cyclic reference completed `cyclicChunk`, or failed and1253 // errored it. A failure rejects the reference's handler. The rejection1254 // reaches `cyclicChunk`, directly or through the cycle, because1255 // `handler.chunk` is set.1256 return;1257 }1258 if (handler !== null && handler.deps > 0) {1259 // The model references chunks that have not resolved yet. `cyclicChunk`1260 // stays BLOCKED, with the deferred listeners queued on it, until the last1261 // of those resolves.1262 return;1263 }1264 initializeBlockedChunk(response, cyclicChunk, value, null);1265 } catch (error) {1266 const erroredChunk: ErroredChunk<T> = chunk as any;1267 erroredChunk.status = ERRORED;1268 erroredChunk.reason = error;1269 } finally {1270 initializingHandler = prevHandler;1271 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {1272 initializingChunk = prevChunk;1273 }1274 }1275}12761277function initializeModuleChunk<T>(chunk: ResolvedModuleChunk<T>): void {1278 try {1279 const value: T = requireModule(chunk.value);1280 const initializedChunk: InitializedChunk<T> = chunk as any;1281 initializedChunk.status = INITIALIZED;1282 initializedChunk.value = value;1283 initializedChunk.reason = null;1284 } catch (error) {1285 const erroredChunk: ErroredChunk<T> = chunk as any;1286 erroredChunk.status = ERRORED;1287 erroredChunk.reason = error;1288 }1289}12901291// Report that any missing chunks in the model is now going to throw this1292// error upon read. Also notify any pending promises.1293export function reportGlobalError(1294 weakResponse: WeakResponse,1295 error: Error,1296): void {1297 if (hasGCedResponse(weakResponse)) {1298 // Ignore close signal if we are not awaiting any more pending chunks.1299 return;1300 }1301 const response = unwrapWeakResponse(weakResponse);1302 response._closed = true;1303 response._closedReason = error;1304 response._chunks.forEach(chunk => {1305 // If this chunk was already resolved or errored, it won't1306 // trigger an error but if it wasn't then we need to1307 // because we won't be getting any new data to resolve it.1308 if (chunk.status === PENDING) {1309 triggerErrorOnChunk(response, chunk, error);1310 } else if (enableFlightWeakThenables && chunk.status === PENDING_WEAK) {1311 // A weak Promise reference may never be emitted by the server. It1312 // stays forever pending instead of erroring.1313 haltChunk(response, chunk);1314 } else if (chunk.status === INITIALIZED && chunk.reason !== null) {1315 chunk.reason.error(error);1316 }1317 });1318 if (__DEV__) {1319 const debugChannel = response._debugChannel;1320 if (debugChannel !== undefined) {1321 // If we don't have any more ways of reading data, we don't have to send1322 // any more neither. So we close the writable side.1323 closeDebugChannel(debugChannel);1324 response._debugChannel = undefined;1325 // Make sure the debug channel is not closed a second time when the1326 // Response gets GC:ed.1327 if (debugChannelRegistry !== null) {1328 debugChannelRegistry.unregister(response);1329 }1330 }1331 }1332}13331334function nullRefGetter() {1335 if (__DEV__) {1336 return null;1337 }1338}13391340function getIOInfoTaskName(ioInfo: ReactIOInfo): string {1341 return ioInfo.name || 'unknown';1342}13431344function getAsyncInfoTaskName(asyncInfo: ReactAsyncInfo): string {1345 return 'await ' + getIOInfoTaskName(asyncInfo.awaited);1346}13471348function getServerComponentTaskName(componentInfo: ReactComponentInfo): string {1349 return '<' + (componentInfo.name || '...') + '>';1350}13511352function getTaskName(type: mixed): string {1353 if (type === REACT_FRAGMENT_TYPE) {1354 return '<>';1355 }1356 if (typeof type === 'function') {1357 // This is a function so it must have been a Client Reference that resolved to1358 // a function. We use "use client" to indicate that this is the boundary into1359 // the client. There should only be one for any given owner chain.1360 return '"use client"';1361 }1362 if (1363 typeof type === 'object' &&1364 type !== null &&1365 type.$$typeof === REACT_LAZY_TYPE1366 ) {1367 if (type._payload instanceof ReactPromise) {1368 // This is a lazy node created by Flight, i.e. it wraps a chunk. It is1369 // probably a client reference. We use the "use client" string to indicate1370 // that this is the boundary into the client. There will only be one for1371 // any given owner chain.1372 return '"use client"';1373 }1374 // We don't want to eagerly initialize the initializer in DEV mode so we can't1375 // call it to extract the type so we don't know the type of this component.1376 return '<...>';1377 }1378 try {1379 const name = getComponentNameFromType(type);1380 return name ? '<' + name + '>' : '<...>';1381 } catch (x) {1382 return '<...>';1383 }1384}13851386function initializeElement(1387 response: Response,1388 element: any,1389 lazyNode: null | LazyComponent<1390 React$Element<any>,1391 SomeChunk<React$Element<any>>,1392 >,1393): void {1394 if (!__DEV__) {1395 return;1396 }1397 const stack = element._debugStack;1398 const owner = element._owner;1399 if (owner === null) {1400 element._owner = response._debugRootOwner;1401 }1402 let env = response._rootEnvironmentName;1403 if (owner !== null && owner.env != null) {1404 // Interestingly we don't actually have the environment name of where1405 // this JSX was created if it doesn't have an owner but if it does1406 // it must be the same environment as the owner. We could send it separately1407 // but it seems a bit unnecessary for this edge case.1408 env = owner.env;1409 }1410 let normalizedStackTrace: null | Error = null;1411 if (owner === null && response._debugRootStack != null) {1412 // We override the stack if we override the owner since the stack where the root JSX1413 // was created on the server isn't very useful but where the request was made is.1414 normalizedStackTrace = response._debugRootStack;1415 } else if (stack !== null) {1416 // We create a fake stack and then create an Error object inside of it.1417 // This means that the stack trace is now normalized into the native format1418 // of the browser and the stack frames will have been registered with1419 // source mapping information.1420 // This can unfortunately happen within a user space callstack which will1421 // remain on the stack.1422 normalizedStackTrace = createFakeJSXCallStackInDEV(response, stack, env);1423 }1424 element._debugStack = normalizedStackTrace;1425 let task: null | ConsoleTask = null;1426 if (supportsCreateTask && stack !== null) {1427 const createTaskFn = (console as any).createTask.bind(1428 console,1429 getTaskName(element.type),1430 );1431 const callStack = buildFakeCallStack(1432 response,1433 stack,1434 env,1435 false,1436 createTaskFn,1437 );1438 // This owner should ideally have already been initialized to avoid getting1439 // user stack frames on the stack.1440 const ownerTask =1441 owner === null ? null : initializeFakeTask(response, owner);1442 if (ownerTask === null) {1443 const rootTask = response._debugRootTask;1444 if (rootTask != null) {1445 task = rootTask.run(callStack);1446 } else {1447 task = callStack();1448 }1449 } else {1450 task = ownerTask.run(callStack);1451 }1452 }1453 element._debugTask = task;14541455 // This owner should ideally have already been initialized to avoid getting1456 // user stack frames on the stack.1457 if (owner !== null) {1458 initializeFakeStack(response, owner);1459 }14601461 if (lazyNode !== null) {1462 // If the lazy node is initialized, we move its debug info to the inner1463 // value.1464 if (lazyNode._payload.status === INITIALIZED && lazyNode._debugInfo) {1465 const debugInfo = lazyNode._debugInfo.splice(0);1466 if (element._debugInfo) {1467 // $FlowFixMe[method-unbinding]1468 element._debugInfo.unshift.apply(element._debugInfo, debugInfo);1469 } else {1470 Object.defineProperty(element, '_debugInfo', {1471 configurable: false,1472 enumerable: false,1473 writable: true,1474 value: debugInfo,1475 });1476 }1477 }1478 }14791480 // TODO: We should be freezing the element but currently, we might write into1481 // _debugInfo later. We could move it into _store which remains mutable.1482 Object.freeze(element.props);1483}14841485function createElement(1486 response: Response,1487 type: mixed,1488 key: mixed,1489 props: mixed,1490 owner: ?ReactComponentInfo, // DEV-only1491 stack: ?ReactStackTrace, // DEV-only1492 validated: 0 | 1 | 2, // DEV-only1493):1494 | React$Element<any>1495 | LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> {1496 let element: any;1497 if (__DEV__) {1498 // `ref` is non-enumerable in dev1499 element = {1500 $$typeof: REACT_ELEMENT_TYPE,1501 type,1502 key,1503 props,1504 _owner: owner === undefined ? null : owner,1505 } as any;1506 Object.defineProperty(element, 'ref', {1507 enumerable: false,1508 get: nullRefGetter,1509 });1510 } else {1511 element = {1512 // This tag allows us to uniquely identify this as a React Element1513 $$typeof: REACT_ELEMENT_TYPE,15141515 type,1516 key,1517 ref: null,1518 props,1519 } as any;1520 }15211522 if (__DEV__) {1523 // We don't really need to add any of these but keeping them for good measure.1524 // Unfortunately, _store is enumerable in jest matchers so for equality to1525 // work, I need to keep it or make _store non-enumerable in the other file.1526 element._store = {} as {1527 validated?: number,1528 };1529 Object.defineProperty(element._store, 'validated', {1530 configurable: false,1531 enumerable: false,1532 writable: true,1533 value: validated, // Whether the element has already been validated on the server.1534 });1535 // debugInfo contains Server Component debug information.1536 Object.defineProperty(element, '_debugInfo', {1537 configurable: false,1538 enumerable: false,1539 writable: true,1540 value: null,1541 });1542 Object.defineProperty(element, '_debugStack', {1543 configurable: false,1544 enumerable: false,1545 writable: true,1546 value: stack === undefined ? null : stack,1547 });1548 Object.defineProperty(element, '_debugTask', {1549 configurable: false,1550 enumerable: false,1551 writable: true,1552 value: null,1553 });1554 }15551556 if (initializingHandler !== null) {1557 const handler = initializingHandler;1558 // We pop the stack to the previous outer handler before leaving the Element.1559 // This is effectively the complete phase.1560 initializingHandler = handler.parent;1561 if (handler.errored) {1562 // Something errored inside this Element's props. We can turn this Element1563 // into a Lazy so that we can still render up until that Lazy is rendered.1564 const erroredChunk: ErroredChunk<React$Element<any>> = createErrorChunk(1565 response,1566 handler.reason,1567 );1568 if (__DEV__) {1569 initializeElement(response, element, null);1570 // Conceptually the error happened inside this Element but right before1571 // it was rendered. We don't have a client side component to render but1572 // we can add some DebugInfo to explain that this was conceptually a1573 // Server side error that errored inside this element. That way any stack1574 // traces will point to the nearest JSX that errored - e.g. during1575 // serialization.1576 const erroredComponent: ReactComponentInfo = {1577 name: getComponentNameFromType(element.type) || '',1578 owner: element._owner,1579 };1580 // $FlowFixMe[cannot-write]1581 erroredComponent.debugStack = element._debugStack;1582 if (supportsCreateTask) {1583 // $FlowFixMe[cannot-write]1584 erroredComponent.debugTask = element._debugTask;1585 }1586 erroredChunk._debugInfo = [erroredComponent];1587 }1588 return createLazyChunkWrapper(erroredChunk, validated);1589 }1590 if (handler.deps > 0) {1591 // We have blocked references inside this Element but we can turn this into1592 // a Lazy node referencing this Element to let everything around it proceed.1593 const blockedChunk: BlockedChunk<React$Element<any>> =1594 createBlockedChunk(response);1595 handler.value = element;1596 handler.chunk = blockedChunk;1597 const lazyNode = createLazyChunkWrapper(blockedChunk, validated);1598 if (__DEV__) {1599 // After we have initialized any blocked references, initialize stack etc.1600 const init = initializeElement.bind(null, response, element, lazyNode);1601 blockedChunk.then(init, init);1602 }1603 return lazyNode;1604 }1605 }1606 if (__DEV__) {1607 initializeElement(response, element, null);1608 }16091610 return element;1611}16121613function transferValidation(store: {validated: 0 | 1 | 2}, value: mixed): void {1614 if (store.validated && typeof value === 'object' && value !== null) {1615 // Only elements and lazy nodes carry key validation. Any other value, e.g.1616 // an array of children, needs to have its own items validated instead.1617 const $$typeof = (value as any).$$typeof;1618 if ($$typeof === REACT_ELEMENT_TYPE || $$typeof === REACT_LAZY_TYPE) {1619 const valueStore = (value as any)._store;1620 if (valueStore && !valueStore.validated) {1621 valueStore.validated = store.validated;1622 }1623 }1624 }1625}16261627function readChunkAndTransferValidation<T>(1628 store: {validated: 0 | 1 | 2},1629 payload: SomeChunk<T>,1630): T {1631 const value: T = readChunk(payload);1632 transferValidation(store, value);1633 return value;1634}16351636function createLazyChunkWrapper<T>(1637 chunk: SomeChunk<T>,1638 validated: 0 | 1 | 2, // DEV-only1639): LazyComponent<T, SomeChunk<T>> {1640 const lazyType: LazyComponent<T, SomeChunk<T>> = {1641 $$typeof: REACT_LAZY_TYPE,1642 _payload: chunk,1643 _init: readChunk,1644 };1645 if (__DEV__) {1646 // Forward the live array1647 lazyType._debugInfo = chunk._debugInfo;1648 // Initialize a store for key validation by the JSX runtime. It can only1649 // validate the lazy node itself, because the value it refers to might not1650 // exist yet at that point, e.g. if it's an outlined row that hasn't been1651 // initialized. So the validation is transferred to the value when the lazy1652 // node is unwrapped. If the value is another lazy node, unwrapping that one1653 // forwards the validation further.1654 const store = {validated: validated};1655 lazyType._store = store;1656 // $FlowFixMe[incompatible-type] `bind` loses the type argument.1657 lazyType._init = readChunkAndTransferValidation.bind(null, store);1658 }1659 return lazyType;1660}16611662function getChunk(response: Response, id: number): SomeChunk<any> {1663 const chunks = response._chunks;1664 let chunk = chunks.get(id);1665 if (!chunk) {1666 if (response._closed) {1667 if (response._allowPartialStream) {1668 // For partial streams, chunks accessed after close should be HALTED1669 // (never resolve).1670 chunk = createHaltedChunk(response);1671 } else {1672 // We have already errored the response and we're not going to get1673 // anything more streaming in so this will immediately error.1674 chunk = createErrorChunk(response, response._closedReason);1675 }1676 } else {1677 chunk = createPendingChunk(response);1678 }1679 chunks.set(id, chunk);1680 }1681 return chunk;1682}16831684// Like getChunk, but for weak Promise references. The server may never emit1685// the row for a weak reference, so an unresolved weak chunk halts (stays1686// forever pending) instead of erroring when the stream closes.1687function getWeakChunk(response: Response, id: number): SomeChunk<any> {1688 const chunks = response._chunks;1689 let chunk = chunks.get(id);1690 if (!chunk) {1691 if (response._closed) {1692 // The stream already closed without emitting this row, so it will1693 // never resolve.1694 chunk = createHaltedChunk(response);1695 } else {1696 chunk = createPendingWeakChunk(response);1697 }1698 chunks.set(id, chunk);1699 }1700 return chunk;1701}17021703// Moves a blocked chunk to INITIALIZED and wakes whatever is waiting on it. The1704// other `initialize*Chunk` functions start from a resolved chunk and parse it.1705// This one starts from a chunk whose model is already parsed and whose1706// references have all resolved.1707function initializeBlockedChunk<T>(1708 response: Response,1709 chunk: BlockedChunk<T>,1710 value: T,1711 reason: InitializedChunk<T>['reason'],1712): void {1713 const resolveListeners = chunk.value;1714 if (__DEV__) {1715 // Only a blocked chunk receives debug info, so release the set here.1716 chunk._receivedDebugInfo = null;1717 }1718 const initializedChunk: InitializedChunk<T> = chunk as any;1719 initializedChunk.status = INITIALIZED;1720 initializedChunk.value = value;1721 initializedChunk.reason = reason;1722 if (resolveListeners !== null) {1723 wakeChunk(response, resolveListeners, value, initializedChunk);1724 } else if (__DEV__) {1725 processChunkDebugInfo(response, initializedChunk, value);1726 }1727}17281729// Initializes the handler's chunk once the handler has no references left to1730// wait on. `handler.chunk` is null while the handler's model is still parsing.1731// `initializeModelChunk` handles that case when the parse ends.1732function initializeChunkIfUnblocked(1733 response: Response,1734 handler: InitializationHandler,1735): void {1736 if (handler.deps !== 0 || handler.errored) {1737 return;1738 }1739 const chunk = handler.chunk;1740 if (chunk === null) {1741 return;1742 }1743 // `BlockedChunk` is the chunk's type at the time the handler recorded it.1744 // The chunk can have errored since, or a cyclic reference can have completed1745 // it, so its status is read again here.1746 if ((chunk as SomeChunk<any>).status !== BLOCKED) {1747 return;1748 }1749 // `handler.reason` is the controller of a stream whose chunk was blocked on1750 // its debug info, and null otherwise. It becomes the initialized chunk's1751 // `reason` either way.1752 initializeBlockedChunk(response, chunk, handler.value, handler.reason);1753}17541755function fulfillReference(1756 response: Response,1757 reference: InitializationReference,1758 value: any,1759 fulfilledChunk: SomeChunk<any>,1760): void {1761 const {handler, parentObject, key, map, path} = reference;17621763 try {1764 for (let i = 1; i < path.length; i++) {1765 while (1766 typeof value === 'object' &&1767 value !== null &&1768 value.$$typeof === REACT_LAZY_TYPE1769 ) {1770 // We never expect to see a Lazy node on this path because we encode those as1771 // separate models. This must mean that we have inserted an extra lazy node1772 // e.g. to replace a blocked element. We must instead look for it inside.1773 const referencedChunk: SomeChunk<any> = value._payload;1774 if (referencedChunk === handler.chunk) {1775 // This is a reference to the thing we're currently blocking. We can peak1776 // inside of it to get the value.1777 value = handler.value;1778 continue;1779 } else {1780 switch (referencedChunk.status) {1781 case RESOLVED_MODEL:1782 initializeModelChunk(referencedChunk);1783 break;1784 case RESOLVED_MODULE:1785 initializeModuleChunk(referencedChunk);1786 break;1787 }1788 switch (referencedChunk.status) {1789 case INITIALIZED: {1790 value = referencedChunk.value;1791 continue;1792 }1793 case BLOCKED: {1794 // It is possible that we're blocked on our own chunk if it's a cycle.1795 // Before adding the listener to the inner chunk, let's check if it would1796 // result in a cycle.1797 const cyclicHandler = resolveBlockedCycle(1798 referencedChunk,1799 reference,1800 );1801 if (cyclicHandler !== null) {1802 // This reference points back to this chunk. We can resolve the cycle by1803 // using the value from that handler.1804 value = cyclicHandler.value;1805 continue;1806 }1807 // Fallthrough1808 }1809 case PENDING:1810 case PENDING_WEAK: {1811 // If we're not yet initialized we need to skip what we've already drilled1812 // through and then wait for the next value to become available.1813 path.splice(0, i - 1);1814 // Add "listener" to our new chunk dependency.1815 if (referencedChunk.value === null) {1816 referencedChunk.value = [reference];1817 } else {1818 referencedChunk.value.push(reference);1819 }1820 if (referencedChunk.reason === null) {1821 referencedChunk.reason = [reference];1822 } else {1823 referencedChunk.reason.push(reference);1824 }1825 return;1826 }1827 case HALTED: {1828 // Do nothing. We couldn't fulfill.1829 // TODO: Mark downstreams as halted too.1830 return;1831 }1832 default: {1833 rejectReference(1834 response,1835 reference.handler,1836 referencedChunk.reason,1837 );1838 return;1839 }1840 }1841 }1842 }1843 const name = path[i];1844 if (1845 typeof value === 'object' &&1846 value !== null &&1847 hasOwnProperty.call(value, name)1848 ) {1849 value = value[name];1850 } else {1851 throw new Error('Invalid reference.');1852 }1853 }18541855 while (1856 typeof value === 'object' &&1857 value !== null &&1858 value.$$typeof === REACT_LAZY_TYPE1859 ) {1860 // If what we're referencing is a Lazy it must be because we inserted one as a virtual node1861 // while it was blocked by other data. If it's no longer blocked, we can unwrap it.1862 const referencedChunk: SomeChunk<any> = value._payload;1863 if (referencedChunk === handler.chunk) {1864 // This is a reference to the thing we're currently blocking. We can peak1865 // inside of it to get the value.1866 value = handler.value;1867 continue;1868 } else {1869 switch (referencedChunk.status) {1870 case RESOLVED_MODEL:1871 initializeModelChunk(referencedChunk);1872 break;1873 case RESOLVED_MODULE:1874 initializeModuleChunk(referencedChunk);1875 break;1876 }1877 switch (referencedChunk.status) {1878 case INITIALIZED: {1879 value = referencedChunk.value;1880 continue;1881 }1882 }1883 }1884 break;1885 }18861887 const mappedValue = map(response, value, parentObject, key);1888 if (key !== __PROTO__) {1889 parentObject[key] = mappedValue;1890 }18911892 // If this is the root object for a model reference, where `handler.value`1893 // is a stale `null`, the resolved value can be used directly.1894 if (key === '' && handler.value === null) {1895 handler.value = mappedValue;1896 }18971898 // If the parent object is an unparsed React element tuple, we also need to1899 // update the props and owner of the parsed element object (i.e.1900 // handler.value).1901 if (1902 parentObject[0] === REACT_ELEMENT_TYPE &&1903 typeof handler.value === 'object' &&1904 handler.value !== null &&1905 handler.value.$$typeof === REACT_ELEMENT_TYPE1906 ) {1907 const element: any = handler.value;1908 switch (key) {1909 case '3':1910 if (__DEV__ && !reference.isDebug) {1911 transferReferencedDebugInfo(handler.chunk, fulfilledChunk);1912 }1913 element.props = mappedValue;1914 break;1915 case '4':1916 // This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.1917 if (__DEV__) {1918 element._owner = mappedValue;1919 }1920 break;1921 case '5':1922 // This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.1923 if (__DEV__) {1924 element._debugStack = mappedValue;1925 }1926 break;1927 default:1928 if (__DEV__ && !reference.isDebug) {1929 transferReferencedDebugInfo(handler.chunk, fulfilledChunk);1930 }1931 break;1932 }1933 } else if (__DEV__ && !reference.isDebug) {1934 transferReferencedDebugInfo(handler.chunk, fulfilledChunk);1935 }1936 } catch (error) {1937 rejectReference(response, reference.handler, error);1938 return;1939 }19401941 handler.deps--;1942 initializeChunkIfUnblocked(response, handler);1943}19441945function rejectReference(1946 response: Response,1947 handler: InitializationHandler,1948 error: mixed,1949): void {1950 if (handler.errored) {1951 // We've already errored. We could instead build up an AggregateError1952 // but if there are multiple errors we just take the first one like1953 // Promise.all.1954 return;1955 }1956 const blockedValue = handler.value;1957 const erroredHandler: ErroredInitializationHandler = handler as any;1958 erroredHandler.errored = true;1959 erroredHandler.value = null;1960 erroredHandler.reason = error;1961 const chunk = handler.chunk;1962 if (chunk === null || chunk.status !== BLOCKED) {1963 return;1964 }19651966 if (__DEV__) {1967 if (1968 typeof blockedValue === 'object' &&1969 blockedValue !== null &&1970 blockedValue.$$typeof === REACT_ELEMENT_TYPE1971 ) {1972 const element = blockedValue;1973 // Conceptually the error happened inside this Element but right before1974 // it was rendered. We don't have a client side component to render but1975 // we can add some DebugInfo to explain that this was conceptually a1976 // Server side error that errored inside this element. That way any stack1977 // traces will point to the nearest JSX that errored - e.g. during1978 // serialization.1979 const erroredComponent: ReactComponentInfo = {1980 name: getComponentNameFromType(element.type) || '',1981 owner: element._owner,1982 };1983 // $FlowFixMe[cannot-write]1984 erroredComponent.debugStack = element._debugStack;1985 if (supportsCreateTask) {1986 // $FlowFixMe[cannot-write]1987 erroredComponent.debugTask = element._debugTask;1988 }1989 chunk._debugInfo.push(erroredComponent);1990 }1991 }19921993 triggerErrorOnChunk(response, chunk, error);1994}19951996function waitForReference<T>(1997 referencedChunk: PendingChunk<T> | PendingWeakChunk<T> | BlockedChunk<T>,1998 parentObject: Object,1999 key: string,2000 response: Response,
Findings
✓ No findings reported for this file.