main
js 5,606 lines 178 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import 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';
22 import type {LazyComponent} from 'react/src/ReactLazy';
23
24 import type {
25 ClientReference,
26 ClientReferenceMetadata,
27 ServerConsumerModuleMap,
28 ServerManifest,
29 StringDecoder,
30 ModuleLoading,
31 } from './ReactFlightClientConfig';
32
33 import type {
34 HintCode,
35 HintModel,
36 } from 'react-server/src/ReactFlightServerConfig';
37
38 import type {
39 CallServerCallback,
40 EncodeFormActionCallback,
41 } from './ReactFlightReplyClient';
42
43 import type {TemporaryReferenceSet} from './ReactFlightTemporaryReferences';
44
45 import {
46 enableProfilerTimer,
47 enableComponentPerformanceTrack,
48 enableAsyncDebugInfo,
49 enableFlightWeakThenables,
50 } from 'shared/ReactFeatureFlags';
51
52 import {
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';
68
69 import {
70 createBoundServerReference,
71 registerBoundServerReference,
72 } from './ReactFlightReplyClient';
73
74 import {readTemporaryReference} from './ReactFlightTemporaryReferences';
75
76 import {
77 markAllTracksInOrder,
78 logComponentRender,
79 logDedupedComponentRender,
80 logComponentAborted,
81 logComponentErrored,
82 logIOInfo,
83 logIOInfoErrored,
84 logComponentAwait,
85 logComponentAwaitAborted,
86 logComponentAwaitErrored,
87 } from './ReactFlightPerformanceTrack';
88
89 import {
90 REACT_LAZY_TYPE,
91 REACT_ELEMENT_TYPE,
92 ASYNC_ITERATOR,
93 REACT_FRAGMENT_TYPE,
94 } from 'shared/ReactSymbols';
95
96 import getComponentNameFromType from 'shared/getComponentNameFromType';
97
98 import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';
99
100 import hasOwnProperty from 'shared/hasOwnProperty';
101
102 import getPrototypeOf from 'shared/getPrototypeOf';
103
104 import {injectInternals} from './ReactFlightClientDevToolsHook';
105
106 import {OMITTED_PROP_ERROR} from 'shared/ReactFlightPropertyAccess';
107
108 import ReactVersion from 'shared/ReactVersion';
109
110 import isArray from 'shared/isArray';
111
112 import * as React from 'react';
113
114 import type {SharedStateServer} from 'react/src/ReactSharedInternalsServer';
115 import type {SharedStateClient} from 'react/src/ReactSharedInternalsClient';
116
117 // TODO: This is an unfortunate hack. We shouldn't feature detect the internals
118 // like this. It's just that for now we support the same build of the Flight
119 // client both in the RSC environment, in the SSR environments as well as the
120 // browser client. We should probably have a separate RSC build. This is DEV
121 // only though.
122 const ReactSharedInteralsServer: void | SharedStateServer = (React as any)
123 .__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
124 const ReactSharedInternals: SharedStateServer | SharedStateClient =
125 React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ||
126 ReactSharedInteralsServer;
127
128 export type {CallServerCallback, EncodeFormActionCallback};
129
130 interface FlightStreamController {
131 enqueueValue(value: any): void;
132 enqueueModel(json: UninitializedModel): void;
133 close(json: UninitializedModel): void;
134 error(error: Error): void;
135 }
136
137 type UninitializedModel = string;
138
139 type ProfilingResult = {
140 track: number,
141 endTime: number,
142 component: null | ReactComponentInfo,
143 };
144
145 const ROW_ID = 0;
146 const ROW_TAG = 1;
147 const ROW_LENGTH = 2;
148 const ROW_CHUNK_BY_NEWLINE = 3;
149 const ROW_CHUNK_BY_LENGTH = 4;
150
151 type RowParserState = 0 | 1 | 2 | 3 | 4;
152
153 const PENDING = 'pending';
154 // A weak Promise reference. Behaves like PENDING except that when the stream
155 // closes it transitions to HALTED instead of erroring, because the server
156 // may intentionally never emit it. Only used when enableFlightWeakThenables
157 // is on.
158 const PENDING_WEAK = 'pending_weak';
159 const BLOCKED = 'blocked';
160 const RESOLVED_MODEL = 'resolved_model';
161 const RESOLVED_MODULE = 'resolved_module';
162 const INITIALIZED = 'fulfilled';
163 const ERRORED = 'rejected';
164 // Means it never resolves, even when the connection closes. The shared
165 // terminal state of a weak chunk that didn't settle before close, of any
166 // pending chunk at close when partial streams are allowed, and of DEV-only
167 // debug halts.
168 const HALTED = 'halted';
169
170 const __PROTO__ = '__proto__';
171
172 const ObjectPrototype = Object.prototype;
173 const ArrayPrototype = Array.prototype;
174
175 type PendingChunk<T> = {
176 status: 'pending',
177 value: null | Array<InitializationReference | (T => mixed)>,
178 reason: null | Array<InitializationReference | (mixed => mixed)>,
179 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
180 _debugChunk: null | SomeChunk<ReactDebugInfoEntry>, // DEV-only
181 _debugInfo: ReactDebugInfo, // DEV-only
182 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
183 };
184 type PendingWeakChunk<T> = {
185 status: 'pending_weak',
186 value: null | Array<InitializationReference | (T => mixed)>,
187 reason: null | Array<InitializationReference | (mixed => mixed)>,
188 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
189 _debugChunk: null | SomeChunk<ReactDebugInfoEntry>, // DEV-only
190 _debugInfo: ReactDebugInfo, // DEV-only
191 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
192 };
193 type BlockedChunk<T> = {
194 status: 'blocked',
195 value: null | Array<InitializationReference | (T => mixed)>,
196 reason: null | Array<InitializationReference | (mixed => mixed)>,
197 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
198 _debugChunk: null, // DEV-only
199 _debugInfo: ReactDebugInfo, // DEV-only
200 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
201 };
202 type ResolvedModelChunk<T> = {
203 status: 'resolved_model',
204 value: UninitializedModel,
205 reason: Response,
206 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
207 _debugChunk: null | SomeChunk<ReactDebugInfoEntry>, // DEV-only
208 _debugInfo: ReactDebugInfo, // DEV-only
209 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
210 };
211 type ResolvedModuleChunk<T> = {
212 status: 'resolved_module',
213 value: ClientReference<T>,
214 reason: null,
215 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
216 _debugChunk: null, // DEV-only
217 _debugInfo: ReactDebugInfo, // DEV-only
218 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
219 };
220 type InitializedChunk<T> = {
221 status: 'fulfilled',
222 value: T,
223 reason: null | FlightStreamController,
224 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
225 _debugChunk: null, // DEV-only
226 _debugInfo: ReactDebugInfo, // DEV-only
227 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
228 };
229 type InitializedStreamChunk<
230 T: ReadableStream | $AsyncIterable<any, any, void>,
231 > = {
232 status: 'fulfilled',
233 value: T,
234 reason: FlightStreamController,
235 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
236 _debugChunk: null, // DEV-only
237 _debugInfo: ReactDebugInfo, // DEV-only
238 then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,
239 };
240 type ErroredChunk<T> = {
241 status: 'rejected',
242 value: null,
243 reason: mixed,
244 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
245 _debugChunk: null, // DEV-only
246 _debugInfo: ReactDebugInfo, // DEV-only
247 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
248 };
249 type HaltedChunk<T> = {
250 status: 'halted',
251 value: null,
252 reason: null,
253 _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
254 _debugChunk: null, // DEV-only
255 _debugInfo: ReactDebugInfo, // DEV-only
256 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
257 };
258 type SomeChunk<T> =
259 | PendingChunk<T>
260 | PendingWeakChunk<T>
261 | BlockedChunk<T>
262 | ResolvedModelChunk<T>
263 | ResolvedModuleChunk<T>
264 | InitializedChunk<T>
265 | ErroredChunk<T>
266 | HaltedChunk<T>;
267
268 // $FlowFixMe[missing-this-annot]
269 function ReactPromise(status: any, value: any, reason: any) {
270 this.status = status;
271 this.value = value;
272 this.reason = reason;
273 if (enableProfilerTimer && enableComponentPerformanceTrack) {
274 this._children = [];
275 }
276 if (__DEV__) {
277 this._debugChunk = null;
278 this._debugInfo = [];
279 }
280 }
281 // We subclass Promise.prototype so that we get other methods like .catch
282 ReactPromise.prototype = Object.create(Promise.prototype) as any;
283 // TODO: This doesn't return a new Promise chain unlike the real .then
284 function reactPromiseThen<T>(
285 this: SomeChunk<T>,
286 resolve: (value: T) => mixed,
287 reject?: (reason: mixed) => mixed,
288 ) {
289 const chunk: SomeChunk<T> = this;
290 // If we have resolved content, we try to initialize it first which
291 // might put us back into one of the other states.
292 switch (chunk.status) {
293 case RESOLVED_MODEL:
294 initializeModelChunk(chunk);
295 break;
296 case RESOLVED_MODULE:
297 initializeModuleChunk(chunk);
298 break;
299 }
300 if (__DEV__ && enableAsyncDebugInfo) {
301 // Because only native Promises get picked up when we're awaiting we need to wrap
302 // this in a native Promise in DEV. This means that these callbacks are no longer sync
303 // but the lazy initialization is still sync and the .value can be inspected after,
304 // allowing it to be read synchronously anyway.
305 const resolveCallback = resolve;
306 const rejectCallback = reject;
307 const wrapperPromise: Promise<T> = new Promise((res, rej) => {
308 resolve = value => {
309 // $FlowFixMe[prop-missing]
310 wrapperPromise._debugInfo = this._debugInfo;
311 res(value);
312 };
313 reject = reason => {
314 // $FlowFixMe[prop-missing]
315 wrapperPromise._debugInfo = this._debugInfo;
316 rej(reason);
317 };
318 });
319 wrapperPromise.then(resolveCallback, rejectCallback);
320 }
321 // The status might have changed after initialization.
322 switch (chunk.status) {
323 case INITIALIZED:
324 if (typeof resolve === 'function') {
325 resolve(chunk.value);
326 }
327 break;
328 case PENDING:
329 case PENDING_WEAK:
330 case BLOCKED:
331 if (typeof resolve === 'function') {
332 if (chunk.value === null) {
333 chunk.value = [] as Array<InitializationReference | (T => mixed)>;
334 }
335 chunk.value.push(resolve);
336 }
337 if (typeof reject === 'function') {
338 if (chunk.reason === null) {
339 chunk.reason = [] as Array<
340 InitializationReference | (mixed => mixed),
341 >;
342 }
343 chunk.reason.push(reject);
344 }
345 break;
346 case HALTED: {
347 break;
348 }
349 default:
350 if (typeof reject === 'function') {
351 reject(chunk.reason);
352 }
353 break;
354 }
355 }
356 // The shadowing `then` must be defined with `Object.defineProperty` instead of
357 // assignment. Assignment would throw when `Promise.prototype` is frozen (e.g.
358 // by SES lockdown) because assigning over an inherited non-writable property
359 // is rejected.
360 Object.defineProperty(ReactPromise.prototype, 'then', {
361 writable: true,
362 enumerable: true,
363 configurable: true,
364 value: reactPromiseThen,
365 });
366
367 export type FindSourceMapURLCallback = (
368 fileName: string,
369 environmentName: string,
370 ) => null | string;
371
372 export type DebugChannelCallback = (message: string) => void;
373
374 export type DebugChannel = {
375 hasReadable: boolean,
376 callback: DebugChannelCallback | null,
377 };
378
379 type Response = {
380 _bundlerConfig: ServerConsumerModuleMap,
381 _serverReferenceConfig: null | ServerManifest,
382 _moduleLoading: ModuleLoading,
383 _callServer: CallServerCallback,
384 _encodeFormAction: void | EncodeFormActionCallback,
385 _nonce: ?string,
386 _chunks: Map<number, SomeChunk<any>>,
387 _stringDecoder: StringDecoder,
388 _closed: boolean,
389 _closedReason: mixed,
390 _allowPartialStream: boolean,
391 _tempRefs: void | TemporaryReferenceSet, // the set temporary references can be resolved from
392 _timeOrigin: number, // Profiling-only
393 _pendingInitialRender: null | TimeoutID, // Profiling-only,
394 _pendingChunks: number, // DEV-only
395 _weakResponse: WeakResponse, // DEV-only
396 _debugRootOwner?: null | ReactComponentInfo, // DEV-only
397 _debugRootStack?: null | Error, // DEV-only
398 _debugRootTask?: null | ConsoleTask, // DEV-only
399 _debugStartTime: number, // DEV-only
400 _debugEndTime: null | number, // DEV-only
401 _debugIOStarted: boolean, // DEV-only
402 _debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only
403 _debugChannel?: void | DebugChannel, // DEV-only
404 _blockedConsole?: null | SomeChunk<ConsoleEntry>, // DEV-only
405 _replayConsole: boolean, // DEV-only
406 _rootEnvironmentName: string, // DEV-only, the requested environment name.
407 };
408
409 // This indirection exists only to clean up DebugChannel when all Lazy References are GC:ed.
410 // Therefore we only use the indirection in DEV.
411 type WeakResponse = {
412 weak: WeakRef<Response>,
413 response: null | Response, // This is null when there are no pending chunks.
414 };
415
416 export type {WeakResponse as Response};
417
418 function hasGCedResponse(weakResponse: WeakResponse): boolean {
419 return __DEV__ && weakResponse.weak.deref() === undefined;
420 }
421
422 function unwrapWeakResponse(weakResponse: WeakResponse): Response {
423 if (__DEV__) {
424 const response = weakResponse.weak.deref();
425 if (response === undefined) {
426 // eslint-disable-next-line react-internal/prod-error-codes
427 throw new Error(
428 'We did not expect to receive new data after GC:ing the response.',
429 );
430 }
431 return response;
432 } else {
433 return weakResponse as any; // In prod we just use the real Response directly.
434 }
435 }
436
437 function getWeakResponse(response: Response): WeakResponse {
438 if (__DEV__) {
439 return response._weakResponse;
440 } else {
441 return response as any; // In prod we just use the real Response directly.
442 }
443 }
444
445 function closeDebugChannel(debugChannel: DebugChannel): void {
446 if (debugChannel.callback) {
447 debugChannel.callback('');
448 }
449 }
450
451 // If FinalizationRegistry doesn't exist, we cannot use the debugChannel.
452 const debugChannelRegistry =
453 __DEV__ && typeof FinalizationRegistry === 'function'
454 ? new FinalizationRegistry(closeDebugChannel)
455 : null;
456
457 function readChunk<T>(chunk: SomeChunk<T>): T {
458 // If we have resolved content, we try to initialize it first which
459 // might put us back into one of the other states.
460 switch (chunk.status) {
461 case RESOLVED_MODEL:
462 initializeModelChunk(chunk);
463 break;
464 case RESOLVED_MODULE:
465 initializeModuleChunk(chunk);
466 break;
467 }
468 // The status might have changed after initialization.
469 switch (chunk.status) {
470 case INITIALIZED:
471 return chunk.value;
472 case PENDING:
473 case PENDING_WEAK:
474 case BLOCKED:
475 case HALTED:
476 // eslint-disable-next-line no-throw-literal
477 throw chunk as any as Thenable<T>;
478 default:
479 throw chunk.reason;
480 }
481 }
482
483 export function getRoot<T>(weakResponse: WeakResponse): Thenable<T> {
484 const response = unwrapWeakResponse(weakResponse);
485 const chunk = getChunk(response, 0);
486 return chunk as any;
487 }
488
489 function createPendingChunk<T>(response: Response): PendingChunk<T> {
490 if (__DEV__) {
491 // Retain a strong reference to the Response while we wait for the result.
492 if (response._pendingChunks++ === 0) {
493 response._weakResponse.response = response;
494 if (response._pendingInitialRender !== null) {
495 clearTimeout(response._pendingInitialRender);
496 response._pendingInitialRender = null;
497 }
498 }
499 }
500 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
501 return new ReactPromise(PENDING, null, null);
502 }
503
504 function createPendingWeakChunk<T>(response: Response): PendingWeakChunk<T> {
505 // Unlike a regular pending chunk, a weak chunk may never settle, so it
506 // doesn't retain a strong reference to the Response while it waits.
507 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
508 return new ReactPromise(PENDING_WEAK, null, null);
509 }
510
511 function releasePendingChunk(response: Response, chunk: SomeChunk<any>): void {
512 if (__DEV__ && chunk.status === PENDING) {
513 if (--response._pendingChunks === 0) {
514 // We're no longer waiting for any more chunks. We can release the strong reference
515 // to the response. We'll regain it if we ask for any more data later on.
516 response._weakResponse.response = null;
517 // Wait a short period to see if any more chunks get asked for. E.g. by a React render.
518 // These chunks might discover more pending chunks.
519 // If we don't ask for more then we assume that those chunks weren't blocking initial
520 // render and are excluded from the performance track.
521 response._pendingInitialRender = setTimeout(
522 flushInitialRenderPerformance.bind(null, response),
523 100,
524 );
525 }
526 }
527 }
528
529 function createHaltedChunk<T>(response: Response): HaltedChunk<T> {
530 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
531 return new ReactPromise(HALTED, null, null);
532 }
533
534 // Transition a chunk to HALTED: it will never resolve, even when the
535 // connection closes. Clears any listeners to release their closures. Future
536 // .then() calls on HALTED chunks are no-ops.
537 function haltChunk<T>(response: Response, chunk: SomeChunk<T>): void {
538 releasePendingChunk(response, chunk);
539 const haltedChunk: HaltedChunk<T> = chunk as any;
540 haltedChunk.status = HALTED;
541 haltedChunk.value = null;
542 haltedChunk.reason = null;
543 }
544
545 function createBlockedChunk<T>(response: Response): BlockedChunk<T> {
546 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
547 return new ReactPromise(BLOCKED, null, null);
548 }
549
550 function createErrorChunk<T>(
551 response: Response,
552 error: mixed,
553 ): ErroredChunk<T> {
554 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
555 return new ReactPromise(ERRORED, null, error);
556 }
557
558 function filterDebugInfo(
559 response: Response,
560 value: {_debugInfo: ReactDebugInfo, ...},
561 ) {
562 if (response._debugEndTime === null) {
563 // No end time was defined, so we keep all debug info entries.
564 return;
565 }
566
567 // Remove any debug info entries after the defined end time. For async info
568 // that means we're including anything that was awaited before the end time,
569 // but it doesn't need to be resolved before the end time.
570 const relativeEndTime =
571 response._debugEndTime -
572 // $FlowFixMe[prop-missing]
573 performance.timeOrigin;
574 const debugInfo = [];
575 for (let i = 0; i < value._debugInfo.length; i++) {
576 const info = value._debugInfo[i];
577 if (typeof info.time === 'number' && info.time > relativeEndTime) {
578 break;
579 }
580 debugInfo.push(info);
581 }
582 value._debugInfo = debugInfo;
583 }
584
585 function pruneDebugInfoAfterError(
586 response: Response,
587 chunk: ErroredChunk<any>,
588 ): void {
589 if (response._debugEndTime === null) {
590 return;
591 }
592
593 const relativeEndTime =
594 response._debugEndTime -
595 // $FlowFixMe[prop-missing]
596 performance.timeOrigin;
597 const debugInfo = chunk._debugInfo;
598 for (let i = 0; i < debugInfo.length; i++) {
599 const info = debugInfo[i];
600 if (typeof info.time === 'number' && info.time > relativeEndTime) {
601 // This array may already be attached to the Lazy suspended in Fizz.
602 debugInfo.length = i;
603 return;
604 }
605 }
606 }
607
608 function moveDebugInfoFromChunkToInnerValue<T>(
609 chunk: InitializedChunk<T> | InitializedStreamChunk<any>,
610 value: T,
611 ): void {
612 // Remove the debug info from the initialized chunk, and add it to the inner
613 // value instead. This can be a React element, an array, or an uninitialized
614 // Lazy.
615 const resolvedValue = resolveLazy(value);
616 if (
617 typeof resolvedValue === 'object' &&
618 resolvedValue !== null &&
619 (isArray(resolvedValue) ||
620 typeof resolvedValue[ASYNC_ITERATOR] === 'function' ||
621 resolvedValue.$$typeof === REACT_ELEMENT_TYPE ||
622 resolvedValue.$$typeof === REACT_LAZY_TYPE)
623 ) {
624 const debugInfo = chunk._debugInfo.splice(0);
625 if (isArray(resolvedValue._debugInfo)) {
626 // $FlowFixMe[method-unbinding]
627 resolvedValue._debugInfo.unshift.apply(
628 resolvedValue._debugInfo,
629 debugInfo,
630 );
631 } else if (!Object.isFrozen(resolvedValue)) {
632 Object.defineProperty(resolvedValue as any, '_debugInfo', {
633 configurable: false,
634 enumerable: false,
635 writable: true,
636 value: debugInfo,
637 });
638 }
639 // TODO: If the resolved value is a frozen element (e.g. a client-created
640 // element from a temporary reference, or a JSX element exported as a client
641 // reference), server debug info is currently dropped because the element
642 // can't be mutated. We should probably clone the element so each rendering
643 // context gets its own mutable copy with the correct debug info.
644 }
645 }
646
647 function processChunkDebugInfo<T>(
648 response: Response,
649 chunk: InitializedChunk<T> | InitializedStreamChunk<any>,
650 value: T,
651 ): void {
652 filterDebugInfo(response, chunk);
653 moveDebugInfoFromChunkToInnerValue(chunk, value);
654 }
655
656 function wakeChunk<T>(
657 response: Response,
658 listeners: Array<InitializationReference | (T => mixed)>,
659 value: T,
660 chunk: InitializedChunk<T>,
661 ): void {
662 for (let i = 0; i < listeners.length; i++) {
663 const listener = listeners[i];
664 if (typeof listener === 'function') {
665 listener(value);
666 } else {
667 fulfillReference(response, listener, value, chunk);
668 }
669 }
670
671 if (__DEV__) {
672 processChunkDebugInfo(response, chunk, value);
673 }
674 }
675
676 function rejectChunk(
677 response: Response,
678 listeners: Array<InitializationReference | (mixed => mixed)>,
679 error: mixed,
680 ): void {
681 for (let i = 0; i < listeners.length; i++) {
682 const listener = listeners[i];
683 if (typeof listener === 'function') {
684 listener(error);
685 } else {
686 rejectReference(response, listener.handler, error);
687 }
688 }
689 }
690
691 function resolveBlockedCycle<T>(
692 resolvedChunk: SomeChunk<T>,
693 reference: InitializationReference,
694 ): null | InitializationHandler {
695 const referencedChunk = reference.handler.chunk;
696 if (referencedChunk === null) {
697 return null;
698 }
699 if (referencedChunk === resolvedChunk) {
700 // We found the cycle. We can resolve the blocked cycle now.
701 return reference.handler;
702 }
703 const resolveListeners = referencedChunk.value;
704 if (resolveListeners !== null) {
705 for (let i = 0; i < resolveListeners.length; i++) {
706 const listener = resolveListeners[i];
707 if (typeof listener !== 'function') {
708 const foundHandler = resolveBlockedCycle(resolvedChunk, listener);
709 if (foundHandler !== null) {
710 return foundHandler;
711 }
712 }
713 }
714 }
715 return null;
716 }
717
718 function wakeChunkIfInitialized<T>(
719 response: Response,
720 chunk: SomeChunk<T>,
721 resolveListeners: Array<InitializationReference | (T => mixed)>,
722 rejectListeners: null | Array<InitializationReference | (mixed => mixed)>,
723 ): void {
724 switch (chunk.status) {
725 case INITIALIZED:
726 wakeChunk(response, resolveListeners, chunk.value, chunk);
727 break;
728 case BLOCKED:
729 // It is possible that we're blocked on our own chunk if it's a cycle.
730 // Before adding back the listeners to the chunk, let's check if it would
731 // result in a cycle.
732 for (let i = 0; i < resolveListeners.length; i++) {
733 const listener = resolveListeners[i];
734 if (typeof listener !== 'function') {
735 const reference: InitializationReference = listener;
736 const cyclicHandler = resolveBlockedCycle(chunk, reference);
737 if (cyclicHandler !== null) {
738 // This reference points back to this chunk. We can resolve the cycle by
739 // using the value from that handler.
740 fulfillReference(response, reference, cyclicHandler.value, chunk);
741 resolveListeners.splice(i, 1);
742 i--;
743 if (rejectListeners !== null) {
744 const rejectionIdx = rejectListeners.indexOf(reference);
745 if (rejectionIdx !== -1) {
746 rejectListeners.splice(rejectionIdx, 1);
747 }
748 }
749 // The status might have changed after fulfilling the reference.
750 switch ((chunk as SomeChunk<T>).status) {
751 case INITIALIZED:
752 const initializedChunk: InitializedChunk<T> = chunk as any;
753 wakeChunk(
754 response,
755 resolveListeners,
756 initializedChunk.value,
757 initializedChunk,
758 );
759 return;
760 case ERRORED:
761 if (rejectListeners !== null) {
762 rejectChunk(response, rejectListeners, chunk.reason);
763 }
764 return;
765 }
766 }
767 }
768 }
769 // Fallthrough
770 case PENDING:
771 if (chunk.value) {
772 for (let i = 0; i < resolveListeners.length; i++) {
773 chunk.value.push(resolveListeners[i]);
774 }
775 } else {
776 chunk.value = resolveListeners;
777 }
778
779 if (chunk.reason) {
780 if (rejectListeners) {
781 for (let i = 0; i < rejectListeners.length; i++) {
782 chunk.reason.push(rejectListeners[i]);
783 }
784 }
785 } else {
786 chunk.reason = rejectListeners;
787 }
788
789 break;
790 case ERRORED:
791 if (rejectListeners) {
792 rejectChunk(response, rejectListeners, chunk.reason);
793 }
794 break;
795 }
796 }
797
798 function triggerErrorOnChunk<T>(
799 response: Response,
800 chunk: SomeChunk<T>,
801 error: mixed,
802 ): void {
803 if (
804 chunk.status !== PENDING &&
805 chunk.status !== PENDING_WEAK &&
806 chunk.status !== BLOCKED
807 ) {
808 // If we get more data to an already resolved ID, we assume that it's
809 // a stream chunk since any other row shouldn't have more than one entry.
810 const streamChunk: InitializedStreamChunk<any> = chunk as any;
811 const controller = streamChunk.reason;
812 // $FlowFixMe[incompatible-type]: The error method should accept mixed.
813 controller.error(error);
814 return;
815 }
816 releasePendingChunk(response, chunk);
817 const listeners = chunk.reason;
818
819 if (__DEV__ && (chunk.status === PENDING || chunk.status === PENDING_WEAK)) {
820 // Lazily initialize any debug info and block the initializing chunk on any unresolved entries.
821 if (chunk._debugChunk != null) {
822 const prevHandler = initializingHandler;
823 const prevChunk = initializingChunk;
824 initializingHandler = null;
825 const cyclicChunk: BlockedChunk<T> = chunk as any;
826 cyclicChunk.status = BLOCKED;
827 cyclicChunk.value = null;
828 cyclicChunk.reason = null;
829 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
830 initializingChunk = cyclicChunk;
831 }
832 try {
833 initializeDebugChunk(response, chunk);
834 if (initializingHandler !== null) {
835 if (initializingHandler.errored) {
836 // Ignore error parsing debug info, we'll report the original error instead.
837 } else if (initializingHandler.deps > 0) {
838 // TODO: Block the resolution of the error until all the debug info has loaded.
839 // We currently don't have a way to throw an error after all dependencies have
840 // loaded because we currently treat errors as immediately cancelling the handler.
841 }
842 }
843 } finally {
844 initializingHandler = prevHandler;
845 initializingChunk = prevChunk;
846 }
847 }
848 }
849
850 const erroredChunk: ErroredChunk<T> = chunk as any;
851 erroredChunk.status = ERRORED;
852 erroredChunk.reason = error;
853 if (__DEV__) {
854 pruneDebugInfoAfterError(response, erroredChunk);
855 }
856 if (listeners !== null) {
857 rejectChunk(response, listeners, error);
858 }
859 }
860
861 function createResolvedModelChunk<T>(
862 response: Response,
863 value: UninitializedModel,
864 ): ResolvedModelChunk<T> {
865 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
866 return new ReactPromise(RESOLVED_MODEL, value, response);
867 }
868
869 function createResolvedModuleChunk<T>(
870 response: Response,
871 value: ClientReference<T>,
872 ): ResolvedModuleChunk<T> {
873 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
874 return new ReactPromise(RESOLVED_MODULE, value, null);
875 }
876
877 function createInitializedTextChunk(
878 response: Response,
879 value: string,
880 ): InitializedChunk<string> {
881 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
882 return new ReactPromise(INITIALIZED, value, null);
883 }
884
885 function createInitializedBufferChunk(
886 response: Response,
887 value: $ArrayBufferView | ArrayBuffer,
888 ): InitializedChunk<Uint8Array> {
889 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
890 return new ReactPromise(INITIALIZED, value, null);
891 }
892
893 function createInitializedIteratorResultChunk<T>(
894 response: Response,
895 value: T,
896 done: boolean,
897 ): InitializedChunk<IteratorResult<T, T>> {
898 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
899 return new ReactPromise(INITIALIZED, {done: done, value: value}, null);
900 }
901
902 function createInitializedStreamChunk<
903 T: ReadableStream | $AsyncIterable<any, any, void>,
904 >(
905 response: Response,
906 value: T,
907 controller: FlightStreamController,
908 ): InitializedChunk<T> {
909 if (__DEV__) {
910 // Retain a strong reference to the Response while we wait for chunks.
911 if (response._pendingChunks++ === 0) {
912 response._weakResponse.response = response;
913 }
914 }
915 // We use the reason field to stash the controller since we already have that
916 // field. It's a bit of a hack but efficient.
917 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
918 return new ReactPromise(INITIALIZED, value, controller);
919 }
920
921 function createResolvedIteratorResultChunk<T>(
922 response: Response,
923 value: UninitializedModel,
924 done: boolean,
925 ): ResolvedModelChunk<IteratorResult<T, T>> {
926 // To reuse code as much code as possible we add the wrapper element as part of the JSON.
927 const iteratorResultJSON =
928 (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
929 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
930 return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, response);
931 }
932
933 function resolveIteratorResultChunk<T>(
934 response: Response,
935 chunk: SomeChunk<IteratorResult<T, T>>,
936 value: UninitializedModel,
937 done: boolean,
938 ): void {
939 // To reuse code as much code as possible we add the wrapper element as part of the JSON.
940 const iteratorResultJSON =
941 (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
942 resolveModelChunk(response, chunk, iteratorResultJSON);
943 }
944
945 function resolveModelChunk<T>(
946 response: Response,
947 chunk: SomeChunk<T>,
948 value: UninitializedModel,
949 ): void {
950 if (chunk.status !== PENDING && chunk.status !== PENDING_WEAK) {
951 // If we get more data to an already resolved ID, we assume that it's
952 // a stream chunk since any other row shouldn't have more than one entry.
953 const streamChunk: InitializedStreamChunk<any> = chunk as any;
954 const controller = streamChunk.reason;
955 controller.enqueueModel(value);
956 return;
957 }
958 releasePendingChunk(response, chunk);
959 const resolveListeners = chunk.value;
960 const rejectListeners = chunk.reason;
961 const resolvedChunk: ResolvedModelChunk<T> = chunk as any;
962 resolvedChunk.status = RESOLVED_MODEL;
963 resolvedChunk.value = value;
964 resolvedChunk.reason = response;
965 if (resolveListeners !== null) {
966 // This is unfortunate that we're reading this eagerly if
967 // we already have listeners attached since they might no
968 // longer be rendered or might not be the highest pri.
969 initializeModelChunk(resolvedChunk);
970 // The status might have changed after initialization.
971 wakeChunkIfInitialized(response, chunk, resolveListeners, rejectListeners);
972 }
973 }
974
975 function resolveModuleChunk<T>(
976 response: Response,
977 chunk: SomeChunk<T>,
978 value: ClientReference<T>,
979 ): void {
980 if (
981 chunk.status !== PENDING &&
982 chunk.status !== PENDING_WEAK &&
983 chunk.status !== BLOCKED
984 ) {
985 // We already resolved. We didn't expect to see this.
986 return;
987 }
988 releasePendingChunk(response, chunk);
989 const resolveListeners = chunk.value;
990 const rejectListeners = chunk.reason;
991 const resolvedChunk: ResolvedModuleChunk<T> = chunk as any;
992 resolvedChunk.status = RESOLVED_MODULE;
993 resolvedChunk.value = value;
994 resolvedChunk.reason = null;
995 if (__DEV__) {
996 const debugInfo = getModuleDebugInfo(value);
997 if (debugInfo !== null) {
998 // Add to the live set if it was already initialized.
999 // $FlowFixMe[method-unbinding]
1000 resolvedChunk._debugInfo.push.apply(resolvedChunk._debugInfo, debugInfo);
1001 }
1002 }
1003 if (resolveListeners !== null) {
1004 initializeModuleChunk(resolvedChunk);
1005 wakeChunkIfInitialized(response, chunk, resolveListeners, rejectListeners);
1006 }
1007 }
1008
1009 type InitializationReference = {
1010 handler: InitializationHandler,
1011 parentObject: Object,
1012 key: string,
1013 map: (
1014 response: Response,
1015 model: any,
1016 parentObject: Object,
1017 key: string,
1018 ) => any,
1019 path: Array<string>,
1020 isDebug?: boolean, // DEV-only
1021 };
1022 type InitializationHandler = {
1023 parent: null | InitializationHandler,
1024 chunk: null | BlockedChunk<any>,
1025 value: any,
1026 reason: any,
1027 deps: number,
1028 errored: boolean,
1029 };
1030 let initializingHandler: null | InitializationHandler = null;
1031 let initializingChunk: null | BlockedChunk<any> = null;
1032 let isInitializingDebugInfo: boolean = false;
1033
1034 function initializeDebugChunk(
1035 response: Response,
1036 chunk: ResolvedModelChunk<any> | PendingChunk<any> | PendingWeakChunk<any>,
1037 ): void {
1038 const debugChunk = chunk._debugChunk;
1039 if (debugChunk !== null) {
1040 const debugInfo = chunk._debugInfo;
1041 const prevIsInitializingDebugInfo = isInitializingDebugInfo;
1042 isInitializingDebugInfo = true;
1043 try {
1044 if (debugChunk.status === RESOLVED_MODEL) {
1045 // Find the index of this debug info by walking the linked list.
1046 let idx = debugInfo.length;
1047 let c = debugChunk._debugChunk;
1048 while (c !== null) {
1049 if (c.status !== INITIALIZED) {
1050 idx++;
1051 }
1052 c = c._debugChunk;
1053 }
1054 // Initializing the model for the first time.
1055 initializeModelChunk(debugChunk);
1056 const initializedChunk = debugChunk as any as SomeChunk<any>;
1057 switch (initializedChunk.status) {
1058 case INITIALIZED: {
1059 debugInfo[idx] = initializeDebugInfo(
1060 response,
1061 initializedChunk.value,
1062 );
1063 break;
1064 }
1065 case BLOCKED:
1066 case PENDING:
1067 case PENDING_WEAK: {
1068 waitForReference(
1069 initializedChunk,
1070 debugInfo,
1071 '' + idx,
1072 response,
1073 initializeDebugInfo,
1074 [''], // path
1075 true,
1076 );
1077 break;
1078 }
1079 default:
1080 throw initializedChunk.reason;
1081 }
1082 } else {
1083 switch (debugChunk.status) {
1084 case INITIALIZED: {
1085 // Already done.
1086 break;
1087 }
1088 case BLOCKED:
1089 case PENDING:
1090 case PENDING_WEAK: {
1091 // Signal to the caller that we need to wait.
1092 waitForReference(
1093 debugChunk,
1094 {}, // noop, since we'll have already added an entry to debug info
1095 'debug', // noop, but we need it to not be empty string since that indicates the root object
1096 response,
1097 initializeDebugInfo,
1098 [''], // path
1099 true,
1100 );
1101 break;
1102 }
1103 default:
1104 throw debugChunk.reason;
1105 }
1106 }
1107 } catch (error) {
1108 triggerErrorOnChunk(response, chunk, error);
1109 } finally {
1110 isInitializingDebugInfo = prevIsInitializingDebugInfo;
1111 }
1112 }
1113 }
1114
1115 function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
1116 const prevHandler = initializingHandler;
1117 const prevChunk = initializingChunk;
1118 initializingHandler = null;
1119
1120 const resolvedModel = chunk.value;
1121 const response = chunk.reason;
1122
1123 // We go to the BLOCKED state until we've fully resolved this.
1124 // We do this before parsing in case we try to initialize the same chunk
1125 // while parsing the model. Such as in a cyclic reference.
1126 const cyclicChunk: BlockedChunk<T> = chunk as any;
1127 cyclicChunk.status = BLOCKED;
1128 cyclicChunk.value = null;
1129 cyclicChunk.reason = null;
1130
1131 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
1132 initializingChunk = cyclicChunk;
1133 }
1134
1135 if (__DEV__) {
1136 // Initialize any debug info and block the initializing chunk on any
1137 // unresolved entries.
1138 initializeDebugChunk(response, chunk);
1139 // TODO: The chunk might have transitioned to ERRORED now.
1140 // Should we return early if that happens?
1141 }
1142
1143 try {
1144 const value: T = parseModel(response, resolvedModel);
1145 // Invoke any listeners added while resolving this model. I.e. cyclic
1146 // references. This may or may not fully resolve the model depending on
1147 // if they were blocked.
1148 const resolveListeners = cyclicChunk.value;
1149 if (resolveListeners !== null) {
1150 cyclicChunk.value = null;
1151 cyclicChunk.reason = null;
1152 for (let i = 0; i < resolveListeners.length; i++) {
1153 const listener = resolveListeners[i];
1154 if (typeof listener === 'function') {
1155 listener(value);
1156 } else {
1157 fulfillReference(response, listener, value, cyclicChunk);
1158 }
1159 }
1160 }
1161 if (initializingHandler !== null) {
1162 if (initializingHandler.errored) {
1163 throw initializingHandler.reason;
1164 }
1165 if (initializingHandler.deps > 0) {
1166 // We discovered new dependencies on modules that are not yet resolved.
1167 // We have to keep the BLOCKED state until they're resolved.
1168 initializingHandler.value = value;
1169 initializingHandler.chunk = cyclicChunk;
1170 return;
1171 }
1172 }
1173 const initializedChunk: InitializedChunk<T> = chunk as any;
1174 initializedChunk.status = INITIALIZED;
1175 initializedChunk.value = value;
1176 initializedChunk.reason = null;
1177
1178 if (__DEV__) {
1179 processChunkDebugInfo(response, initializedChunk, value);
1180 }
1181 } catch (error) {
1182 const erroredChunk: ErroredChunk<T> = chunk as any;
1183 erroredChunk.status = ERRORED;
1184 erroredChunk.reason = error;
1185 } finally {
1186 initializingHandler = prevHandler;
1187 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
1188 initializingChunk = prevChunk;
1189 }
1190 }
1191 }
1192
1193 function initializeModuleChunk<T>(chunk: ResolvedModuleChunk<T>): void {
1194 try {
1195 const value: T = requireModule(chunk.value);
1196 const initializedChunk: InitializedChunk<T> = chunk as any;
1197 initializedChunk.status = INITIALIZED;
1198 initializedChunk.value = value;
1199 initializedChunk.reason = null;
1200 } catch (error) {
1201 const erroredChunk: ErroredChunk<T> = chunk as any;
1202 erroredChunk.status = ERRORED;
1203 erroredChunk.reason = error;
1204 }
1205 }
1206
1207 // Report that any missing chunks in the model is now going to throw this
1208 // error upon read. Also notify any pending promises.
1209 export function reportGlobalError(
1210 weakResponse: WeakResponse,
1211 error: Error,
1212 ): void {
1213 if (hasGCedResponse(weakResponse)) {
1214 // Ignore close signal if we are not awaiting any more pending chunks.
1215 return;
1216 }
1217 const response = unwrapWeakResponse(weakResponse);
1218 response._closed = true;
1219 response._closedReason = error;
1220 response._chunks.forEach(chunk => {
1221 // If this chunk was already resolved or errored, it won't
1222 // trigger an error but if it wasn't then we need to
1223 // because we won't be getting any new data to resolve it.
1224 if (chunk.status === PENDING) {
1225 triggerErrorOnChunk(response, chunk, error);
1226 } else if (enableFlightWeakThenables && chunk.status === PENDING_WEAK) {
1227 // A weak Promise reference may never be emitted by the server. It
1228 // stays forever pending instead of erroring.
1229 haltChunk(response, chunk);
1230 } else if (chunk.status === INITIALIZED && chunk.reason !== null) {
1231 chunk.reason.error(error);
1232 }
1233 });
1234 if (__DEV__) {
1235 const debugChannel = response._debugChannel;
1236 if (debugChannel !== undefined) {
1237 // If we don't have any more ways of reading data, we don't have to send
1238 // any more neither. So we close the writable side.
1239 closeDebugChannel(debugChannel);
1240 response._debugChannel = undefined;
1241 // Make sure the debug channel is not closed a second time when the
1242 // Response gets GC:ed.
1243 if (debugChannelRegistry !== null) {
1244 debugChannelRegistry.unregister(response);
1245 }
1246 }
1247 }
1248 }
1249
1250 function nullRefGetter() {
1251 if (__DEV__) {
1252 return null;
1253 }
1254 }
1255
1256 function getIOInfoTaskName(ioInfo: ReactIOInfo): string {
1257 return ioInfo.name || 'unknown';
1258 }
1259
1260 function getAsyncInfoTaskName(asyncInfo: ReactAsyncInfo): string {
1261 return 'await ' + getIOInfoTaskName(asyncInfo.awaited);
1262 }
1263
1264 function getServerComponentTaskName(componentInfo: ReactComponentInfo): string {
1265 return '<' + (componentInfo.name || '...') + '>';
1266 }
1267
1268 function getTaskName(type: mixed): string {
1269 if (type === REACT_FRAGMENT_TYPE) {
1270 return '<>';
1271 }
1272 if (typeof type === 'function') {
1273 // This is a function so it must have been a Client Reference that resolved to
1274 // a function. We use "use client" to indicate that this is the boundary into
1275 // the client. There should only be one for any given owner chain.
1276 return '"use client"';
1277 }
1278 if (
1279 typeof type === 'object' &&
1280 type !== null &&
1281 type.$$typeof === REACT_LAZY_TYPE
1282 ) {
1283 if (type._payload instanceof ReactPromise) {
1284 // This is a lazy node created by Flight, i.e. it wraps a chunk. It is
1285 // probably a client reference. We use the "use client" string to indicate
1286 // that this is the boundary into the client. There will only be one for
1287 // any given owner chain.
1288 return '"use client"';
1289 }
1290 // We don't want to eagerly initialize the initializer in DEV mode so we can't
1291 // call it to extract the type so we don't know the type of this component.
1292 return '<...>';
1293 }
1294 try {
1295 const name = getComponentNameFromType(type);
1296 return name ? '<' + name + '>' : '<...>';
1297 } catch (x) {
1298 return '<...>';
1299 }
1300 }
1301
1302 function initializeElement(
1303 response: Response,
1304 element: any,
1305 lazyNode: null | LazyComponent<
1306 React$Element<any>,
1307 SomeChunk<React$Element<any>>,
1308 >,
1309 ): void {
1310 if (!__DEV__) {
1311 return;
1312 }
1313 const stack = element._debugStack;
1314 const owner = element._owner;
1315 if (owner === null) {
1316 element._owner = response._debugRootOwner;
1317 }
1318 let env = response._rootEnvironmentName;
1319 if (owner !== null && owner.env != null) {
1320 // Interestingly we don't actually have the environment name of where
1321 // this JSX was created if it doesn't have an owner but if it does
1322 // it must be the same environment as the owner. We could send it separately
1323 // but it seems a bit unnecessary for this edge case.
1324 env = owner.env;
1325 }
1326 let normalizedStackTrace: null | Error = null;
1327 if (owner === null && response._debugRootStack != null) {
1328 // We override the stack if we override the owner since the stack where the root JSX
1329 // was created on the server isn't very useful but where the request was made is.
1330 normalizedStackTrace = response._debugRootStack;
1331 } else if (stack !== null) {
1332 // We create a fake stack and then create an Error object inside of it.
1333 // This means that the stack trace is now normalized into the native format
1334 // of the browser and the stack frames will have been registered with
1335 // source mapping information.
1336 // This can unfortunately happen within a user space callstack which will
1337 // remain on the stack.
1338 normalizedStackTrace = createFakeJSXCallStackInDEV(response, stack, env);
1339 }
1340 element._debugStack = normalizedStackTrace;
1341 let task: null | ConsoleTask = null;
1342 if (supportsCreateTask && stack !== null) {
1343 const createTaskFn = (console as any).createTask.bind(
1344 console,
1345 getTaskName(element.type),
1346 );
1347 const callStack = buildFakeCallStack(
1348 response,
1349 stack,
1350 env,
1351 false,
1352 createTaskFn,
1353 );
1354 // This owner should ideally have already been initialized to avoid getting
1355 // user stack frames on the stack.
1356 const ownerTask =
1357 owner === null ? null : initializeFakeTask(response, owner);
1358 if (ownerTask === null) {
1359 const rootTask = response._debugRootTask;
1360 if (rootTask != null) {
1361 task = rootTask.run(callStack);
1362 } else {
1363 task = callStack();
1364 }
1365 } else {
1366 task = ownerTask.run(callStack);
1367 }
1368 }
1369 element._debugTask = task;
1370
1371 // This owner should ideally have already been initialized to avoid getting
1372 // user stack frames on the stack.
1373 if (owner !== null) {
1374 initializeFakeStack(response, owner);
1375 }
1376
1377 if (lazyNode !== null) {
1378 // If the lazy node is initialized, we move its debug info to the inner
1379 // value.
1380 if (lazyNode._payload.status === INITIALIZED && lazyNode._debugInfo) {
1381 const debugInfo = lazyNode._debugInfo.splice(0);
1382 if (element._debugInfo) {
1383 // $FlowFixMe[method-unbinding]
1384 element._debugInfo.unshift.apply(element._debugInfo, debugInfo);
1385 } else {
1386 Object.defineProperty(element, '_debugInfo', {
1387 configurable: false,
1388 enumerable: false,
1389 writable: true,
1390 value: debugInfo,
1391 });
1392 }
1393 }
1394 }
1395
1396 // TODO: We should be freezing the element but currently, we might write into
1397 // _debugInfo later. We could move it into _store which remains mutable.
1398 Object.freeze(element.props);
1399 }
1400
1401 function createElement(
1402 response: Response,
1403 type: mixed,
1404 key: mixed,
1405 props: mixed,
1406 owner: ?ReactComponentInfo, // DEV-only
1407 stack: ?ReactStackTrace, // DEV-only
1408 validated: 0 | 1 | 2, // DEV-only
1409 ):
1410 | React$Element<any>
1411 | LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> {
1412 let element: any;
1413 if (__DEV__) {
1414 // `ref` is non-enumerable in dev
1415 element = {
1416 $$typeof: REACT_ELEMENT_TYPE,
1417 type,
1418 key,
1419 props,
1420 _owner: owner === undefined ? null : owner,
1421 } as any;
1422 Object.defineProperty(element, 'ref', {
1423 enumerable: false,
1424 get: nullRefGetter,
1425 });
1426 } else {
1427 element = {
1428 // This tag allows us to uniquely identify this as a React Element
1429 $$typeof: REACT_ELEMENT_TYPE,
1430
1431 type,
1432 key,
1433 ref: null,
1434 props,
1435 } as any;
1436 }
1437
1438 if (__DEV__) {
1439 // We don't really need to add any of these but keeping them for good measure.
1440 // Unfortunately, _store is enumerable in jest matchers so for equality to
1441 // work, I need to keep it or make _store non-enumerable in the other file.
1442 element._store = {} as {
1443 validated?: number,
1444 };
1445 Object.defineProperty(element._store, 'validated', {
1446 configurable: false,
1447 enumerable: false,
1448 writable: true,
1449 value: validated, // Whether the element has already been validated on the server.
1450 });
1451 // debugInfo contains Server Component debug information.
1452 Object.defineProperty(element, '_debugInfo', {
1453 configurable: false,
1454 enumerable: false,
1455 writable: true,
1456 value: null,
1457 });
1458 Object.defineProperty(element, '_debugStack', {
1459 configurable: false,
1460 enumerable: false,
1461 writable: true,
1462 value: stack === undefined ? null : stack,
1463 });
1464 Object.defineProperty(element, '_debugTask', {
1465 configurable: false,
1466 enumerable: false,
1467 writable: true,
1468 value: null,
1469 });
1470 }
1471
1472 if (initializingHandler !== null) {
1473 const handler = initializingHandler;
1474 // We pop the stack to the previous outer handler before leaving the Element.
1475 // This is effectively the complete phase.
1476 initializingHandler = handler.parent;
1477 if (handler.errored) {
1478 // Something errored inside this Element's props. We can turn this Element
1479 // into a Lazy so that we can still render up until that Lazy is rendered.
1480 const erroredChunk: ErroredChunk<React$Element<any>> = createErrorChunk(
1481 response,
1482 handler.reason,
1483 );
1484 if (__DEV__) {
1485 initializeElement(response, element, null);
1486 // Conceptually the error happened inside this Element but right before
1487 // it was rendered. We don't have a client side component to render but
1488 // we can add some DebugInfo to explain that this was conceptually a
1489 // Server side error that errored inside this element. That way any stack
1490 // traces will point to the nearest JSX that errored - e.g. during
1491 // serialization.
1492 const erroredComponent: ReactComponentInfo = {
1493 name: getComponentNameFromType(element.type) || '',
1494 owner: element._owner,
1495 };
1496 // $FlowFixMe[cannot-write]
1497 erroredComponent.debugStack = element._debugStack;
1498 if (supportsCreateTask) {
1499 // $FlowFixMe[cannot-write]
1500 erroredComponent.debugTask = element._debugTask;
1501 }
1502 erroredChunk._debugInfo = [erroredComponent];
1503 }
1504 return createLazyChunkWrapper(erroredChunk, validated);
1505 }
1506 if (handler.deps > 0) {
1507 // We have blocked references inside this Element but we can turn this into
1508 // a Lazy node referencing this Element to let everything around it proceed.
1509 const blockedChunk: BlockedChunk<React$Element<any>> =
1510 createBlockedChunk(response);
1511 handler.value = element;
1512 handler.chunk = blockedChunk;
1513 const lazyNode = createLazyChunkWrapper(blockedChunk, validated);
1514 if (__DEV__) {
1515 // After we have initialized any blocked references, initialize stack etc.
1516 const init = initializeElement.bind(null, response, element, lazyNode);
1517 blockedChunk.then(init, init);
1518 }
1519 return lazyNode;
1520 }
1521 }
1522 if (__DEV__) {
1523 initializeElement(response, element, null);
1524 }
1525
1526 return element;
1527 }
1528
1529 function transferValidation(store: {validated: 0 | 1 | 2}, value: mixed): void {
1530 if (store.validated && typeof value === 'object' && value !== null) {
1531 // Only elements and lazy nodes carry key validation. Any other value, e.g.
1532 // an array of children, needs to have its own items validated instead.
1533 const $$typeof = (value as any).$$typeof;
1534 if ($$typeof === REACT_ELEMENT_TYPE || $$typeof === REACT_LAZY_TYPE) {
1535 const valueStore = (value as any)._store;
1536 if (valueStore && !valueStore.validated) {
1537 valueStore.validated = store.validated;
1538 }
1539 }
1540 }
1541 }
1542
1543 function readChunkAndTransferValidation<T>(
1544 store: {validated: 0 | 1 | 2},
1545 payload: SomeChunk<T>,
1546 ): T {
1547 const value: T = readChunk(payload);
1548 transferValidation(store, value);
1549 return value;
1550 }
1551
1552 function createLazyChunkWrapper<T>(
1553 chunk: SomeChunk<T>,
1554 validated: 0 | 1 | 2, // DEV-only
1555 ): LazyComponent<T, SomeChunk<T>> {
1556 const lazyType: LazyComponent<T, SomeChunk<T>> = {
1557 $$typeof: REACT_LAZY_TYPE,
1558 _payload: chunk,
1559 _init: readChunk,
1560 };
1561 if (__DEV__) {
1562 // Forward the live array
1563 lazyType._debugInfo = chunk._debugInfo;
1564 // Initialize a store for key validation by the JSX runtime. It can only
1565 // validate the lazy node itself, because the value it refers to might not
1566 // exist yet at that point, e.g. if it's an outlined row that hasn't been
1567 // initialized. So the validation is transferred to the value when the lazy
1568 // node is unwrapped. If the value is another lazy node, unwrapping that one
1569 // forwards the validation further.
1570 const store = {validated: validated};
1571 lazyType._store = store;
1572 // $FlowFixMe[incompatible-type] `bind` loses the type argument.
1573 lazyType._init = readChunkAndTransferValidation.bind(null, store);
1574 }
1575 return lazyType;
1576 }
1577
1578 function getChunk(response: Response, id: number): SomeChunk<any> {
1579 const chunks = response._chunks;
1580 let chunk = chunks.get(id);
1581 if (!chunk) {
1582 if (response._closed) {
1583 if (response._allowPartialStream) {
1584 // For partial streams, chunks accessed after close should be HALTED
1585 // (never resolve).
1586 chunk = createHaltedChunk(response);
1587 } else {
1588 // We have already errored the response and we're not going to get
1589 // anything more streaming in so this will immediately error.
1590 chunk = createErrorChunk(response, response._closedReason);
1591 }
1592 } else {
1593 chunk = createPendingChunk(response);
1594 }
1595 chunks.set(id, chunk);
1596 }
1597 return chunk;
1598 }
1599
1600 // Like getChunk, but for weak Promise references. The server may never emit
1601 // the row for a weak reference, so an unresolved weak chunk halts (stays
1602 // forever pending) instead of erroring when the stream closes.
1603 function getWeakChunk(response: Response, id: number): SomeChunk<any> {
1604 const chunks = response._chunks;
1605 let chunk = chunks.get(id);
1606 if (!chunk) {
1607 if (response._closed) {
1608 // The stream already closed without emitting this row, so it will
1609 // never resolve.
1610 chunk = createHaltedChunk(response);
1611 } else {
1612 chunk = createPendingWeakChunk(response);
1613 }
1614 chunks.set(id, chunk);
1615 }
1616 return chunk;
1617 }
1618
1619 function fulfillReference(
1620 response: Response,
1621 reference: InitializationReference,
1622 value: any,
1623 fulfilledChunk: SomeChunk<any>,
1624 ): void {
1625 const {handler, parentObject, key, map, path} = reference;
1626
1627 try {
1628 for (let i = 1; i < path.length; i++) {
1629 while (
1630 typeof value === 'object' &&
1631 value !== null &&
1632 value.$$typeof === REACT_LAZY_TYPE
1633 ) {
1634 // We never expect to see a Lazy node on this path because we encode those as
1635 // separate models. This must mean that we have inserted an extra lazy node
1636 // e.g. to replace a blocked element. We must instead look for it inside.
1637 const referencedChunk: SomeChunk<any> = value._payload;
1638 if (referencedChunk === handler.chunk) {
1639 // This is a reference to the thing we're currently blocking. We can peak
1640 // inside of it to get the value.
1641 value = handler.value;
1642 continue;
1643 } else {
1644 switch (referencedChunk.status) {
1645 case RESOLVED_MODEL:
1646 initializeModelChunk(referencedChunk);
1647 break;
1648 case RESOLVED_MODULE:
1649 initializeModuleChunk(referencedChunk);
1650 break;
1651 }
1652 switch (referencedChunk.status) {
1653 case INITIALIZED: {
1654 value = referencedChunk.value;
1655 continue;
1656 }
1657 case BLOCKED: {
1658 // It is possible that we're blocked on our own chunk if it's a cycle.
1659 // Before adding the listener to the inner chunk, let's check if it would
1660 // result in a cycle.
1661 const cyclicHandler = resolveBlockedCycle(
1662 referencedChunk,
1663 reference,
1664 );
1665 if (cyclicHandler !== null) {
1666 // This reference points back to this chunk. We can resolve the cycle by
1667 // using the value from that handler.
1668 value = cyclicHandler.value;
1669 continue;
1670 }
1671 // Fallthrough
1672 }
1673 case PENDING:
1674 case PENDING_WEAK: {
1675 // If we're not yet initialized we need to skip what we've already drilled
1676 // through and then wait for the next value to become available.
1677 path.splice(0, i - 1);
1678 // Add "listener" to our new chunk dependency.
1679 if (referencedChunk.value === null) {
1680 referencedChunk.value = [reference];
1681 } else {
1682 referencedChunk.value.push(reference);
1683 }
1684 if (referencedChunk.reason === null) {
1685 referencedChunk.reason = [reference];
1686 } else {
1687 referencedChunk.reason.push(reference);
1688 }
1689 return;
1690 }
1691 case HALTED: {
1692 // Do nothing. We couldn't fulfill.
1693 // TODO: Mark downstreams as halted too.
1694 return;
1695 }
1696 default: {
1697 rejectReference(
1698 response,
1699 reference.handler,
1700 referencedChunk.reason,
1701 );
1702 return;
1703 }
1704 }
1705 }
1706 }
1707 const name = path[i];
1708 if (
1709 typeof value === 'object' &&
1710 value !== null &&
1711 hasOwnProperty.call(value, name)
1712 ) {
1713 value = value[name];
1714 } else {
1715 throw new Error('Invalid reference.');
1716 }
1717 }
1718
1719 while (
1720 typeof value === 'object' &&
1721 value !== null &&
1722 value.$$typeof === REACT_LAZY_TYPE
1723 ) {
1724 // If what we're referencing is a Lazy it must be because we inserted one as a virtual node
1725 // while it was blocked by other data. If it's no longer blocked, we can unwrap it.
1726 const referencedChunk: SomeChunk<any> = value._payload;
1727 if (referencedChunk === handler.chunk) {
1728 // This is a reference to the thing we're currently blocking. We can peak
1729 // inside of it to get the value.
1730 value = handler.value;
1731 continue;
1732 } else {
1733 switch (referencedChunk.status) {
1734 case RESOLVED_MODEL:
1735 initializeModelChunk(referencedChunk);
1736 break;
1737 case RESOLVED_MODULE:
1738 initializeModuleChunk(referencedChunk);
1739 break;
1740 }
1741 switch (referencedChunk.status) {
1742 case INITIALIZED: {
1743 value = referencedChunk.value;
1744 continue;
1745 }
1746 }
1747 }
1748 break;
1749 }
1750
1751 const mappedValue = map(response, value, parentObject, key);
1752 if (key !== __PROTO__) {
1753 parentObject[key] = mappedValue;
1754 }
1755
1756 // If this is the root object for a model reference, where `handler.value`
1757 // is a stale `null`, the resolved value can be used directly.
1758 if (key === '' && handler.value === null) {
1759 handler.value = mappedValue;
1760 }
1761
1762 // If the parent object is an unparsed React element tuple, we also need to
1763 // update the props and owner of the parsed element object (i.e.
1764 // handler.value).
1765 if (
1766 parentObject[0] === REACT_ELEMENT_TYPE &&
1767 typeof handler.value === 'object' &&
1768 handler.value !== null &&
1769 handler.value.$$typeof === REACT_ELEMENT_TYPE
1770 ) {
1771 const element: any = handler.value;
1772 switch (key) {
1773 case '3':
1774 if (__DEV__) {
1775 transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
1776 }
1777 element.props = mappedValue;
1778 break;
1779 case '4':
1780 // This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.
1781 if (__DEV__) {
1782 element._owner = mappedValue;
1783 }
1784 break;
1785 case '5':
1786 // This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.
1787 if (__DEV__) {
1788 element._debugStack = mappedValue;
1789 }
1790 break;
1791 default:
1792 if (__DEV__) {
1793 transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
1794 }
1795 break;
1796 }
1797 } else if (__DEV__ && !reference.isDebug) {
1798 transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
1799 }
1800 } catch (error) {
1801 rejectReference(response, reference.handler, error);
1802 return;
1803 }
1804
1805 handler.deps--;
1806
1807 if (handler.deps === 0) {
1808 const chunk = handler.chunk;
1809 if (chunk === null || chunk.status !== BLOCKED) {
1810 return;
1811 }
1812 const resolveListeners = chunk.value;
1813 const initializedChunk: InitializedChunk<any> = chunk as any;
1814 initializedChunk.status = INITIALIZED;
1815 initializedChunk.value = handler.value;
1816 initializedChunk.reason = handler.reason; // Used by streaming chunks
1817 if (resolveListeners !== null) {
1818 wakeChunk(response, resolveListeners, handler.value, initializedChunk);
1819 } else {
1820 if (__DEV__) {
1821 processChunkDebugInfo(response, initializedChunk, handler.value);
1822 }
1823 }
1824 }
1825 }
1826
1827 function rejectReference(
1828 response: Response,
1829 handler: InitializationHandler,
1830 error: mixed,
1831 ): void {
1832 if (handler.errored) {
1833 // We've already errored. We could instead build up an AggregateError
1834 // but if there are multiple errors we just take the first one like
1835 // Promise.all.
1836 return;
1837 }
1838 const blockedValue = handler.value;
1839 handler.errored = true;
1840 handler.value = null;
1841 handler.reason = error;
1842 const chunk = handler.chunk;
1843 if (chunk === null || chunk.status !== BLOCKED) {
1844 return;
1845 }
1846
1847 if (__DEV__) {
1848 if (
1849 typeof blockedValue === 'object' &&
1850 blockedValue !== null &&
1851 blockedValue.$$typeof === REACT_ELEMENT_TYPE
1852 ) {
1853 const element = blockedValue;
1854 // Conceptually the error happened inside this Element but right before
1855 // it was rendered. We don't have a client side component to render but
1856 // we can add some DebugInfo to explain that this was conceptually a
1857 // Server side error that errored inside this element. That way any stack
1858 // traces will point to the nearest JSX that errored - e.g. during
1859 // serialization.
1860 const erroredComponent: ReactComponentInfo = {
1861 name: getComponentNameFromType(element.type) || '',
1862 owner: element._owner,
1863 };
1864 // $FlowFixMe[cannot-write]
1865 erroredComponent.debugStack = element._debugStack;
1866 if (supportsCreateTask) {
1867 // $FlowFixMe[cannot-write]
1868 erroredComponent.debugTask = element._debugTask;
1869 }
1870 chunk._debugInfo.push(erroredComponent);
1871 }
1872 }
1873
1874 triggerErrorOnChunk(response, chunk, error);
1875 }
1876
1877 function waitForReference<T>(
1878 referencedChunk: PendingChunk<T> | PendingWeakChunk<T> | BlockedChunk<T>,
1879 parentObject: Object,
1880 key: string,
1881 response: Response,
1882 map: (response: Response, model: any, parentObject: Object, key: string) => T,
1883 path: Array<string>,
1884 isAwaitingDebugInfo: boolean, // DEV-only
1885 ): T {
1886 if (
1887 __DEV__ &&
1888 (response._debugChannel === undefined ||
1889 !response._debugChannel.hasReadable)
1890 ) {
1891 if (
1892 referencedChunk.status === PENDING &&
1893 parentObject[0] === REACT_ELEMENT_TYPE &&
1894 (key === '4' || key === '5')
1895 ) {
1896 // If the parent object is an unparsed React element tuple, and this is a reference
1897 // to the owner or debug stack. Then we expect the chunk to have been emitted earlier
1898 // in the stream. It might be blocked on other things but chunk should no longer be pending.
1899 // If it's still pending that suggests that it was referencing an object in the debug
1900 // channel, but no debug channel was wired up so it's missing. In this case we can just
1901 // drop the debug info instead of halting the whole stream.
1902 return null as any;
1903 }
1904 }
1905
1906 let handler: InitializationHandler;
1907 if (initializingHandler) {
1908 handler = initializingHandler;
1909 handler.deps++;
1910 } else {
1911 handler = initializingHandler = {
1912 parent: null,
1913 chunk: null,
1914 value: null,
1915 reason: null,
1916 deps: 1,
1917 errored: false,
1918 };
1919 }
1920
1921 const reference: InitializationReference = {
1922 handler,
1923 parentObject,
1924 key,
1925 map,
1926 path,
1927 };
1928 if (__DEV__) {
1929 reference.isDebug = isAwaitingDebugInfo;
1930 }
1931
1932 // Add "listener".
1933 if (referencedChunk.value === null) {
1934 referencedChunk.value = [reference];
1935 } else {
1936 referencedChunk.value.push(reference);
1937 }
1938 if (referencedChunk.reason === null) {
1939 referencedChunk.reason = [reference];
1940 } else {
1941 referencedChunk.reason.push(reference);
1942 }
1943
1944 // Return a place holder value for now.
1945 return null as any;
1946 }
1947
1948 function loadServerReference<A: Iterable<any>, T>(
1949 response: Response,
1950 metaData: {
1951 id: any,
1952 bound: null | Thenable<Array<any>>,
1953 name?: string, // DEV-only
1954 env?: string, // DEV-only
1955 location?: ReactFunctionLocation, // DEV-only
1956 },
1957 parentObject: Object,
1958 key: string,
1959 ): (...A) => Promise<T> {
1960 if (!response._serverReferenceConfig) {
1961 // In the normal case, we can't load this Server Reference in the current environment and
1962 // we just return a proxy to it.
1963 return createBoundServerReference(
1964 metaData,
1965 response._callServer,
1966 response._encodeFormAction,
1967 __DEV__ ? response._debugFindSourceMapURL : undefined,
1968 );
1969 }
1970 // If we have a module mapping we can load the real version of this Server Reference.
1971 const serverReference: ClientReference<T> =
1972 resolveServerReference<$FlowFixMe>(
1973 response._serverReferenceConfig,
1974 metaData.id,
1975 );
1976
1977 let promise: null | Thenable<any> = preloadModule(serverReference);
1978 if (!promise) {
1979 if (!metaData.bound) {
1980 const resolvedValue = requireModule(serverReference) as any;
1981 registerBoundServerReference(
1982 resolvedValue,
1983 metaData.id,
1984 metaData.bound,
1985 response._encodeFormAction,
1986 );
1987 return resolvedValue;
1988 } else {
1989 promise = Promise.resolve(metaData.bound);
1990 }
1991 } else if (metaData.bound) {
1992 promise = Promise.all([promise, metaData.bound]);
1993 }
1994
1995 let handler: InitializationHandler;
1996 if (initializingHandler) {
1997 handler = initializingHandler;
1998 handler.deps++;
1999 } else {
2000 handler = initializingHandler = {
2001 parent: null,
2002 chunk: null,
2003 value: null,
2004 reason: null,
2005 deps: 1,
2006 errored: false,
2007 };
2008 }
2009
2010 function fulfill(): void {
2011 let resolvedValue = requireModule(serverReference) as any;
2012
2013 if (metaData.bound) {
2014 // This promise is coming from us and should have initilialized by now.
2015 const boundArgs: Array<any> = (metaData.bound as any).value.slice(0);
2016 boundArgs.unshift(null); // this
2017 resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs);
2018 }
2019
2020 registerBoundServerReference(
2021 resolvedValue,
2022 metaData.id,
2023 metaData.bound,
2024 response._encodeFormAction,
2025 );
2026
2027 if (key !== __PROTO__) {
2028 parentObject[key] = resolvedValue;
2029 }
2030
2031 // If this is the root object for a model reference, where `handler.value`
2032 // is a stale `null`, the resolved value can be used directly.
2033 if (key === '' && handler.value === null) {
2034 handler.value = resolvedValue;
2035 }
2036
2037 // If the parent object is an unparsed React element tuple, we also need to
2038 // update the props and owner of the parsed element object (i.e.
2039 // handler.value).
2040 if (
2041 parentObject[0] === REACT_ELEMENT_TYPE &&
2042 typeof handler.value === 'object' &&
2043 handler.value !== null &&
2044 handler.value.$$typeof === REACT_ELEMENT_TYPE
2045 ) {
2046 const element: any = handler.value;
2047 switch (key) {
2048 case '3':
2049 element.props = resolvedValue;
2050 break;
2051 case '4':
2052 if (__DEV__) {
2053 element._owner = resolvedValue;
2054 }
2055 break;
2056 }
2057 }
2058
2059 handler.deps--;
2060
2061 if (handler.deps === 0) {
2062 const chunk = handler.chunk;
2063 if (chunk === null || chunk.status !== BLOCKED) {
2064 return;
2065 }
2066 const resolveListeners = chunk.value;
2067 const initializedChunk: InitializedChunk<T> = chunk as any;
2068 initializedChunk.status = INITIALIZED;
2069 initializedChunk.value = handler.value;
2070 initializedChunk.reason = null;
2071 if (resolveListeners !== null) {
2072 wakeChunk(response, resolveListeners, handler.value, initializedChunk);
2073 } else {
2074 if (__DEV__) {
2075 processChunkDebugInfo(response, initializedChunk, handler.value);
2076 }
2077 }
2078 }
2079 }
2080
2081 function reject(error: mixed): void {
2082 if (handler.errored) {
2083 // We've already errored. We could instead build up an AggregateError
2084 // but if there are multiple errors we just take the first one like
2085 // Promise.all.
2086 return;
2087 }
2088 const blockedValue = handler.value;
2089 handler.errored = true;
2090 handler.value = null;
2091 handler.reason = error;
2092 const chunk = handler.chunk;
2093 if (chunk === null || chunk.status !== BLOCKED) {
2094 return;
2095 }
2096
2097 if (__DEV__) {
2098 if (
2099 typeof blockedValue === 'object' &&
2100 blockedValue !== null &&
2101 blockedValue.$$typeof === REACT_ELEMENT_TYPE
2102 ) {
2103 const element = blockedValue;
2104 // Conceptually the error happened inside this Element but right before
2105 // it was rendered. We don't have a client side component to render but
2106 // we can add some DebugInfo to explain that this was conceptually a
2107 // Server side error that errored inside this element. That way any stack
2108 // traces will point to the nearest JSX that errored - e.g. during
2109 // serialization.
2110 const erroredComponent: ReactComponentInfo = {
2111 name: getComponentNameFromType(element.type) || '',
2112 owner: element._owner,
2113 };
2114 // $FlowFixMe[cannot-write]
2115 erroredComponent.debugStack = element._debugStack;
2116 if (supportsCreateTask) {
2117 // $FlowFixMe[cannot-write]
2118 erroredComponent.debugTask = element._debugTask;
2119 }
2120 chunk._debugInfo.push(erroredComponent);
2121 }
2122 }
2123
2124 triggerErrorOnChunk(response, chunk, error);
2125 }
2126
2127 promise.then(fulfill, reject);
2128
2129 // Return a place holder value for now.
2130 return null as any;
2131 }
2132
2133 function resolveLazy(value: any): mixed {
2134 while (
2135 typeof value === 'object' &&
2136 value !== null &&
2137 value.$$typeof === REACT_LAZY_TYPE
2138 ) {
2139 const payload: SomeChunk<any> = value._payload;
2140 if (payload.status === INITIALIZED) {
2141 value = payload.value;
2142 continue;
2143 }
2144 break;
2145 }
2146
2147 return value;
2148 }
2149
2150 function transferReferencedDebugInfo(
2151 parentChunk: null | SomeChunk<any>,
2152 referencedChunk: SomeChunk<any>,
2153 ): void {
2154 if (__DEV__) {
2155 // We add the debug info to the initializing chunk since the resolution of
2156 // that promise is also blocked by the referenced debug info. By adding it
2157 // to both we can track it even if the array/element/lazy is extracted, or
2158 // if the root is rendered as is.
2159 if (parentChunk !== null) {
2160 const referencedDebugInfo = referencedChunk._debugInfo;
2161 const parentDebugInfo = parentChunk._debugInfo;
2162 for (let i = 0; i < referencedDebugInfo.length; ++i) {
2163 const debugInfoEntry = referencedDebugInfo[i];
2164 if (debugInfoEntry.name != null) {
2165 debugInfoEntry as ReactComponentInfo;
2166 // We're not transferring Component info since we use Component info
2167 // in Debug info to fill in gaps between Fibers for the parent stack.
2168 } else {
2169 parentDebugInfo.push(debugInfoEntry);
2170 }
2171 }
2172 }
2173 }
2174 }
2175
2176 // Most references have no path, so they can all share the same empty array.
2177 // It's never mutated because only paths with entries get spliced in place.
2178 const EMPTY_REFERENCE_PATH: Array<string> = [];
2179
2180 function getOutlinedModel<T>(
2181 response: Response,
2182 reference: string,
2183 parentObject: Object,
2184 key: string,
2185 map: (response: Response, model: any, parentObject: Object, key: string) => T,
2186 ): T {
2187 // parseInt stops at the ':' so we only need to split when there's a path.
2188 const id = parseInt(reference, 16);
2189 const path =
2190 reference.indexOf(':') === -1 ? EMPTY_REFERENCE_PATH : reference.split(':');
2191 const chunk = getChunk(response, id);
2192 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2193 if (initializingChunk !== null && isArray(initializingChunk._children)) {
2194 initializingChunk._children.push(chunk);
2195 }
2196 }
2197 switch (chunk.status) {
2198 case RESOLVED_MODEL:
2199 initializeModelChunk(chunk);
2200 break;
2201 case RESOLVED_MODULE:
2202 initializeModuleChunk(chunk);
2203 break;
2204 }
2205 // The status might have changed after initialization.
2206 switch (chunk.status) {
2207 case INITIALIZED:
2208 let value = chunk.value;
2209 for (let i = 1; i < path.length; i++) {
2210 while (
2211 typeof value === 'object' &&
2212 value !== null &&
2213 value.$$typeof === REACT_LAZY_TYPE
2214 ) {
2215 const referencedChunk: SomeChunk<any> = value._payload;
2216 switch (referencedChunk.status) {
2217 case RESOLVED_MODEL:
2218 initializeModelChunk(referencedChunk);
2219 break;
2220 case RESOLVED_MODULE:
2221 initializeModuleChunk(referencedChunk);
2222 break;
2223 }
2224 switch (referencedChunk.status) {
2225 case INITIALIZED: {
2226 value = referencedChunk.value;
2227 break;
2228 }
2229 case BLOCKED:
2230 case PENDING:
2231 case PENDING_WEAK: {
2232 return waitForReference(
2233 referencedChunk,
2234 parentObject,
2235 key,
2236 response,
2237 map,
2238 path.slice(i - 1),
2239 isInitializingDebugInfo,
2240 );
2241 }
2242 case HALTED: {
2243 // Add a dependency that will never resolve.
2244 // TODO: Mark downstreams as halted too.
2245 let handler: InitializationHandler;
2246 if (initializingHandler) {
2247 handler = initializingHandler;
2248 handler.deps++;
2249 } else {
2250 handler = initializingHandler = {
2251 parent: null,
2252 chunk: null,
2253 value: null,
2254 reason: null,
2255 deps: 1,
2256 errored: false,
2257 };
2258 }
2259 return null as any;
2260 }
2261 default: {
2262 // This is an error. Instead of erroring directly, we're going to encode this on
2263 // an initialization handler so that we can catch it at the nearest Element.
2264 if (initializingHandler) {
2265 initializingHandler.errored = true;
2266 initializingHandler.value = null;
2267 initializingHandler.reason = referencedChunk.reason;
2268 } else {
2269 initializingHandler = {
2270 parent: null,
2271 chunk: null,
2272 value: null,
2273 reason: referencedChunk.reason,
2274 deps: 0,
2275 errored: true,
2276 };
2277 }
2278 return null as any;
2279 }
2280 }
2281 }
2282 const name = path[i];
2283 if (
2284 typeof value === 'object' &&
2285 value !== null &&
2286 (getPrototypeOf(value) === ObjectPrototype ||
2287 getPrototypeOf(value) === ArrayPrototype) &&
2288 hasOwnProperty.call(value, name)
2289 ) {
2290 value = value[name];
2291 } else {
2292 throw new Error('Invalid reference.');
2293 }
2294 }
2295
2296 while (
2297 typeof value === 'object' &&
2298 value !== null &&
2299 value.$$typeof === REACT_LAZY_TYPE
2300 ) {
2301 // If what we're referencing is a Lazy it must be because we inserted one as a virtual node
2302 // while it was blocked by other data. If it's no longer blocked, we can unwrap it.
2303 const referencedChunk: SomeChunk<any> = value._payload;
2304 switch (referencedChunk.status) {
2305 case RESOLVED_MODEL:
2306 initializeModelChunk(referencedChunk);
2307 break;
2308 case RESOLVED_MODULE:
2309 initializeModuleChunk(referencedChunk);
2310 break;
2311 }
2312 switch (referencedChunk.status) {
2313 case INITIALIZED: {
2314 value = referencedChunk.value;
2315 continue;
2316 }
2317 }
2318 break;
2319 }
2320
2321 const chunkValue = map(response, value, parentObject, key);
2322 if (__DEV__) {
2323 if (
2324 parentObject[0] === REACT_ELEMENT_TYPE &&
2325 (key === '4' || key === '5')
2326 ) {
2327 // If we're resolving the "owner" or "stack" slot of an Element array,
2328 // we don't call transferReferencedDebugInfo because this reference is
2329 // to a debug chunk.
2330 } else if (isInitializingDebugInfo) {
2331 // If we're resolving references as part of debug info resolution, we
2332 // don't call transferReferencedDebugInfo because these references are
2333 // to debug chunks.
2334 } else {
2335 transferReferencedDebugInfo(initializingChunk, chunk);
2336 }
2337 }
2338 return chunkValue;
2339 case PENDING:
2340 case PENDING_WEAK:
2341 case BLOCKED:
2342 return waitForReference(
2343 chunk,
2344 parentObject,
2345 key,
2346 response,
2347 map,
2348 path,
2349 isInitializingDebugInfo,
2350 );
2351 case HALTED: {
2352 // Add a dependency that will never resolve.
2353 // TODO: Mark downstreams as halted too.
2354 let handler: InitializationHandler;
2355 if (initializingHandler) {
2356 handler = initializingHandler;
2357 handler.deps++;
2358 } else {
2359 handler = initializingHandler = {
2360 parent: null,
2361 chunk: null,
2362 value: null,
2363 reason: null,
2364 deps: 1,
2365 errored: false,
2366 };
2367 }
2368 return null as any;
2369 }
2370 default:
2371 // This is an error. Instead of erroring directly, we're going to encode this on
2372 // an initialization handler so that we can catch it at the nearest Element.
2373 if (initializingHandler) {
2374 initializingHandler.errored = true;
2375 initializingHandler.value = null;
2376 initializingHandler.reason = chunk.reason;
2377 } else {
2378 initializingHandler = {
2379 parent: null,
2380 chunk: null,
2381 value: null,
2382 reason: chunk.reason,
2383 deps: 0,
2384 errored: true,
2385 };
2386 }
2387 // Placeholder
2388 return null as any;
2389 }
2390 }
2391
2392 function createMap(
2393 response: Response,
2394 model: Array<[any, any]>,
2395 ): Map<any, any> {
2396 return new Map(model);
2397 }
2398
2399 function createSet(response: Response, model: Array<any>): Set<any> {
2400 return new Set(model);
2401 }
2402
2403 function createBlob(response: Response, model: Array<any>): Blob {
2404 return new Blob(model.slice(1), {type: model[0]});
2405 }
2406
2407 function createFormData(
2408 response: Response,
2409 model: Array<[any, any]>,
2410 ): FormData {
2411 const formData = new FormData();
2412 for (let i = 0; i < model.length; i++) {
2413 formData.append(model[i][0], model[i][1]);
2414 }
2415 return formData;
2416 }
2417
2418 function applyConstructor(
2419 response: Response,
2420 model: Function,
2421 parentObject: Object,
2422 key: string,
2423 ): void {
2424 Object.setPrototypeOf(parentObject, model.prototype);
2425 // Delete the property. It was just a placeholder.
2426 return undefined;
2427 }
2428
2429 function defineLazyGetter<T>(
2430 response: Response,
2431 chunk: SomeChunk<T>,
2432 parentObject: Object,
2433 key: string,
2434 ): any {
2435 // We don't immediately initialize it even if it's resolved.
2436 // Instead, we wait for the getter to get accessed.
2437 if (key !== __PROTO__) {
2438 Object.defineProperty(parentObject, key, {
2439 get: function () {
2440 if (chunk.status === RESOLVED_MODEL) {
2441 // If it was now resolved, then we initialize it. This may then discover
2442 // a new set of lazy references that are then asked for eagerly in case
2443 // we get that deep.
2444 initializeModelChunk(chunk);
2445 }
2446 switch (chunk.status) {
2447 case INITIALIZED: {
2448 return chunk.value;
2449 }
2450 case ERRORED:
2451 throw chunk.reason;
2452 }
2453 // Otherwise, we didn't have enough time to load the object before it was
2454 // accessed or the connection closed. So we just log that it was omitted.
2455 // TODO: We should ideally throw here to indicate a difference.
2456 return OMITTED_PROP_ERROR;
2457 },
2458 // no-op: the walk function may try to reassign this property after
2459 // parseModelString returns. With the JSON.parse reviver, the engine's
2460 // internal CreateDataProperty silently failed. We use a no-op setter
2461 // to match that behavior in strict mode.
2462 set: function () {},
2463 enumerable: true,
2464 configurable: false,
2465 });
2466 }
2467 return null;
2468 }
2469
2470 function extractIterator(response: Response, model: Array<any>): Iterator<any> {
2471 // $FlowFixMe[incompatible-use]: This uses raw Symbols because we're extracting from a native array.
2472 return model[Symbol.iterator]();
2473 }
2474
2475 function createModel(response: Response, model: any): any {
2476 return model;
2477 }
2478
2479 const mightHaveStaticConstructor = /\bclass\b.*\bstatic\b/;
2480
2481 function getInferredFunctionApproximate(code: string): () => void {
2482 let slicedCode;
2483 if (code.startsWith('Object.defineProperty(')) {
2484 slicedCode = code.slice('Object.defineProperty('.length);
2485 } else if (code.startsWith('(')) {
2486 slicedCode = code.slice(1);
2487 } else {
2488 slicedCode = code;
2489 }
2490 if (slicedCode.startsWith('async function')) {
2491 const idx = slicedCode.indexOf('(', 14);
2492 if (idx !== -1) {
2493 const name = slicedCode.slice(14, idx).trim();
2494 // eslint-disable-next-line no-eval
2495 return (0, eval)('({' + JSON.stringify(name) + ':async function(){}})')[
2496 name
2497 ];
2498 }
2499 } else if (slicedCode.startsWith('function')) {
2500 const idx = slicedCode.indexOf('(', 8);
2501 if (idx !== -1) {
2502 const name = slicedCode.slice(8, idx).trim();
2503 // eslint-disable-next-line no-eval
2504 return (0, eval)('({' + JSON.stringify(name) + ':function(){}})')[name];
2505 }
2506 } else if (slicedCode.startsWith('class')) {
2507 const idx = slicedCode.indexOf('{', 5);
2508 if (idx !== -1) {
2509 const name = slicedCode.slice(5, idx).trim();
2510 // eslint-disable-next-line no-eval
2511 return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[name];
2512 }
2513 }
2514 return function () {};
2515 }
2516
2517 function parseModelString(
2518 response: Response,
2519 parentObject: Object,
2520 key: string,
2521 value: string,
2522 ): any {
2523 if (value[0] === '$') {
2524 if (value === '$') {
2525 // A very common symbol.
2526 if (initializingHandler !== null && key === '0') {
2527 // We we already have an initializing handler and we're abound to enter
2528 // a new element, we need to shadow it because we're now in a new scope.
2529 // This is effectively the "begin" or "push" phase of Element parsing.
2530 // We'll pop later when we parse the array itself.
2531 initializingHandler = {
2532 parent: initializingHandler,
2533 chunk: null,
2534 value: null,
2535 reason: null,
2536 deps: 0,
2537 errored: false,
2538 };
2539 }
2540 return REACT_ELEMENT_TYPE;
2541 }
2542 switch (value[1]) {
2543 case '$': {
2544 // This was an escaped string value.
2545 return value.slice(1);
2546 }
2547 case 'L': {
2548 // Lazy node
2549 const id = parseInt(value.slice(2), 16);
2550 const chunk = getChunk(response, id);
2551 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2552 if (
2553 initializingChunk !== null &&
2554 isArray(initializingChunk._children)
2555 ) {
2556 initializingChunk._children.push(chunk);
2557 }
2558 }
2559 // We create a React.lazy wrapper around any lazy values.
2560 // When passed into React, we'll know how to suspend on this.
2561 return createLazyChunkWrapper(chunk, 0);
2562 }
2563 case '@': {
2564 // Promise
2565 const id = parseInt(value.slice(2), 16);
2566 const chunk = getChunk(response, id);
2567 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2568 if (
2569 initializingChunk !== null &&
2570 isArray(initializingChunk._children)
2571 ) {
2572 initializingChunk._children.push(chunk);
2573 }
2574 }
2575 return chunk;
2576 }
2577 case 'w': {
2578 if (enableFlightWeakThenables) {
2579 // Weak Promise
2580 const id = parseInt(value.slice(2), 16);
2581 const chunk = getWeakChunk(response, id);
2582 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2583 if (
2584 initializingChunk !== null &&
2585 isArray(initializingChunk._children)
2586 ) {
2587 initializingChunk._children.push(chunk);
2588 }
2589 }
2590 return chunk;
2591 }
2592 return undefined;
2593 }
2594 case 'S': {
2595 // Symbol
2596 return Symbol.for(value.slice(2));
2597 }
2598 case 'h': {
2599 // Server Reference
2600 const ref = value.slice(2);
2601 return getOutlinedModel(
2602 response,
2603 ref,
2604 parentObject,
2605 key,
2606 loadServerReference,
2607 );
2608 }
2609 case 'T': {
2610 // Temporary Reference
2611 const reference = '$' + value.slice(2);
2612 const temporaryReferences = response._tempRefs;
2613 if (temporaryReferences == null) {
2614 throw new Error(
2615 'Missing a temporary reference set but the RSC response returned a temporary reference. ' +
2616 'Pass a temporaryReference option with the set that was used with the reply.',
2617 );
2618 }
2619 return readTemporaryReference(temporaryReferences, reference);
2620 }
2621 case 'Q': {
2622 // Map
2623 const ref = value.slice(2);
2624 return getOutlinedModel(response, ref, parentObject, key, createMap);
2625 }
2626 case 'W': {
2627 // Set
2628 const ref = value.slice(2);
2629 return getOutlinedModel(response, ref, parentObject, key, createSet);
2630 }
2631 case 'B': {
2632 // Blob
2633 const ref = value.slice(2);
2634 return getOutlinedModel(response, ref, parentObject, key, createBlob);
2635 }
2636 case 'K': {
2637 // FormData
2638 const ref = value.slice(2);
2639 return getOutlinedModel(
2640 response,
2641 ref,
2642 parentObject,
2643 key,
2644 createFormData,
2645 );
2646 }
2647 case 'Z': {
2648 // Error
2649 if (__DEV__) {
2650 const ref = value.slice(2);
2651 return getOutlinedModel(
2652 response,
2653 ref,
2654 parentObject,
2655 key,
2656 resolveErrorDev,
2657 );
2658 } else {
2659 return resolveErrorProd(response);
2660 }
2661 }
2662 case 'i': {
2663 // Iterator
2664 const ref = value.slice(2);
2665 return getOutlinedModel(
2666 response,
2667 ref,
2668 parentObject,
2669 key,
2670 extractIterator,
2671 );
2672 }
2673 case 'I': {
2674 // $Infinity
2675 return Infinity;
2676 }
2677 case '-': {
2678 // $-0 or $-Infinity
2679 if (value === '$-0') {
2680 return -0;
2681 } else {
2682 return -Infinity;
2683 }
2684 }
2685 case 'N': {
2686 // $NaN
2687 return NaN;
2688 }
2689 case 'u': {
2690 // matches "$undefined"
2691 // Special encoding for `undefined` which can't be serialized as JSON otherwise.
2692 return undefined;
2693 }
2694 case 'D': {
2695 // Date
2696 return new Date(Date.parse(value.slice(2)));
2697 }
2698 case 'n': {
2699 // BigInt
2700 return BigInt(value.slice(2));
2701 }
2702 case 'P': {
2703 if (__DEV__) {
2704 // In DEV mode we allow debug objects to specify themselves as instances of
2705 // another constructor.
2706 const ref = value.slice(2);
2707 return getOutlinedModel(
2708 response,
2709 ref,
2710 parentObject,
2711 key,
2712 applyConstructor,
2713 );
2714 }
2715 //Fallthrough
2716 }
2717 case 'E': {
2718 if (__DEV__) {
2719 // In DEV mode we allow indirect eval to produce functions for logging.
2720 // This should not compile to eval() because then it has local scope access.
2721 const code = value.slice(2);
2722 try {
2723 // If this might be a class constructor with a static initializer or
2724 // static constructor then don't eval it. It might cause unexpected
2725 // side-effects. Instead, fallback to parsing out the function type
2726 // and name.
2727 if (!mightHaveStaticConstructor.test(code)) {
2728 // eslint-disable-next-line no-eval
2729 return (0, eval)(code);
2730 }
2731 } catch (x) {
2732 // Fallthrough to fallback case.
2733 }
2734 // We currently use this to express functions so we fail parsing it,
2735 // let's just return a blank function as a place holder.
2736 let fn;
2737 try {
2738 fn = getInferredFunctionApproximate(code);
2739 if (code.startsWith('Object.defineProperty(')) {
2740 const DESCRIPTOR = ',"name",{value:"';
2741 const idx = code.lastIndexOf(DESCRIPTOR);
2742 if (idx !== -1) {
2743 const name = JSON.parse(
2744 code.slice(idx + DESCRIPTOR.length - 1, code.length - 2),
2745 );
2746 // $FlowFixMe[cannot-write]
2747 Object.defineProperty(fn, 'name', {value: name});
2748 }
2749 }
2750 } catch (_) {
2751 fn = function () {};
2752 }
2753 return fn;
2754 }
2755 // Fallthrough
2756 }
2757 case 'Y': {
2758 if (__DEV__) {
2759 if (value.length > 2) {
2760 const debugChannelCallback =
2761 response._debugChannel && response._debugChannel.callback;
2762 if (debugChannelCallback) {
2763 if (value[2] === '@') {
2764 // This is a deferred Promise.
2765 const ref = value.slice(3); // We assume this doesn't have a path just id.
2766 const id = parseInt(ref, 16);
2767 if (!response._chunks.has(id)) {
2768 // We haven't seen this id before. Query the server to start sending it.
2769 debugChannelCallback('P:' + ref);
2770 }
2771 // Start waiting. This now creates a pending chunk if it doesn't already exist.
2772 // This is the actual Promise we're waiting for.
2773 return getChunk(response, id);
2774 }
2775 const ref = value.slice(2); // We assume this doesn't have a path just id.
2776 const id = parseInt(ref, 16);
2777 if (!response._chunks.has(id)) {
2778 // We haven't seen this id before. Query the server to start sending it.
2779 debugChannelCallback('Q:' + ref);
2780 }
2781 // Start waiting. This now creates a pending chunk if it doesn't already exist.
2782 const chunk = getChunk(response, id);
2783 if (chunk.status === INITIALIZED) {
2784 // We already loaded this before. We can just use the real value.
2785 return chunk.value;
2786 }
2787 return defineLazyGetter(response, chunk, parentObject, key);
2788 }
2789 }
2790
2791 // In DEV mode we encode omitted objects in logs as a getter that throws
2792 // so that when you try to access it on the client, you know why that
2793 // happened.
2794 if (key !== __PROTO__) {
2795 Object.defineProperty(parentObject, key, {
2796 get: function () {
2797 // TODO: We should ideally throw here to indicate a difference.
2798 return OMITTED_PROP_ERROR;
2799 },
2800 // no-op: the walk function may try to reassign this property
2801 // after parseModelString returns. With the JSON.parse reviver,
2802 // the engine's internal CreateDataProperty silently failed.
2803 // We use a no-op setter to match that behavior in strict mode.
2804 set: function () {},
2805 enumerable: true,
2806 configurable: false,
2807 });
2808 }
2809 return null;
2810 }
2811 // Fallthrough
2812 }
2813 default: {
2814 // We assume that anything else is a reference ID.
2815 const ref = value.slice(1);
2816 return getOutlinedModel(response, ref, parentObject, key, createModel);
2817 }
2818 }
2819 }
2820 return value;
2821 }
2822
2823 function parseModelTuple(
2824 response: Response,
2825 value: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
2826 ): any {
2827 const tuple: [mixed, mixed, mixed, mixed] = value as any;
2828
2829 if (tuple[0] === REACT_ELEMENT_TYPE) {
2830 // TODO: Consider having React just directly accept these arrays as elements.
2831 // Or even change the ReactElement type to be an array.
2832 return createElement(
2833 response,
2834 tuple[1],
2835 tuple[2],
2836 tuple[3],
2837 __DEV__ ? (tuple as any)[4] : null,
2838 __DEV__ ? (tuple as any)[5] : null,
2839 __DEV__ ? (tuple as any)[6] : 0,
2840 );
2841 }
2842 return value;
2843 }
2844
2845 function missingCall() {
2846 throw new Error(
2847 'Trying to call a function from "use server" but the callServer option ' +
2848 'was not implemented in your router runtime.',
2849 );
2850 }
2851
2852 function markIOStarted(this: Response) {
2853 this._debugIOStarted = true;
2854 }
2855
2856 function ResponseInstance(
2857 this: $FlowFixMe,
2858 bundlerConfig: ServerConsumerModuleMap,
2859 serverReferenceConfig: null | ServerManifest,
2860 moduleLoading: ModuleLoading,
2861 callServer: void | CallServerCallback,
2862 encodeFormAction: void | EncodeFormActionCallback,
2863 nonce: void | string,
2864 temporaryReferences: void | TemporaryReferenceSet,
2865 allowPartialStream: boolean,
2866 findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
2867 replayConsole: boolean, // DEV-only
2868 environmentName: void | string, // DEV-only
2869 debugStartTime: void | number, // DEV-only
2870 debugEndTime: void | number, // DEV-only
2871 debugChannel: void | DebugChannel, // DEV-only
2872 ) {
2873 const chunks: Map<number, SomeChunk<any>> = new Map();
2874 this._bundlerConfig = bundlerConfig;
2875 this._serverReferenceConfig = serverReferenceConfig;
2876 this._moduleLoading = moduleLoading;
2877 this._callServer = callServer !== undefined ? callServer : missingCall;
2878 this._encodeFormAction = encodeFormAction;
2879 this._nonce = nonce;
2880 this._chunks = chunks;
2881 this._stringDecoder = createStringDecoder();
2882 this._closed = false;
2883 this._closedReason = null;
2884 this._allowPartialStream = allowPartialStream;
2885 this._tempRefs = temporaryReferences;
2886 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2887 this._timeOrigin = 0;
2888 this._pendingInitialRender = null;
2889 }
2890 if (__DEV__) {
2891 this._pendingChunks = 0;
2892 this._weakResponse = {
2893 weak: new WeakRef(this),
2894 response: this,
2895 };
2896 // TODO: The Flight Client can be used in a Client Environment too and we should really support
2897 // getting the owner there as well, but currently the owner of ReactComponentInfo is typed as only
2898 // supporting other ReactComponentInfo as owners (and not Fiber or Fizz's ComponentStackNode).
2899 // We need to update all the callsites consuming ReactComponentInfo owners to support those.
2900 // In the meantime we only check ReactSharedInteralsServer since we know that in an RSC environment
2901 // the only owners will be ReactComponentInfo.
2902 const rootOwner: null | ReactComponentInfo =
2903 ReactSharedInteralsServer === undefined ||
2904 ReactSharedInteralsServer.A === null
2905 ? null
2906 : (ReactSharedInteralsServer.A.getOwner() as any);
2907
2908 this._debugRootOwner = rootOwner;
2909 this._debugRootStack =
2910 rootOwner !== null
2911 ? // TODO: Consider passing the top frame in so we can avoid internals showing up.
2912 new Error('react-stack-top-frame')
2913 : null;
2914
2915 const rootEnv = environmentName === undefined ? 'Server' : environmentName;
2916 if (supportsCreateTask) {
2917 // Any stacks that appear on the server need to be rooted somehow on the client
2918 // so we create a root Task for this response which will be the root owner for any
2919 // elements created by the server. We use the "use server" string to indicate that
2920 // this is where we enter the server from the client.
2921 // TODO: Make this string configurable.
2922 this._debugRootTask = (console as any).createTask(
2923 '"use ' + rootEnv.toLowerCase() + '"',
2924 );
2925 }
2926 if (enableAsyncDebugInfo) {
2927 // Track the start of the fetch to the best of our knowledge.
2928 // Note: createFromFetch allows this to be marked at the start of the fetch
2929 // where as if you use createFromReadableStream from the body of the fetch
2930 // then the start time is when the headers resolved.
2931 this._debugStartTime =
2932 debugStartTime == null ? performance.now() : debugStartTime;
2933 this._debugIOStarted = false;
2934 // We consider everything before the first setTimeout task to be cached data
2935 // and is not considered I/O required to load the stream.
2936 setTimeout(markIOStarted.bind(this), 0);
2937 }
2938 this._debugEndTime = debugEndTime === undefined ? null : debugEndTime;
2939 this._debugFindSourceMapURL = findSourceMapURL;
2940 this._debugChannel = debugChannel;
2941 this._blockedConsole = null;
2942 this._replayConsole = replayConsole;
2943 this._rootEnvironmentName = rootEnv;
2944 if (debugChannel) {
2945 if (debugChannelRegistry === null) {
2946 // We can't safely clean things up later, so we immediately close the
2947 // debug channel.
2948 closeDebugChannel(debugChannel);
2949 this._debugChannel = undefined;
2950 } else {
2951 // When a Response gets GC:ed because nobody is referring to any of the
2952 // objects that lazily load from the Response anymore, then we can close
2953 // the debug channel.
2954 debugChannelRegistry.register(this, debugChannel, this);
2955 }
2956 }
2957 }
2958 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2959 // Since we don't know when recording of profiles will start and stop, we have to
2960 // mark the order over and over again.
2961 if (replayConsole) {
2962 markAllTracksInOrder();
2963 }
2964 }
2965 }
2966
2967 export function createResponse(
2968 bundlerConfig: ServerConsumerModuleMap,
2969 serverReferenceConfig: null | ServerManifest,
2970 moduleLoading: ModuleLoading,
2971 callServer: void | CallServerCallback,
2972 encodeFormAction: void | EncodeFormActionCallback,
2973 nonce: void | string,
2974 temporaryReferences: void | TemporaryReferenceSet,
2975 allowPartialStream: boolean,
2976 findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
2977 replayConsole: boolean, // DEV-only
2978 environmentName: void | string, // DEV-only
2979 debugStartTime: void | number, // DEV-only
2980 debugEndTime: void | number, // DEV-only
2981 debugChannel: void | DebugChannel, // DEV-only
2982 ): WeakResponse {
2983 if (__DEV__) {
2984 // We use eval to create fake function stacks which includes Component stacks.
2985 // A warning would be noise if you used Flight without Components and don't encounter
2986 // errors. We're warning eagerly so that you configure your environment accordingly
2987 // before you encounter an error.
2988 checkEvalAvailabilityOnceDev();
2989 }
2990
2991 return getWeakResponse(
2992 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
2993 new ResponseInstance(
2994 bundlerConfig,
2995 serverReferenceConfig,
2996 moduleLoading,
2997 callServer,
2998 encodeFormAction,
2999 nonce,
3000 temporaryReferences,
3001 allowPartialStream,
3002 findSourceMapURL,
3003 replayConsole,
3004 environmentName,
3005 debugStartTime,
3006 debugEndTime,
3007 debugChannel,
3008 ),
3009 );
3010 }
3011
3012 export type StreamState = {
3013 _rowState: RowParserState,
3014 _rowID: number, // parts of a row ID parsed so far
3015 _rowTag: number, // 0 indicates that we're currently parsing the row ID
3016 _rowLength: number, // remaining bytes in the row. 0 indicates that we're looking for a newline.
3017 _buffer: Array<Uint8Array>, // chunks received so far as part of this row
3018 _debugInfo: ReactIOInfo, // DEV-only
3019 _debugTargetChunkSize: number, // DEV-only
3020 };
3021
3022 export function createStreamState(
3023 weakResponse: WeakResponse, // DEV-only
3024 streamDebugValue: mixed, // DEV-only
3025 ): StreamState {
3026 const streamState: StreamState = {
3027 _rowState: 0,
3028 _rowID: 0,
3029 _rowTag: 0,
3030 _rowLength: 0,
3031 _buffer: [],
3032 } as Omit<StreamState, '_debugInfo' | '_debugTargetChunkSize'> as any;
3033 if (__DEV__ && enableAsyncDebugInfo) {
3034 const response = unwrapWeakResponse(weakResponse);
3035 // Create an entry for the I/O to load the stream itself.
3036 const debugValuePromise = Promise.resolve(streamDebugValue);
3037 (debugValuePromise as any).status = 'fulfilled';
3038 (debugValuePromise as any).value = streamDebugValue;
3039 streamState._debugInfo = {
3040 name: 'rsc stream',
3041 start: response._debugStartTime,
3042 end: response._debugStartTime, // will be updated once we finish a chunk
3043 byteSize: 0, // will be updated as we resolve a data chunk
3044 value: debugValuePromise,
3045 owner: response._debugRootOwner,
3046 debugStack: response._debugRootStack,
3047 debugTask: response._debugRootTask,
3048 };
3049 streamState._debugTargetChunkSize = MIN_CHUNK_SIZE;
3050 }
3051 return streamState;
3052 }
3053
3054 // Depending on set up the chunks of a TLS connection can vary in size. However in practice it's often
3055 // at 64kb or even multiples of 64kb. It can also be smaller but in practice it also happens that 64kb
3056 // is around what you can download on fast 4G connection in 300ms which is what we throttle reveals at
3057 // anyway. The net effect is that in practice, you won't really reveal anything in smaller units than
3058 // 64kb if they're revealing at maximum speed in production. Therefore we group smaller chunks into
3059 // these larger chunks since in production that's more realistic.
3060 // TODO: If the stream is compressed, then you could fit much more in a single 300ms so maybe it should
3061 // actually be larger.
3062 const MIN_CHUNK_SIZE = 65536;
3063
3064 function incrementChunkDebugInfo(
3065 streamState: StreamState,
3066 chunkLength: number,
3067 ): void {
3068 if (__DEV__ && enableAsyncDebugInfo) {
3069 const debugInfo: ReactIOInfo = streamState._debugInfo;
3070 const endTime = performance.now();
3071 const previousEndTime = debugInfo.end;
3072 const newByteLength = (debugInfo.byteSize as any as number) + chunkLength;
3073 if (
3074 newByteLength > streamState._debugTargetChunkSize ||
3075 endTime > previousEndTime + 10
3076 ) {
3077 // This new chunk would overshoot the chunk size so therefore we treat it as its own new chunk
3078 // by cloning the old one. Similarly, if some time has passed we assume that it was actually
3079 // due to the server being unable to flush chunks faster e.g. due to I/O so it would be a
3080 // new chunk in production even if the buffer hasn't been reached.
3081 streamState._debugInfo = {
3082 name: debugInfo.name,
3083 start: debugInfo.start,
3084 end: endTime,
3085 byteSize: newByteLength,
3086 value: debugInfo.value,
3087 owner: debugInfo.owner,
3088 debugStack: debugInfo.debugStack,
3089 debugTask: debugInfo.debugTask,
3090 };
3091 streamState._debugTargetChunkSize = newByteLength + MIN_CHUNK_SIZE;
3092 } else {
3093 // Otherwise we reuse the old chunk but update the end time and byteSize to the latest.
3094 // $FlowFixMe[cannot-write]
3095 debugInfo.end = endTime;
3096 // $FlowFixMe[cannot-write]
3097 debugInfo.byteSize = newByteLength;
3098 }
3099 }
3100 }
3101
3102 function addAsyncInfo(chunk: SomeChunk<any>, asyncInfo: ReactAsyncInfo): void {
3103 const value = resolveLazy(chunk.value);
3104 if (
3105 typeof value === 'object' &&
3106 value !== null &&
3107 (isArray(value) ||
3108 typeof value[ASYNC_ITERATOR] === 'function' ||
3109 value.$$typeof === REACT_ELEMENT_TYPE ||
3110 value.$$typeof === REACT_LAZY_TYPE)
3111 ) {
3112 if (isArray(value._debugInfo)) {
3113 // $FlowFixMe[method-unbinding]
3114 value._debugInfo.push(asyncInfo);
3115 } else if (!Object.isFrozen(value)) {
3116 // TODO: Debug info is dropped for frozen elements. See the TODO in
3117 // moveDebugInfoFromChunkToInnerValue.
3118 Object.defineProperty(value as any, '_debugInfo', {
3119 configurable: false,
3120 enumerable: false,
3121 writable: true,
3122 value: [asyncInfo],
3123 });
3124 }
3125 } else {
3126 // $FlowFixMe[method-unbinding]
3127 chunk._debugInfo.push(asyncInfo);
3128 }
3129 }
3130
3131 function resolveChunkDebugInfo(
3132 response: Response,
3133 streamState: StreamState,
3134 chunk: SomeChunk<any>,
3135 ): void {
3136 if (__DEV__ && enableAsyncDebugInfo) {
3137 // Only include stream information after a macrotask. Any chunk processed
3138 // before that is considered cached data.
3139 if (response._debugIOStarted) {
3140 // Add the currently resolving chunk's debug info representing the stream
3141 // to the Promise that was waiting on the stream, or its underlying value.
3142 const asyncInfo: ReactAsyncInfo = {awaited: streamState._debugInfo};
3143 if (chunk.status === PENDING || chunk.status === BLOCKED) {
3144 const boundAddAsyncInfo = addAsyncInfo.bind(null, chunk, asyncInfo);
3145 chunk.then(boundAddAsyncInfo, boundAddAsyncInfo);
3146 } else {
3147 addAsyncInfo(chunk, asyncInfo);
3148 }
3149 }
3150 }
3151 }
3152
3153 function resolveDebugHalt(response: Response, id: number): void {
3154 const chunks = response._chunks;
3155 let chunk = chunks.get(id);
3156 if (!chunk) {
3157 chunks.set(id, (chunk = createPendingChunk(response)));
3158 } else {
3159 }
3160 if (
3161 chunk.status !== PENDING &&
3162 chunk.status !== PENDING_WEAK &&
3163 chunk.status !== BLOCKED
3164 ) {
3165 return;
3166 }
3167 haltChunk(response, chunk);
3168 }
3169
3170 function resolveModel(
3171 response: Response,
3172 id: number,
3173 model: UninitializedModel,
3174 streamState: StreamState,
3175 ): void {
3176 const chunks = response._chunks;
3177 const chunk = chunks.get(id);
3178 if (!chunk) {
3179 const newChunk: ResolvedModelChunk<any> = createResolvedModelChunk(
3180 response,
3181 model,
3182 );
3183 if (__DEV__) {
3184 resolveChunkDebugInfo(response, streamState, newChunk);
3185 }
3186 chunks.set(id, newChunk);
3187 } else {
3188 if (__DEV__) {
3189 resolveChunkDebugInfo(response, streamState, chunk);
3190 }
3191 resolveModelChunk(response, chunk, model);
3192 }
3193 }
3194
3195 function resolveText(
3196 response: Response,
3197 id: number,
3198 text: string,
3199 streamState: StreamState,
3200 ): void {
3201 const chunks = response._chunks;
3202 const chunk = chunks.get(id);
3203 if (chunk && chunk.status !== PENDING) {
3204 // If we get more data to an already resolved ID, we assume that it's
3205 // a stream chunk since any other row shouldn't have more than one entry.
3206 const streamChunk: InitializedStreamChunk<any> = chunk as any;
3207 const controller = streamChunk.reason;
3208 controller.enqueueValue(text);
3209 return;
3210 }
3211 if (chunk) {
3212 releasePendingChunk(response, chunk);
3213 }
3214 const newChunk = createInitializedTextChunk(response, text);
3215 if (__DEV__) {
3216 resolveChunkDebugInfo(response, streamState, newChunk);
3217 }
3218 chunks.set(id, newChunk);
3219 }
3220
3221 function resolveBuffer(
3222 response: Response,
3223 id: number,
3224 buffer: $ArrayBufferView | ArrayBuffer,
3225 streamState: StreamState,
3226 ): void {
3227 const chunks = response._chunks;
3228 const chunk = chunks.get(id);
3229 if (chunk && chunk.status !== PENDING) {
3230 // If we get more data to an already resolved ID, we assume that it's
3231 // a stream chunk since any other row shouldn't have more than one entry.
3232 const streamChunk: InitializedStreamChunk<any> = chunk as any;
3233 const controller = streamChunk.reason;
3234 controller.enqueueValue(buffer);
3235 return;
3236 }
3237 if (chunk) {
3238 releasePendingChunk(response, chunk);
3239 }
3240 const newChunk = createInitializedBufferChunk(response, buffer);
3241 if (__DEV__) {
3242 resolveChunkDebugInfo(response, streamState, newChunk);
3243 }
3244 chunks.set(id, newChunk);
3245 }
3246
3247 function resolveModule(
3248 response: Response,
3249 id: number,
3250 model: UninitializedModel,
3251 streamState: StreamState,
3252 ): void {
3253 const chunks = response._chunks;
3254 const chunk = chunks.get(id);
3255 const prevHandler = initializingHandler;
3256 initializingHandler = null;
3257 let clientReferenceMetadata: ClientReferenceMetadata;
3258 try {
3259 clientReferenceMetadata = parseModel(response, model);
3260 if (initializingHandler !== null) {
3261 // We resolve the client reference below and have nothing to wait on,
3262 // so the metadata can't reference a row that hasn't arrived.
3263 throw new Error(
3264 'A client reference was blocked on a row that has not been received yet. ' +
3265 'This is a bug in React.',
3266 );
3267 }
3268 } finally {
3269 initializingHandler = prevHandler;
3270 }
3271 const clientReference = resolveClientReference<$FlowFixMe>(
3272 response._bundlerConfig,
3273 clientReferenceMetadata,
3274 );
3275
3276 prepareDestinationForModule(
3277 response._moduleLoading,
3278 response._nonce,
3279 clientReferenceMetadata,
3280 );
3281
3282 // TODO: Add an option to encode modules that are lazy loaded.
3283 // For now we preload all modules as early as possible since it's likely
3284 // that we'll need them.
3285 const promise = preloadModule(clientReference);
3286 if (promise) {
3287 let blockedChunk: BlockedChunk<any>;
3288 if (!chunk) {
3289 // Technically, we should just treat promise as the chunk in this
3290 // case. Because it'll just behave as any other promise.
3291 blockedChunk = createBlockedChunk(response);
3292 chunks.set(id, blockedChunk);
3293 } else {
3294 releasePendingChunk(response, chunk);
3295 // This can't actually happen because we don't have any forward
3296 // references to modules.
3297 blockedChunk = chunk as any;
3298 blockedChunk.status = BLOCKED;
3299 }
3300 if (__DEV__) {
3301 resolveChunkDebugInfo(response, streamState, blockedChunk);
3302 }
3303 promise.then(
3304 () => resolveModuleChunk(response, blockedChunk, clientReference),
3305 error => triggerErrorOnChunk(response, blockedChunk, error),
3306 );
3307 } else {
3308 if (!chunk) {
3309 const newChunk = createResolvedModuleChunk(response, clientReference);
3310 if (__DEV__) {
3311 resolveChunkDebugInfo(response, streamState, newChunk);
3312 }
3313 chunks.set(id, newChunk);
3314 } else {
3315 if (__DEV__) {
3316 resolveChunkDebugInfo(response, streamState, chunk);
3317 }
3318 // This can't actually happen because we don't have any forward
3319 // references to modules.
3320 resolveModuleChunk(response, chunk, clientReference);
3321 }
3322 }
3323 }
3324
3325 function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
3326 response: Response,
3327 id: number,
3328 stream: T,
3329 controller: FlightStreamController,
3330 streamState: StreamState,
3331 ): void {
3332 const chunks = response._chunks;
3333 const chunk = chunks.get(id);
3334 if (!chunk) {
3335 const newChunk = createInitializedStreamChunk(response, stream, controller);
3336 if (__DEV__) {
3337 resolveChunkDebugInfo(response, streamState, newChunk);
3338 }
3339 chunks.set(id, newChunk);
3340 return;
3341 }
3342 if (__DEV__) {
3343 resolveChunkDebugInfo(response, streamState, chunk);
3344 }
3345 if (chunk.status !== PENDING) {
3346 // We already resolved. We didn't expect to see this.
3347 return;
3348 }
3349
3350 const resolveListeners = chunk.value;
3351
3352 if (__DEV__) {
3353 // Initialize any debug info and block the initializing chunk on any
3354 // unresolved entries.
3355 if (chunk._debugChunk != null) {
3356 const prevHandler = initializingHandler;
3357 const prevChunk = initializingChunk;
3358 initializingHandler = null;
3359 const cyclicChunk: BlockedChunk<T> = chunk as any;
3360 cyclicChunk.status = BLOCKED;
3361 cyclicChunk.value = null;
3362 cyclicChunk.reason = null;
3363 if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
3364 initializingChunk = cyclicChunk;
3365 }
3366 try {
3367 initializeDebugChunk(response, chunk);
3368 if (initializingHandler !== null) {
3369 if (initializingHandler.errored) {
3370 // Ignore error parsing debug info, we'll report the original error instead.
3371 } else if (initializingHandler.deps > 0) {
3372 // Leave blocked until we can resolve all the debug info.
3373 initializingHandler.value = stream;
3374 initializingHandler.reason = controller;
3375 initializingHandler.chunk = cyclicChunk;
3376 return;
3377 }
3378 }
3379 } finally {
3380 initializingHandler = prevHandler;
3381 initializingChunk = prevChunk;
3382 }
3383 }
3384 }
3385
3386 const resolvedChunk: InitializedStreamChunk<T> = chunk as any;
3387 resolvedChunk.status = INITIALIZED;
3388 resolvedChunk.value = stream;
3389 resolvedChunk.reason = controller;
3390 if (resolveListeners !== null) {
3391 wakeChunk(response, resolveListeners, chunk.value, chunk as any);
3392 } else {
3393 if (__DEV__) {
3394 processChunkDebugInfo(response, resolvedChunk, stream);
3395 }
3396 }
3397 }
3398
3399 function startReadableStream<T>(
3400 response: Response,
3401 id: number,
3402 type: void | 'bytes',
3403 streamState: StreamState,
3404 ): void {
3405 let controller: ReadableStreamController = null as any;
3406 let closed = false;
3407 const stream = new ReadableStream({
3408 type: type,
3409 start(c) {
3410 controller = c;
3411 },
3412 });
3413 let previousBlockedChunk: SomeChunk<T> | null = null;
3414 const flightController = {
3415 enqueueValue(value: T): void {
3416 if (previousBlockedChunk === null) {
3417 controller.enqueue(value);
3418 } else {
3419 // We're still waiting on a previous chunk so we can't enqueue quite yet.
3420 previousBlockedChunk.then(function () {
3421 controller.enqueue(value);
3422 });
3423 }
3424 },
3425 enqueueModel(json: UninitializedModel): void {
3426 if (previousBlockedChunk === null) {
3427 // If we're not blocked on any other chunks, we can try to eagerly initialize
3428 // this as a fast-path to avoid awaiting them.
3429 const chunk: ResolvedModelChunk<T> = createResolvedModelChunk(
3430 response,
3431 json,
3432 );
3433 initializeModelChunk(chunk);
3434 const initializedChunk: SomeChunk<T> = chunk;
3435 if (initializedChunk.status === INITIALIZED) {
3436 controller.enqueue(initializedChunk.value);
3437 } else {
3438 chunk.then(
3439 v => controller.enqueue(v),
3440 e => controller.error(e as any),
3441 );
3442 previousBlockedChunk = chunk;
3443 }
3444 } else {
3445 // We're still waiting on a previous chunk so we can't enqueue quite yet.
3446 const blockedChunk = previousBlockedChunk;
3447 const chunk: SomeChunk<T> = createPendingChunk(response);
3448 chunk.then(
3449 v => controller.enqueue(v),
3450 e => controller.error(e as any),
3451 );
3452 previousBlockedChunk = chunk;
3453 blockedChunk.then(function () {
3454 if (previousBlockedChunk === chunk) {
3455 // We were still the last chunk so we can now clear the queue and return
3456 // to synchronous emitting.
3457 previousBlockedChunk = null;
3458 }
3459 resolveModelChunk(response, chunk, json);
3460 });
3461 }
3462 },
3463 close(json: UninitializedModel): void {
3464 if (closed) {
3465 return;
3466 }
3467 closed = true;
3468 if (previousBlockedChunk === null) {
3469 controller.close();
3470 } else {
3471 const blockedChunk = previousBlockedChunk;
3472 // We shouldn't get any more enqueues after this so we can set it back to null.
3473 previousBlockedChunk = null;
3474 blockedChunk.then(() => controller.close());
3475 }
3476 },
3477 error(error: mixed): void {
3478 if (closed) {
3479 return;
3480 }
3481 closed = true;
3482 if (previousBlockedChunk === null) {
3483 // $FlowFixMe[incompatible-type]
3484 controller.error(error);
3485 } else {
3486 const blockedChunk = previousBlockedChunk;
3487 // We shouldn't get any more enqueues after this so we can set it back to null.
3488 previousBlockedChunk = null;
3489 blockedChunk.then(() => controller.error(error as any));
3490 }
3491 },
3492 };
3493 resolveStream(response, id, stream, flightController, streamState);
3494 }
3495
3496 function asyncIterator(this: $AsyncIterator<any, any, void>) {
3497 // Self referencing iterator.
3498 return this;
3499 }
3500
3501 function createIterator<T>(
3502 next: (arg: void) => SomeChunk<IteratorResult<T, T>>,
3503 ): $AsyncIterator<T, T, void> {
3504 const iterator: any = {
3505 next: next,
3506 // TODO: Add return/throw as options for aborting.
3507 };
3508 // TODO: The iterator could inherit the AsyncIterator prototype which is not exposed as
3509 // a global but exists as a prototype of an AsyncGenerator. However, it's not needed
3510 // to satisfy the iterable protocol.
3511 (iterator as any)[ASYNC_ITERATOR] = asyncIterator;
3512 return iterator;
3513 }
3514
3515 function startAsyncIterable<T>(
3516 response: Response,
3517 id: number,
3518 iterator: boolean,
3519 streamState: StreamState,
3520 ): void {
3521 const buffer: Array<SomeChunk<IteratorResult<T, T>>> = [];
3522 let closed = false;
3523 let nextWriteIndex = 0;
3524 const flightController = {
3525 enqueueValue(value: T): void {
3526 if (nextWriteIndex === buffer.length) {
3527 buffer[nextWriteIndex] = createInitializedIteratorResultChunk(
3528 response,
3529 value,
3530 false,
3531 );
3532 } else {
3533 const chunk: PendingChunk<IteratorResult<T, T>> = buffer[
3534 nextWriteIndex
3535 ] as any;
3536 const resolveListeners = chunk.value;
3537 const rejectListeners = chunk.reason;
3538 const initializedChunk: InitializedChunk<IteratorResult<T, T>> =
3539 chunk as any;
3540 initializedChunk.status = INITIALIZED;
3541 initializedChunk.value = {done: false, value: value};
3542 initializedChunk.reason = null;
3543 if (resolveListeners !== null) {
3544 wakeChunkIfInitialized(
3545 response,
3546 chunk,
3547 resolveListeners,
3548 rejectListeners,
3549 );
3550 }
3551 }
3552 nextWriteIndex++;
3553 },
3554 enqueueModel(value: UninitializedModel): void {
3555 if (nextWriteIndex === buffer.length) {
3556 buffer[nextWriteIndex] = createResolvedIteratorResultChunk(
3557 response,
3558 value,
3559 false,
3560 );
3561 } else {
3562 resolveIteratorResultChunk(
3563 response,
3564 buffer[nextWriteIndex],
3565 value,
3566 false,
3567 );
3568 }
3569 nextWriteIndex++;
3570 },
3571 close(value: UninitializedModel): void {
3572 if (closed) {
3573 return;
3574 }
3575 closed = true;
3576 if (nextWriteIndex === buffer.length) {
3577 buffer[nextWriteIndex] = createResolvedIteratorResultChunk(
3578 response,
3579 value,
3580 true,
3581 );
3582 } else {
3583 resolveIteratorResultChunk(
3584 response,
3585 buffer[nextWriteIndex],
3586 value,
3587 true,
3588 );
3589 }
3590 nextWriteIndex++;
3591 while (nextWriteIndex < buffer.length) {
3592 // In generators, any extra reads from the iterator have the value undefined.
3593 resolveIteratorResultChunk(
3594 response,
3595 buffer[nextWriteIndex++],
3596 '"$undefined"',
3597 true,
3598 );
3599 }
3600 },
3601 error(error: Error): void {
3602 if (closed) {
3603 return;
3604 }
3605 closed = true;
3606 if (nextWriteIndex === buffer.length) {
3607 buffer[nextWriteIndex] =
3608 createPendingChunk<IteratorResult<T, T>>(response);
3609 }
3610 while (nextWriteIndex < buffer.length) {
3611 triggerErrorOnChunk(response, buffer[nextWriteIndex++], error);
3612 }
3613 },
3614 };
3615
3616 const iterable: $AsyncIterable<T, T, void> = {} as any;
3617 // $FlowFixMe[cannot-write]
3618 iterable[ASYNC_ITERATOR] = (): $AsyncIterator<T, T, void> => {
3619 let nextReadIndex = 0;
3620 return createIterator(arg => {
3621 if (arg !== undefined) {
3622 throw new Error(
3623 'Values cannot be passed to next() of AsyncIterables passed to Client Components.',
3624 );
3625 }
3626 if (nextReadIndex === buffer.length) {
3627 if (closed) {
3628 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
3629 return new ReactPromise(
3630 INITIALIZED,
3631 {done: true, value: undefined},
3632 null,
3633 );
3634 }
3635 buffer[nextReadIndex] =
3636 createPendingChunk<IteratorResult<T, T>>(response);
3637 }
3638 return buffer[nextReadIndex++];
3639 });
3640 };
3641
3642 // TODO: If it's a single shot iterator we can optimize memory by cleaning up the buffer after
3643 // reading through the end, but currently we favor code size over this optimization.
3644 resolveStream(
3645 response,
3646 id,
3647 iterator ? iterable[ASYNC_ITERATOR]() : iterable,
3648 flightController,
3649 streamState,
3650 );
3651 }
3652
3653 function stopStream(
3654 response: Response,
3655 id: number,
3656 row: UninitializedModel,
3657 ): void {
3658 const chunks = response._chunks;
3659 const chunk = chunks.get(id);
3660 if (!chunk || chunk.status !== INITIALIZED) {
3661 // We didn't expect not to have an existing stream;
3662 return;
3663 }
3664 if (__DEV__) {
3665 if (--response._pendingChunks === 0) {
3666 // We're no longer waiting for any more chunks. We can release the strong
3667 // reference to the response. We'll regain it if we ask for any more data
3668 // later on.
3669 response._weakResponse.response = null;
3670 }
3671 }
3672 const streamChunk: InitializedStreamChunk<any> = chunk as any;
3673 const controller = streamChunk.reason;
3674 controller.close(row === '' ? '"$undefined"' : row);
3675 }
3676
3677 type ErrorWithDigest = Error & {digest?: string};
3678 function resolveErrorProd(response: Response): Error {
3679 if (__DEV__) {
3680 // These errors should never make it into a build so we don't need to encode them in codes.json
3681 // eslint-disable-next-line react-internal/prod-error-codes
3682 throw new Error(
3683 'resolveErrorProd should never be called in development mode. Use resolveErrorDev instead. This is a bug in React.',
3684 );
3685 }
3686 const error = new Error(
3687 'An error occurred in the Server Components render. The specific message is omitted in production' +
3688 ' builds to avoid leaking sensitive details. A digest property is included on this error instance which' +
3689 ' may provide additional details about the nature of the error.',
3690 );
3691 error.stack = 'Error: ' + error.message;
3692 return error;
3693 }
3694
3695 function resolveErrorDev(
3696 response: Response,
3697 errorInfo: ReactErrorInfoDev,
3698 ): Error {
3699 const name = errorInfo.name;
3700 const message = errorInfo.message;
3701 const stack = errorInfo.stack;
3702 const env = errorInfo.env;
3703
3704 if (!__DEV__) {
3705 // These errors should never make it into a build so we don't need to encode them in codes.json
3706 // eslint-disable-next-line react-internal/prod-error-codes
3707 throw new Error(
3708 'resolveErrorDev should never be called in production mode. Use resolveErrorProd instead. This is a bug in React.',
3709 );
3710 }
3711
3712 let error;
3713 const errorOptions =
3714 // We don't serialize Error.cause in prod so we never need to deserialize
3715 // $FlowFixMe[constant-condition]
3716 __DEV__ && 'cause' in errorInfo
3717 ? {
3718 cause: reviveModel(
3719 response,
3720 // $FlowFixMe[incompatible-type] -- Flow thinks `cause` in `cause?: JSONValue` can be undefined after `in` check.
3721 errorInfo.cause as JSONValue,
3722 errorInfo,
3723 'cause',
3724 ),
3725 }
3726 : undefined;
3727 const isAggregateError =
3728 typeof AggregateError !== 'undefined' && 'errors' in errorInfo;
3729 const revivedErrors =
3730 // We don't serialize AggregateError.errors in prod so we never need to deserialize
3731 __DEV__ && isAggregateError
3732 ? reviveModel(
3733 response,
3734 // $FlowFixMe[incompatible-type]
3735 errorInfo.errors as JSONValue,
3736 errorInfo,
3737 'errors',
3738 )
3739 : null;
3740 const callStack = buildFakeCallStack(
3741 response,
3742 stack,
3743 env,
3744 false,
3745 isAggregateError
3746 ? // $FlowFixMe[incompatible-use]
3747 AggregateError.bind(
3748 null,
3749 revivedErrors,
3750 message ||
3751 'An error occurred in the Server Components render but no message was provided',
3752 errorOptions,
3753 )
3754 : // $FlowFixMe[incompatible-use]
3755 Error.bind(
3756 null,
3757 message ||
3758 'An error occurred in the Server Components render but no message was provided',
3759 errorOptions,
3760 ),
3761 );
3762
3763 let ownerTask: null | ConsoleTask = null;
3764 if (errorInfo.owner != null) {
3765 const ownerRef = errorInfo.owner.slice(1);
3766 // TODO: This is not resilient to the owner loading later in an Error like a debug channel.
3767 // The whole error serialization should probably go through the regular model at least for DEV.
3768 const owner = getOutlinedModel(response, ownerRef, {}, '', createModel);
3769 if (owner !== null) {
3770 ownerTask = initializeFakeTask(response, owner);
3771 }
3772 }
3773
3774 if (ownerTask === null) {
3775 const rootTask = getRootTask(response, env);
3776 if (rootTask != null) {
3777 error = rootTask.run(callStack);
3778 } else {
3779 error = callStack();
3780 }
3781 } else {
3782 error = ownerTask.run(callStack);
3783 }
3784
3785 (error as any).name = name;
3786 (error as any).environmentName = env;
3787 return error;
3788 }
3789
3790 function resolveErrorModel(
3791 response: Response,
3792 id: number,
3793 row: UninitializedModel,
3794 streamState: StreamState,
3795 ): void {
3796 const chunks = response._chunks;
3797 const chunk = chunks.get(id);
3798 const errorInfo = JSON.parse(row);
3799 let error;
3800 if (__DEV__) {
3801 error = resolveErrorDev(response, errorInfo);
3802 } else {
3803 error = resolveErrorProd(response);
3804 }
3805 (error as any).digest = errorInfo.digest;
3806 const errorWithDigest: ErrorWithDigest = error as any;
3807 if (!chunk) {
3808 const newChunk: ErroredChunk<any> = createErrorChunk(
3809 response,
3810 errorWithDigest,
3811 );
3812 if (__DEV__) {
3813 resolveChunkDebugInfo(response, streamState, newChunk);
3814 }
3815 chunks.set(id, newChunk);
3816 } else {
3817 if (__DEV__) {
3818 resolveChunkDebugInfo(response, streamState, chunk);
3819 }
3820 triggerErrorOnChunk(response, chunk, errorWithDigest);
3821 }
3822 }
3823
3824 function resolveHint<Code: HintCode>(
3825 response: Response,
3826 code: Code,
3827 model: UninitializedModel,
3828 ): void {
3829 const hintModel: HintModel<Code> = parseModel(response, model);
3830 dispatchHint(code, hintModel);
3831 }
3832
3833 const supportsCreateTask = __DEV__ && !!(console as any).createTask;
3834
3835 type FakeFunction<T> = (() => T) => T;
3836 const fakeFunctionCache: Map<string, FakeFunction<any>> = __DEV__
3837 ? new Map()
3838 : (null as any);
3839
3840 let fakeFunctionIdx = 0;
3841 function createFakeFunction<T>(
3842 name: string,
3843 filename: string,
3844 sourceMap: null | string,
3845 line: number,
3846 col: number,
3847 enclosingLine: number,
3848 enclosingCol: number,
3849 environmentName: string,
3850 ): FakeFunction<T> {
3851 // This creates a fake copy of a Server Module. It represents a module that has already
3852 // executed on the server but we re-execute a blank copy for its stack frames on the client.
3853
3854 const comment =
3855 '/* This module was rendered by a Server Component. Turn on Source Maps to see the server source. */';
3856
3857 if (!name) {
3858 // An eval:ed function with no name gets the name "eval". We give it something more descriptive.
3859 name = '<anonymous>';
3860 }
3861 const encodedName = JSON.stringify(name);
3862 // We generate code where the call is at the line and column of the server executed code.
3863 // This allows us to use the original source map as the source map of this fake file to
3864 // point to the original source.
3865 let code;
3866 // Normalize line/col to zero based.
3867 if (enclosingLine < 1) {
3868 enclosingLine = 0;
3869 } else {
3870 enclosingLine--;
3871 }
3872 if (enclosingCol < 1) {
3873 enclosingCol = 0;
3874 } else {
3875 enclosingCol--;
3876 }
3877 if (line < 1) {
3878 line = 0;
3879 } else {
3880 line--;
3881 }
3882 if (col < 1) {
3883 col = 0;
3884 } else {
3885 col--;
3886 }
3887 if (line < enclosingLine || (line === enclosingLine && col < enclosingCol)) {
3888 // Protection against invalid enclosing information. Should not happen.
3889 enclosingLine = 0;
3890 enclosingCol = 0;
3891 }
3892 if (line < 1) {
3893 // Fit everything on the first line.
3894 const minCol = encodedName.length + 3;
3895 let enclosingColDistance = enclosingCol - minCol;
3896 if (enclosingColDistance < 0) {
3897 enclosingColDistance = 0;
3898 }
3899 let colDistance = col - enclosingColDistance - minCol - 3;
3900 if (colDistance < 0) {
3901 colDistance = 0;
3902 }
3903 code =
3904 '({' +
3905 encodedName +
3906 ':' +
3907 ' '.repeat(enclosingColDistance) +
3908 '_=>' +
3909 ' '.repeat(colDistance) +
3910 '_()})';
3911 } else if (enclosingLine < 1) {
3912 // Fit just the enclosing function on the first line.
3913 const minCol = encodedName.length + 3;
3914 let enclosingColDistance = enclosingCol - minCol;
3915 if (enclosingColDistance < 0) {
3916 enclosingColDistance = 0;
3917 }
3918 code =
3919 '({' +
3920 encodedName +
3921 ':' +
3922 ' '.repeat(enclosingColDistance) +
3923 '_=>' +
3924 '\n'.repeat(line - enclosingLine) +
3925 ' '.repeat(col) +
3926 '_()})';
3927 } else if (enclosingLine === line) {
3928 // Fit the enclosing function and callsite on same line.
3929 let colDistance = col - enclosingCol - 3;
3930 if (colDistance < 0) {
3931 colDistance = 0;
3932 }
3933 code =
3934 '\n'.repeat(enclosingLine - 1) +
3935 '({' +
3936 encodedName +
3937 ':\n' +
3938 ' '.repeat(enclosingCol) +
3939 '_=>' +
3940 ' '.repeat(colDistance) +
3941 '_()})';
3942 } else {
3943 // This is the ideal because we can always encode any position.
3944 code =
3945 '\n'.repeat(enclosingLine - 1) +
3946 '({' +
3947 encodedName +
3948 ':\n' +
3949 ' '.repeat(enclosingCol) +
3950 '_=>' +
3951 '\n'.repeat(line - enclosingLine) +
3952 ' '.repeat(col) +
3953 '_()})';
3954 }
3955
3956 if (enclosingLine < 1) {
3957 // If the function starts at the first line, we append the comment after.
3958 code = code + '\n' + comment;
3959 } else {
3960 // Otherwise we prepend the comment on the first line.
3961 code = comment + code;
3962 }
3963
3964 if (filename.startsWith('/')) {
3965 // If the filename starts with `/` we assume that it is a file system file
3966 // rather than relative to the current host. Since on the server fully qualified
3967 // stack traces use the file path.
3968 // TODO: What does this look like on Windows?
3969 filename = 'file://' + filename;
3970 }
3971
3972 if (sourceMap) {
3973 // We use the prefix about://React/ to separate these from other files listed in
3974 // the Chrome DevTools. We need a "host name" and not just a protocol because
3975 // otherwise the group name becomes the root folder. Ideally we don't want to
3976 // show these at all but there's two reasons to assign a fake URL.
3977 // 1) A printed stack trace string needs a unique URL to be able to source map it.
3978 // 2) If source maps are disabled or fails, you should at least be able to tell
3979 // which file it was.
3980 code +=
3981 '\n//# sourceURL=about://React/' +
3982 encodeURIComponent(environmentName) +
3983 '/' +
3984 encodeURI(filename) +
3985 '?' +
3986 fakeFunctionIdx++;
3987 code += '\n//# sourceMappingURL=' + sourceMap;
3988 } else if (filename) {
3989 code += '\n//# sourceURL=' + encodeURI(filename);
3990 } else {
3991 code += '\n//# sourceURL=<anonymous>';
3992 }
3993
3994 let fn: FakeFunction<T>;
3995 try {
3996 // eslint-disable-next-line no-eval
3997 fn = (0, eval)(code)[name];
3998 } catch (x) {
3999 // If eval fails, such as if in an environment that doesn't support it,
4000 // we fallback to creating a function here. It'll still have the right
4001 // name but it'll lose line/column number and file name.
4002 fn = function (_) {
4003 return _();
4004 };
4005 // Using the usual {[name]: _() => _()}.bind() trick to avoid minifiers
4006 // doesn't work here since this will produce `Object.*` names.
4007 Object.defineProperty(
4008 fn,
4009 // $FlowFixMe[cannot-write] -- `name` is configurable though.
4010 'name',
4011 {value: name},
4012 );
4013 }
4014 return fn;
4015 }
4016
4017 function buildFakeCallStack<T>(
4018 response: Response,
4019 stack: ReactStackTrace,
4020 environmentName: string,
4021 useEnclosingLine: boolean,
4022 innerCall: () => T,
4023 ): () => T {
4024 let callStack = innerCall;
4025 for (let i = 0; i < stack.length; i++) {
4026 const frame = stack[i];
4027 const frameKey =
4028 frame.join('-') +
4029 '-' +
4030 environmentName +
4031 (useEnclosingLine ? '-e' : '-n');
4032 let fn = fakeFunctionCache.get(frameKey);
4033 if (fn === undefined) {
4034 const [name, filename, line, col, enclosingLine, enclosingCol] = frame;
4035 const findSourceMapURL = response._debugFindSourceMapURL;
4036 const sourceMap = findSourceMapURL
4037 ? findSourceMapURL(filename, environmentName)
4038 : null;
4039 fn = createFakeFunction(
4040 name,
4041 filename,
4042 sourceMap,
4043 line,
4044 col,
4045 useEnclosingLine ? line : enclosingLine,
4046 useEnclosingLine ? col : enclosingCol,
4047 environmentName,
4048 );
4049 // TODO: This cache should technically live on the response since the _debugFindSourceMapURL
4050 // function is an input and can vary by response.
4051 fakeFunctionCache.set(frameKey, fn);
4052 }
4053 callStack = fn.bind(null, callStack);
4054 }
4055 return callStack;
4056 }
4057
4058 function getRootTask(
4059 response: Response,
4060 childEnvironmentName: string,
4061 ): null | ConsoleTask {
4062 const rootTask = response._debugRootTask;
4063 if (!rootTask) {
4064 return null;
4065 }
4066 if (response._rootEnvironmentName !== childEnvironmentName) {
4067 // If the root most owner component is itself in a different environment than the requested
4068 // environment then we create an extra task to indicate that we're transitioning into it.
4069 // Like if one environment just requests another environment.
4070 const createTaskFn = (console as any).createTask.bind(
4071 console,
4072 '"use ' + childEnvironmentName.toLowerCase() + '"',
4073 );
4074 return rootTask.run(createTaskFn);
4075 }
4076 return rootTask;
4077 }
4078
4079 function initializeFakeTask(
4080 response: Response,
4081 debugInfo: ReactComponentInfo | ReactAsyncInfo | ReactIOInfo,
4082 ): null | ConsoleTask {
4083 if (!supportsCreateTask) {
4084 return null;
4085 }
4086 if (debugInfo.stack == null) {
4087 // If this is an error, we should've really already initialized the task.
4088 // If it's null, we can't initialize a task.
4089 return null;
4090 }
4091 const cachedEntry = debugInfo.debugTask;
4092 if (cachedEntry !== undefined) {
4093 return cachedEntry;
4094 }
4095
4096 // Workaround for a bug where Chrome Performance tracking uses the enclosing line/column
4097 // instead of the callsite. For ReactAsyncInfo/ReactIOInfo, the only thing we're going
4098 // to use the fake task for is the Performance tracking so we encode the enclosing line/
4099 // column at the callsite to get a better line number. We could do this for Components too
4100 // but we're going to use those for other things too like console logs and it's not worth
4101 // duplicating. If this bug is every fixed in Chrome, this should be set to false.
4102 const useEnclosingLine = debugInfo.key === undefined;
4103
4104 const stack = debugInfo.stack;
4105 const env: string =
4106 debugInfo.env == null ? response._rootEnvironmentName : debugInfo.env;
4107 const ownerEnv: string =
4108 debugInfo.owner == null || debugInfo.owner.env == null
4109 ? response._rootEnvironmentName
4110 : debugInfo.owner.env;
4111 const ownerTask =
4112 debugInfo.owner == null
4113 ? null
4114 : initializeFakeTask(response, debugInfo.owner);
4115 const taskName =
4116 // This is the boundary between two environments so we'll annotate the task name.
4117 // We assume that the stack frame of the entry into the new environment was done
4118 // from the old environment. So we use the owner's environment as the current.
4119 env !== ownerEnv
4120 ? '"use ' + env.toLowerCase() + '"'
4121 : // Some unfortunate pattern matching to refine the type.
4122 debugInfo.key !== undefined
4123 ? getServerComponentTaskName(debugInfo as any as ReactComponentInfo)
4124 : debugInfo.name !== undefined
4125 ? getIOInfoTaskName(debugInfo as any as ReactIOInfo)
4126 : getAsyncInfoTaskName(debugInfo as any as ReactAsyncInfo);
4127 // $FlowFixMe[cannot-write]: We consider this part of initialization.
4128 return (debugInfo.debugTask = buildFakeTask(
4129 response,
4130 ownerTask,
4131 stack,
4132 taskName,
4133 ownerEnv,
4134 useEnclosingLine,
4135 ));
4136 }
4137
4138 function buildFakeTask(
4139 response: Response,
4140 ownerTask: null | ConsoleTask,
4141 stack: ReactStackTrace,
4142 taskName: string,
4143 env: string,
4144 useEnclosingLine: boolean,
4145 ): ConsoleTask {
4146 const createTaskFn = (console as any).createTask.bind(console, taskName);
4147 const callStack = buildFakeCallStack(
4148 response,
4149 stack,
4150 env,
4151 useEnclosingLine,
4152 createTaskFn,
4153 );
4154 if (ownerTask === null) {
4155 const rootTask = getRootTask(response, env);
4156 if (rootTask != null) {
4157 return rootTask.run(callStack);
4158 } else {
4159 return callStack();
4160 }
4161 } else {
4162 return ownerTask.run(callStack);
4163 }
4164 }
4165
4166 const createFakeJSXCallStack = {
4167 react_stack_bottom_frame: function (
4168 response: Response,
4169 stack: ReactStackTrace,
4170 environmentName: string,
4171 ): Error {
4172 const callStackForError = buildFakeCallStack(
4173 response,
4174 stack,
4175 environmentName,
4176 false,
4177 fakeJSXCallSite,
4178 );
4179 return callStackForError();
4180 },
4181 };
4182
4183 const createFakeJSXCallStackInDEV: (
4184 response: Response,
4185 stack: ReactStackTrace,
4186 environmentName: string,
4187 ) => Error = __DEV__
4188 ? // We use this technique to trick minifiers to preserve the function name.
4189 (createFakeJSXCallStack.react_stack_bottom_frame.bind(
4190 createFakeJSXCallStack,
4191 ) as any)
4192 : (null as any);
4193
4194 // v8 (Chromium, Node.js) defaults to 10
4195 // SpiderMonkey (Firefox) does not support Error.stackTraceLimit
4196 // JSC (Safari) defaults to 100
4197 // The lower the limit, the more likely we'll not reach react_stack_bottom_frame
4198 // The higher the limit, the slower Error() is when not inspecting with a debugger.
4199 // When inspecting with a debugger, Error.stackTraceLimit has no impact on Error() performance (in v8).
4200 const ownerStackTraceLimit = 10;
4201
4202 /** @noinline */
4203 function fakeJSXCallSite() {
4204 // This extra call frame represents the JSX creation function. We always pop this frame
4205 // off before presenting so it needs to be part of the stack.
4206 let error;
4207 const previousStackTraceLimit = Error.stackTraceLimit;
4208 Error.stackTraceLimit = ownerStackTraceLimit;
4209 error = Error('react-stack-top-frame'); // eslint-disable-line prefer-const
4210 Error.stackTraceLimit = previousStackTraceLimit;
4211 return error;
4212 }
4213
4214 function initializeFakeStack(
4215 response: Response,
4216 debugInfo: ReactComponentInfo | ReactAsyncInfo | ReactIOInfo,
4217 ): void {
4218 const cachedEntry = debugInfo.debugStack;
4219 if (cachedEntry !== undefined) {
4220 return;
4221 }
4222 if (debugInfo.stack != null) {
4223 const stack = debugInfo.stack;
4224 const env = debugInfo.env == null ? '' : debugInfo.env;
4225 // $FlowFixMe[cannot-write]
4226 debugInfo.debugStack = createFakeJSXCallStackInDEV(response, stack, env);
4227 }
4228 const owner = debugInfo.owner;
4229 if (owner != null) {
4230 // Initialize any owners not yet initialized.
4231 initializeFakeStack(response, owner);
4232 if (owner.debugLocation === undefined && debugInfo.debugStack != null) {
4233 // If we are the child of this owner, then the owner should be the bottom frame
4234 // our stack. We can use it as the implied location of the owner.
4235 owner.debugLocation = debugInfo.debugStack;
4236 }
4237 }
4238 }
4239
4240 function initializeDebugInfo(
4241 response: Response,
4242 debugInfo: ReactDebugInfoEntry,
4243 ): ReactDebugInfoEntry {
4244 if (!__DEV__) {
4245 // These errors should never make it into a build so we don't need to encode them in codes.json
4246 // eslint-disable-next-line react-internal/prod-error-codes
4247 throw new Error(
4248 'initializeDebugInfo should never be called in production mode. This is a bug in React.',
4249 );
4250 }
4251 if (debugInfo.stack !== undefined) {
4252 const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
4253 // $FlowFixMe[incompatible-type]
4254 debugInfo;
4255 // We eagerly initialize the fake task because this resolving happens outside any
4256 // render phase so we're not inside a user space stack at this point. If we waited
4257 // to initialize it when we need it, we might be inside user code.
4258 initializeFakeTask(response, componentInfoOrAsyncInfo);
4259 }
4260 if (debugInfo.owner == null && response._debugRootOwner != null) {
4261 const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
4262 // $FlowFixMe[incompatible-type]: By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
4263 debugInfo;
4264 // $FlowFixMe[cannot-write]
4265 componentInfoOrAsyncInfo.owner = response._debugRootOwner;
4266 // We clear the parsed stack frames to indicate that it needs to be re-parsed from debugStack.
4267 // $FlowFixMe[cannot-write]
4268 componentInfoOrAsyncInfo.stack = null;
4269 // We override the stack if we override the owner since the stack where the root JSX
4270 // was created on the server isn't very useful but where the request was made is.
4271 // $FlowFixMe[cannot-write]
4272 componentInfoOrAsyncInfo.debugStack = response._debugRootStack;
4273 // $FlowFixMe[cannot-write]
4274 componentInfoOrAsyncInfo.debugTask = response._debugRootTask;
4275 } else if (debugInfo.stack !== undefined) {
4276 const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
4277 // $FlowFixMe[incompatible-type]
4278 debugInfo;
4279 initializeFakeStack(response, componentInfoOrAsyncInfo);
4280 }
4281 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4282 if (typeof debugInfo.time === 'number') {
4283 // Adjust the time to the current environment's time space.
4284 // Since this might be a deduped object, we clone it to avoid
4285 // applying the adjustment twice.
4286 debugInfo = {
4287 time: debugInfo.time + response._timeOrigin,
4288 };
4289 }
4290 }
4291 return debugInfo;
4292 }
4293
4294 function resolveDebugModel(
4295 response: Response,
4296 id: number,
4297 json: UninitializedModel,
4298 ): void {
4299 const parentChunk = getChunk(response, id);
4300 if (
4301 parentChunk.status === INITIALIZED ||
4302 parentChunk.status === ERRORED ||
4303 parentChunk.status === HALTED ||
4304 parentChunk.status === BLOCKED
4305 ) {
4306 // We shouldn't really get debug info late. It's too late to add it after we resolved.
4307 return;
4308 }
4309 if (parentChunk.status === RESOLVED_MODULE) {
4310 // We don't expect to get debug info on modules.
4311 return;
4312 }
4313 const previousChunk = parentChunk._debugChunk;
4314 const debugChunk: ResolvedModelChunk<ReactDebugInfoEntry> =
4315 createResolvedModelChunk(response, json);
4316 debugChunk._debugChunk = previousChunk; // Linked list of the debug chunks
4317 parentChunk._debugChunk = debugChunk;
4318 initializeDebugChunk(response, parentChunk);
4319 if (
4320 __DEV__ &&
4321 (debugChunk as any as SomeChunk<any>).status === BLOCKED &&
4322 (response._debugChannel === undefined ||
4323 !response._debugChannel.hasReadable)
4324 ) {
4325 if (json[0] === '"' && json[1] === '$') {
4326 const path = json.slice(2, json.length - 1).split(':');
4327 const outlinedId = parseInt(path[0], 16);
4328 const chunk = getChunk(response, outlinedId);
4329 if (chunk.status === PENDING) {
4330 // We expect the debug chunk to have been emitted earlier in the stream. It might be
4331 // blocked on other things but chunk should no longer be pending.
4332 // If it's still pending that suggests that it was referencing an object in the debug
4333 // channel, but no debug channel was wired up so it's missing. In this case we can just
4334 // drop the debug info instead of halting the whole stream.
4335 parentChunk._debugChunk = null;
4336 }
4337 }
4338 }
4339 }
4340
4341 let currentOwnerInDEV: null | ReactComponentInfo = null;
4342 function getCurrentStackInDEV(): string {
4343 if (__DEV__) {
4344 const owner: null | ReactComponentInfo = currentOwnerInDEV;
4345 if (owner === null) {
4346 return '';
4347 }
4348 return getOwnerStackByComponentInfoInDev(owner);
4349 }
4350 return '';
4351 }
4352
4353 const replayConsoleWithCallStack = {
4354 react_stack_bottom_frame: function (
4355 response: Response,
4356 payload: ConsoleEntry,
4357 ): void {
4358 const methodName = payload[0];
4359 const stackTrace = payload[1];
4360 const owner = payload[2];
4361 const env = payload[3];
4362 const args = payload.slice(4);
4363
4364 // There really shouldn't be anything else on the stack atm.
4365 const prevStack = ReactSharedInternals.getCurrentStack;
4366 ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;
4367 currentOwnerInDEV =
4368 owner === null ? (response._debugRootOwner as any) : owner;
4369
4370 try {
4371 const callStack = buildFakeCallStack(
4372 response,
4373 stackTrace,
4374 env,
4375 false,
4376 bindToConsole(methodName, args, env),
4377 );
4378 if (owner != null) {
4379 const task = initializeFakeTask(response, owner);
4380 initializeFakeStack(response, owner);
4381 if (task !== null) {
4382 task.run(callStack);
4383 return;
4384 }
4385 }
4386 const rootTask = getRootTask(response, env);
4387 if (rootTask != null) {
4388 rootTask.run(callStack);
4389 return;
4390 }
4391 callStack();
4392 } finally {
4393 currentOwnerInDEV = null;
4394 ReactSharedInternals.getCurrentStack = prevStack;
4395 }
4396 },
4397 };
4398
4399 const replayConsoleWithCallStackInDEV: (
4400 response: Response,
4401 payload: ConsoleEntry,
4402 ) => void = __DEV__
4403 ? // We use this technique to trick minifiers to preserve the function name.
4404 (replayConsoleWithCallStack.react_stack_bottom_frame.bind(
4405 replayConsoleWithCallStack,
4406 ) as any)
4407 : (null as any);
4408
4409 type ConsoleEntry = [
4410 string,
4411 ReactStackTrace,
4412 null | ReactComponentInfo,
4413 string,
4414 mixed,
4415 ];
4416
4417 function resolveConsoleEntry(
4418 response: Response,
4419 json: UninitializedModel,
4420 ): void {
4421 if (!__DEV__) {
4422 // These errors should never make it into a build so we don't need to encode them in codes.json
4423 // eslint-disable-next-line react-internal/prod-error-codes
4424 throw new Error(
4425 'resolveConsoleEntry should never be called in production mode. This is a bug in React.',
4426 );
4427 }
4428
4429 if (!response._replayConsole) {
4430 return;
4431 }
4432
4433 const blockedChunk = response._blockedConsole;
4434 if (blockedChunk == null) {
4435 // If we're not blocked on any other chunks, we can try to eagerly initialize
4436 // this as a fast-path to avoid awaiting them.
4437 const chunk: ResolvedModelChunk<ConsoleEntry> = createResolvedModelChunk(
4438 response,
4439 json,
4440 );
4441 initializeModelChunk(chunk);
4442 const initializedChunk: SomeChunk<ConsoleEntry> = chunk;
4443 if (initializedChunk.status === INITIALIZED) {
4444 replayConsoleWithCallStackInDEV(response, initializedChunk.value);
4445 } else {
4446 chunk.then(
4447 v => replayConsoleWithCallStackInDEV(response, v),
4448 e => {
4449 // Ignore console errors for now. Unnecessary noise.
4450 },
4451 );
4452 response._blockedConsole = chunk;
4453 }
4454 } else {
4455 // We're still waiting on a previous chunk so we can't enqueue quite yet.
4456 const chunk: SomeChunk<ConsoleEntry> = createPendingChunk(response);
4457 chunk.then(
4458 v => replayConsoleWithCallStackInDEV(response, v),
4459 e => {
4460 // Ignore console errors for now. Unnecessary noise.
4461 },
4462 );
4463 response._blockedConsole = chunk;
4464 const unblock = () => {
4465 if (response._blockedConsole === chunk) {
4466 // We were still the last chunk so we can now clear the queue and return
4467 // to synchronous emitting.
4468 response._blockedConsole = null;
4469 }
4470 resolveModelChunk(response, chunk, json);
4471 };
4472 blockedChunk.then(unblock, unblock);
4473 }
4474 }
4475
4476 function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
4477 if (ioInfo.stack !== undefined) {
4478 initializeFakeTask(response, ioInfo);
4479 initializeFakeStack(response, ioInfo);
4480 }
4481 // Adjust the time to the current environment's time space.
4482 // $FlowFixMe[cannot-write]
4483 ioInfo.start += response._timeOrigin;
4484 // $FlowFixMe[cannot-write]
4485 ioInfo.end += response._timeOrigin;
4486
4487 if (enableComponentPerformanceTrack && response._replayConsole) {
4488 const env = response._rootEnvironmentName;
4489 const promise = ioInfo.value;
4490 if (promise) {
4491 const thenable: Thenable<mixed> = promise as any;
4492 switch (thenable.status) {
4493 case INITIALIZED:
4494 logIOInfo(ioInfo, env, thenable.value);
4495 break;
4496 case ERRORED:
4497 logIOInfoErrored(ioInfo, env, thenable.reason);
4498 break;
4499 default:
4500 // If we haven't resolved the Promise yet, wait to log until have so we can include
4501 // its data in the log.
4502 promise.then(
4503 logIOInfo.bind(null, ioInfo, env),
4504 logIOInfoErrored.bind(null, ioInfo, env),
4505 );
4506 break;
4507 }
4508 } else {
4509 logIOInfo(ioInfo, env, undefined);
4510 }
4511 }
4512 }
4513
4514 function resolveIOInfo(
4515 response: Response,
4516 id: number,
4517 model: UninitializedModel,
4518 ): void {
4519 const chunks = response._chunks;
4520 let chunk = chunks.get(id);
4521 const prevIsInitializingDebugInfo = isInitializingDebugInfo;
4522 isInitializingDebugInfo = true;
4523 try {
4524 if (!chunk) {
4525 chunk = createResolvedModelChunk(response, model);
4526 chunks.set(id, chunk);
4527 initializeModelChunk(chunk);
4528 } else {
4529 resolveModelChunk(response, chunk, model);
4530 if (chunk.status === RESOLVED_MODEL) {
4531 initializeModelChunk(chunk);
4532 }
4533 }
4534 } finally {
4535 isInitializingDebugInfo = prevIsInitializingDebugInfo;
4536 }
4537 if (chunk.status === INITIALIZED) {
4538 initializeIOInfo(response, chunk.value);
4539 } else {
4540 chunk.then(
4541 v => {
4542 initializeIOInfo(response, v);
4543 },
4544 e => {
4545 // Ignore debug info errors for now. Unnecessary noise.
4546 },
4547 );
4548 }
4549 }
4550
4551 function mergeBuffer(
4552 buffer: Array<Uint8Array>,
4553 lastChunk: Uint8Array,
4554 ): Uint8Array {
4555 const l = buffer.length;
4556 // Count the bytes we'll need
4557 let byteLength = lastChunk.length;
4558 for (let i = 0; i < l; i++) {
4559 byteLength += buffer[i].byteLength;
4560 }
4561 // Allocate enough contiguous space
4562 const result = new Uint8Array(byteLength);
4563 let offset = 0;
4564 // Copy all the buffers into it.
4565 for (let i = 0; i < l; i++) {
4566 const chunk = buffer[i];
4567 result.set(chunk, offset);
4568 offset += chunk.byteLength;
4569 }
4570 result.set(lastChunk, offset);
4571 return result;
4572 }
4573
4574 function resolveTypedArray(
4575 response: Response,
4576 id: number,
4577 buffer: Array<Uint8Array>,
4578 lastChunk: Uint8Array,
4579 constructor: any,
4580 bytesPerElement: number,
4581 streamState: StreamState,
4582 ): void {
4583 // If the view fits into one original buffer, we just reuse that buffer instead of
4584 // copying it out to a separate copy. This means that it's not always possible to
4585 // transfer these values to other threads without copying first since they may
4586 // share array buffer. For this to work, it must also have bytes aligned to a
4587 // multiple of a size of the type.
4588 const chunk =
4589 buffer.length === 0 && lastChunk.byteOffset % bytesPerElement === 0
4590 ? lastChunk
4591 : mergeBuffer(buffer, lastChunk);
4592 // TODO: The transfer protocol of RSC is little-endian. If the client isn't little-endian
4593 // we should convert it instead. In practice big endian isn't really Web compatible so it's
4594 // somewhat safe to assume that browsers aren't going to run it, but maybe there's some SSR
4595 // server that's affected.
4596 const view: $ArrayBufferView = new constructor(
4597 chunk.buffer,
4598 chunk.byteOffset,
4599 chunk.byteLength / bytesPerElement,
4600 );
4601 resolveBuffer(response, id, view, streamState);
4602 }
4603
4604 function logComponentInfo(
4605 response: Response,
4606 root: SomeChunk<any>,
4607 componentInfo: ReactComponentInfo,
4608 trackIdx: number,
4609 startTime: number,
4610 componentEndTime: number,
4611 childrenEndTime: number,
4612 isLastComponent: boolean,
4613 ): void {
4614 // $FlowFixMe[incompatible-type]: Refined.
4615 if (
4616 isLastComponent &&
4617 root.status === ERRORED &&
4618 root.reason !== response._closedReason
4619 ) {
4620 // If this is the last component to render before this chunk rejected, then conceptually
4621 // this component errored. If this was a cancellation then it wasn't this component that
4622 // errored.
4623 logComponentErrored(
4624 componentInfo,
4625 trackIdx,
4626 startTime,
4627 componentEndTime,
4628 childrenEndTime,
4629 response._rootEnvironmentName,
4630 root.reason,
4631 );
4632 } else {
4633 logComponentRender(
4634 componentInfo,
4635 trackIdx,
4636 startTime,
4637 componentEndTime,
4638 childrenEndTime,
4639 response._rootEnvironmentName,
4640 );
4641 }
4642 }
4643
4644 function flushComponentPerformance(
4645 response: Response,
4646 root: SomeChunk<any>,
4647 trackIdx: number, // Next available track
4648 trackTime: number, // The time after which it is available,
4649 parentEndTime: number,
4650 ): ProfilingResult {
4651 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
4652 // eslint-disable-next-line react-internal/prod-error-codes
4653 throw new Error(
4654 'flushComponentPerformance should never be called in production mode. This is a bug in React.',
4655 );
4656 }
4657 // Write performance.measure() entries for Server Components in tree order.
4658 // This must be done at the end to collect the end time from the whole tree.
4659 if (!isArray(root._children)) {
4660 // We have already written this chunk. If this was a cycle, then this will
4661 // be -Infinity and it won't contribute to the parent end time.
4662 // If this was already emitted by another sibling then we reused the same
4663 // chunk in two places. We should extend the current end time as if it was
4664 // rendered as part of this tree.
4665 const previousResult: ProfilingResult = root._children;
4666 const previousEndTime = previousResult.endTime;
4667 if (
4668 parentEndTime > -Infinity &&
4669 parentEndTime < previousEndTime &&
4670 previousResult.component !== null
4671 ) {
4672 // Log a placeholder for the deduped value under this child starting
4673 // from the end of the self time of the parent and spanning until the
4674 // the deduped end.
4675 logDedupedComponentRender(
4676 previousResult.component,
4677 trackIdx,
4678 parentEndTime,
4679 previousEndTime,
4680 response._rootEnvironmentName,
4681 );
4682 }
4683 // Since we didn't bump the track this time, we just return the same track.
4684 previousResult.track = trackIdx;
4685 return previousResult;
4686 }
4687 const children = root._children;
4688
4689 // First find the start time of the first component to know if it was running
4690 // in parallel with the previous.
4691 let debugInfo = null;
4692 if (__DEV__) {
4693 debugInfo = root._debugInfo;
4694 if (debugInfo.length === 0 && root.status === 'fulfilled') {
4695 const resolvedValue = resolveLazy(root.value);
4696 if (
4697 typeof resolvedValue === 'object' &&
4698 resolvedValue !== null &&
4699 (isArray(resolvedValue) ||
4700 typeof resolvedValue[ASYNC_ITERATOR] === 'function' ||
4701 resolvedValue.$$typeof === REACT_ELEMENT_TYPE ||
4702 resolvedValue.$$typeof === REACT_LAZY_TYPE) &&
4703 isArray(resolvedValue._debugInfo)
4704 ) {
4705 // It's possible that the value has been given the debug info.
4706 // In that case we need to look for it on the resolved value.
4707 debugInfo = resolvedValue._debugInfo;
4708 }
4709 }
4710 }
4711 if (debugInfo) {
4712 let startTime = 0;
4713 for (let i = 0; i < debugInfo.length; i++) {
4714 const info = debugInfo[i];
4715 if (typeof info.time === 'number') {
4716 startTime = info.time;
4717 }
4718 if (typeof info.name === 'string') {
4719 if (startTime < trackTime) {
4720 // The start time of this component is before the end time of the previous
4721 // component on this track so we need to bump the next one to a parallel track.
4722 trackIdx++;
4723 }
4724 trackTime = startTime;
4725 break;
4726 }
4727 }
4728 for (let i = debugInfo.length - 1; i >= 0; i--) {
4729 const info = debugInfo[i];
4730 if (typeof info.time === 'number') {
4731 if (info.time > parentEndTime) {
4732 parentEndTime = info.time;
4733 break; // We assume the highest number is at the end.
4734 }
4735 }
4736 }
4737 }
4738
4739 const result: ProfilingResult = {
4740 track: trackIdx,
4741 endTime: -Infinity,
4742 component: null,
4743 };
4744 root._children = result;
4745 let childrenEndTime = -Infinity;
4746 let childTrackIdx = trackIdx;
4747 let childTrackTime = trackTime;
4748 for (let i = 0; i < children.length; i++) {
4749 const childResult = flushComponentPerformance(
4750 response,
4751 children[i],
4752 childTrackIdx,
4753 childTrackTime,
4754 parentEndTime,
4755 );
4756 if (childResult.component !== null) {
4757 result.component = childResult.component;
4758 }
4759 childTrackIdx = childResult.track;
4760 const childEndTime = childResult.endTime;
4761 if (childEndTime > childTrackTime) {
4762 childTrackTime = childEndTime;
4763 }
4764 if (childEndTime > childrenEndTime) {
4765 childrenEndTime = childEndTime;
4766 }
4767 }
4768
4769 if (debugInfo) {
4770 // Write debug info in reverse order (just like stack traces).
4771 let componentEndTime = 0;
4772 let isLastComponent = true;
4773 let endTime = -1;
4774 let endTimeIdx = -1;
4775 for (let i = debugInfo.length - 1; i >= 0; i--) {
4776 const info = debugInfo[i];
4777 if (typeof info.time !== 'number') {
4778 continue;
4779 }
4780 if (componentEndTime === 0) {
4781 // Last timestamp is the end of the last component.
4782 componentEndTime = info.time;
4783 }
4784 const time = info.time;
4785 if (endTimeIdx > -1) {
4786 // Now that we know the start and end time, we can emit the entries between.
4787 for (let j = endTimeIdx - 1; j > i; j--) {
4788 const candidateInfo = debugInfo[j];
4789 if (typeof candidateInfo.name === 'string') {
4790 if (componentEndTime > childrenEndTime) {
4791 childrenEndTime = componentEndTime;
4792 }
4793 // $FlowFixMe[incompatible-type]: Refined.
4794 const componentInfo: ReactComponentInfo = candidateInfo;
4795 logComponentInfo(
4796 response,
4797 root,
4798 componentInfo,
4799 trackIdx,
4800 time,
4801 componentEndTime,
4802 childrenEndTime,
4803 isLastComponent,
4804 );
4805 componentEndTime = time; // The end time of previous component is the start time of the next.
4806 // Track the root most component of the result for deduping logging.
4807 result.component = componentInfo;
4808 isLastComponent = false;
4809 } else if (
4810 candidateInfo.awaited &&
4811 // Skip awaits on client resources since they didn't block the server component.
4812 candidateInfo.awaited.env != null
4813 ) {
4814 if (endTime > childrenEndTime) {
4815 childrenEndTime = endTime;
4816 }
4817 // $FlowFixMe[incompatible-type]: Refined.
4818 const asyncInfo: ReactAsyncInfo = candidateInfo;
4819 const env = response._rootEnvironmentName;
4820 const promise = asyncInfo.awaited.value;
4821 if (promise) {
4822 const thenable: Thenable<mixed> = promise as any;
4823 switch (thenable.status) {
4824 case INITIALIZED:
4825 logComponentAwait(
4826 asyncInfo,
4827 trackIdx,
4828 time,
4829 endTime,
4830 env,
4831 thenable.value,
4832 );
4833 break;
4834 case ERRORED:
4835 logComponentAwaitErrored(
4836 asyncInfo,
4837 trackIdx,
4838 time,
4839 endTime,
4840 env,
4841 thenable.reason,
4842 );
4843 break;
4844 default:
4845 // We assume that we should have received the data by now since this is logged at the
4846 // end of the response stream. This is more sensitive to ordering so we don't wait
4847 // to log it.
4848 logComponentAwait(
4849 asyncInfo,
4850 trackIdx,
4851 time,
4852 endTime,
4853 env,
4854 undefined,
4855 );
4856 break;
4857 }
4858 } else {
4859 logComponentAwait(
4860 asyncInfo,
4861 trackIdx,
4862 time,
4863 endTime,
4864 env,
4865 undefined,
4866 );
4867 }
4868 }
4869 }
4870 } else {
4871 // Anything between the end and now was aborted if it has no end time.
4872 // Either because the client stream was aborted reading it or the server stream aborted.
4873 endTime = time; // If we don't find anything else the endTime is the start time.
4874 for (let j = debugInfo.length - 1; j > i; j--) {
4875 const candidateInfo = debugInfo[j];
4876 if (typeof candidateInfo.name === 'string') {
4877 if (componentEndTime > childrenEndTime) {
4878 childrenEndTime = componentEndTime;
4879 }
4880 // $FlowFixMe[incompatible-type]: Refined.
4881 const componentInfo: ReactComponentInfo = candidateInfo;
4882 const env = response._rootEnvironmentName;
4883 logComponentAborted(
4884 componentInfo,
4885 trackIdx,
4886 time,
4887 componentEndTime,
4888 childrenEndTime,
4889 env,
4890 );
4891 componentEndTime = time; // The end time of previous component is the start time of the next.
4892 // Track the root most component of the result for deduping logging.
4893 result.component = componentInfo;
4894 isLastComponent = false;
4895 } else if (
4896 candidateInfo.awaited &&
4897 // Skip awaits on client resources since they didn't block the server component.
4898 candidateInfo.awaited.env != null
4899 ) {
4900 // If we don't have an end time for an await, that means we aborted.
4901 const asyncInfo: ReactAsyncInfo = candidateInfo;
4902 const env = response._rootEnvironmentName;
4903 if (asyncInfo.awaited.end > endTime) {
4904 endTime = asyncInfo.awaited.end; // Take the end time of the I/O as the await end.
4905 }
4906 if (endTime > childrenEndTime) {
4907 childrenEndTime = endTime;
4908 }
4909 logComponentAwaitAborted(asyncInfo, trackIdx, time, endTime, env);
4910 }
4911 }
4912 }
4913 endTime = time; // The end time of the next entry is this time.
4914 endTimeIdx = i;
4915 }
4916 }
4917 result.endTime = childrenEndTime;
4918 return result;
4919 }
4920
4921 function flushInitialRenderPerformance(response: Response): void {
4922 if (
4923 enableProfilerTimer &&
4924 enableComponentPerformanceTrack &&
4925 response._replayConsole
4926 ) {
4927 const rootChunk = getChunk(response, 0);
4928 if (isArray(rootChunk._children)) {
4929 markAllTracksInOrder();
4930 flushComponentPerformance(response, rootChunk, 0, -Infinity, -Infinity);
4931 }
4932 }
4933 }
4934
4935 function processFullBinaryRow(
4936 response: Response,
4937 streamState: StreamState,
4938 id: number,
4939 tag: number,
4940 buffer: Array<Uint8Array>,
4941 chunk: Uint8Array,
4942 ): void {
4943 switch (tag) {
4944 case 65 /* "A" */:
4945 // We must always clone to extract it into a separate buffer instead of just a view.
4946 resolveBuffer(
4947 response,
4948 id,
4949 mergeBuffer(buffer, chunk).buffer,
4950 streamState,
4951 );
4952 return;
4953 case 79 /* "O" */:
4954 resolveTypedArray(response, id, buffer, chunk, Int8Array, 1, streamState);
4955 return;
4956 case 111 /* "o" */:
4957 resolveBuffer(
4958 response,
4959 id,
4960 buffer.length === 0 ? chunk : mergeBuffer(buffer, chunk),
4961 streamState,
4962 );
4963 return;
4964 case 85 /* "U" */:
4965 resolveTypedArray(
4966 response,
4967 id,
4968 buffer,
4969 chunk,
4970 Uint8ClampedArray,
4971 1,
4972 streamState,
4973 );
4974 return;
4975 case 83 /* "S" */:
4976 resolveTypedArray(
4977 response,
4978 id,
4979 buffer,
4980 chunk,
4981 Int16Array,
4982 2,
4983 streamState,
4984 );
4985 return;
4986 case 115 /* "s" */:
4987 resolveTypedArray(
4988 response,
4989 id,
4990 buffer,
4991 chunk,
4992 Uint16Array,
4993 2,
4994 streamState,
4995 );
4996 return;
4997 case 76 /* "L" */:
4998 resolveTypedArray(
4999 response,
5000 id,
Showing first 5,000 of 5,606 lines. View raw