404
405
interface Reference {}
406
407
+type ReactClientReference = Reference & ReactClientValue;
408
+
409
+type DeferredDebugStore = {
410
+ retained: Map<number, ReactClientReference | string>,
411
+ existing: Map<ReactClientReference | string, number>,
412
+};
413
+
414
const OPENING = 10;
415
const OPEN = 11;
416
const ABORTING = 12;
458
filterStackFrame: (url: string, functionName: string) => boolean,
459
didWarnForKey: null | WeakSet<ReactComponentInfo>,
460
writtenDebugObjects: WeakMap<Reference, string>,
461
+ deferredDebugObjects: null | DeferredDebugStore,
462
};
463
464
const {
503
model: ReactClientValue,
504
bundlerConfig: ClientManifest,
505
onError: void | ((error: mixed) => ?string),
498
- identifierPrefix?: string,
506
onPostpone: void | ((reason: string) => void),
507
+ onAllReady: () => void,
508
+ onFatalError: (error: mixed) => void,
509
+ identifierPrefix?: string,
510
temporaryReferences: void | TemporaryReferenceSet,
511
environmentName: void | string | (() => string), // DEV-only
512
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
503
- onAllReady: () => void,
504
- onFatalError: (error: mixed) => void,
513
+ keepDebugAlive: boolean, // DEV-only
514
) {
515
if (
516
ReactSharedInternals.A !== null &&
580
: filterStackFrame;
581
this.didWarnForKey = null;
582
this.writtenDebugObjects = new WeakMap();
583
+ this.deferredDebugObjects = keepDebugAlive
584
+ ? {
585
+ retained: new Map(),
586
+ existing: new Map(),
587
+ }
588
+ : null;
589
}
590
591
let timeOrigin: number;
630
temporaryReferences: void | TemporaryReferenceSet,
631
environmentName: void | string | (() => string), // DEV-only
632
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
633
+ keepDebugAlive: boolean, // DEV-only
634
): Request {
635
if (__DEV__) {
636
resetOwnerStackLimit();
642
model,
643
bundlerConfig,
644
onError,
629
- identifierPrefix,
645
onPostpone,
646
+ noop,
647
+ noop,
648
+ identifierPrefix,
649
temporaryReferences,
650
environmentName,
651
filterStackFrame,
634
- noop,
635
- noop,
652
+ keepDebugAlive,
653
);
654
}
655
664
temporaryReferences: void | TemporaryReferenceSet,
665
environmentName: void | string | (() => string), // DEV-only
666
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
667
+ keepDebugAlive: boolean, // DEV-only
668
): Request {
669
if (__DEV__) {
670
resetOwnerStackLimit();
676
model,
677
bundlerConfig,
678
onError,
661
- identifierPrefix,
679
onPostpone,
680
+ onAllReady,
681
+ onFatalError,
682
+ identifierPrefix,
683
temporaryReferences,
684
environmentName,
685
filterStackFrame,
666
- onAllReady,
667
- onFatalError,
686
+ keepDebugAlive,
687
);
688
}
689
2350
return '$S' + name;
2351
}
2352
2334
-function serializeLimitedObject(): string {
2353
+function serializeDeferredObject(
2354
+ request: Request,
2355
+ value: ReactClientReference | string,
2356
+): string {
2357
+ const deferredDebugObjects = request.deferredDebugObjects;
2358
+ if (deferredDebugObjects !== null) {
2359
+ // This client supports a long lived connection. We can assign this object
2360
+ // an ID to be lazy loaded later.
2361
+ // This keeps the connection alive until we ask for it or release it.
2362
+ request.pendingChunks++;
2363
+ const id = request.nextChunkId++;
2364
+ deferredDebugObjects.existing.set(value, id);
2365
+ deferredDebugObjects.retained.set(id, value);
2366
+ return '$Y' + id.toString(16);
2367
+ }
2368
return '$Y';
2369
}
2370
4091
4092
if (counter.objectLimit <= 0 && !doNotLimit.has(value)) {
4093
// We've reached our max number of objects to serialize across the wire so we serialize this
4061
- // as a marker so that the client can error when this is accessed by the console.
4062
- return serializeLimitedObject();
4094
+ // as a marker so that the client can error or lazy load this when accessed by the console.
4095
+ return serializeDeferredObject(request, value);
4096
}
4097
4098
counter.objectLimit--;
4099
4100
+ const deferredDebugObjects = request.deferredDebugObjects;
4101
+ if (deferredDebugObjects !== null) {
4102
+ const deferredId = deferredDebugObjects.existing.get(value);
4103
+ // We earlier deferred this same object. We're now going to eagerly emit it so let's emit it
4104
+ // at the same ID that we already used to refer to it.
4105
+ if (deferredId !== undefined) {
4106
+ deferredDebugObjects.existing.delete(value);
4107
+ deferredDebugObjects.retained.delete(deferredId);
4108
+ emitOutlinedDebugModelChunk(request, deferredId, counter, value);
4109
+ return serializeByValueID(deferredId);
4110
+ }
4111
+ }
4112
+
4113
switch ((value: any).$$typeof) {
4114
case REACT_ELEMENT_TYPE: {
4115
const element: ReactElement = (value: any);
4281
}
4282
}
4283
if (value.length >= 1024) {
4284
+ // Large strings are counted towards the object limit.
4285
+ if (counter.objectLimit <= 0) {
4286
+ // We've reached our max number of objects to serialize across the wire so we serialize this
4287
+ // as a marker so that the client can error or lazy load this when accessed by the console.
4288
+ return serializeDeferredObject(request, value);
4289
+ }
4290
+ counter.objectLimit--;
4291
// For large strings, we encode them outside the JSON payload so that we
4292
// don't have to double encode and double parse the strings. This can also
4293
// be more compact in case the string has a lot of escaped characters.
5307
fatalError(request, error);
5308
}
5309
}
5310
+
5311
+function fromHex(str: string): number {
5312
+ return parseInt(str, 16);
5313
+}
5314
+
5315
+export function resolveDebugMessage(request: Request, message: string): void {
5316
+ if (!__DEV__) {
5317
+ // These errors should never make it into a build so we don't need to encode them in codes.json
5318
+ // eslint-disable-next-line react-internal/prod-error-codes
5319
+ throw new Error(
5320
+ 'resolveDebugMessage should never be called in production mode. This is a bug in React.',
5321
+ );
5322
+ }
5323
+ const deferredDebugObjects = request.deferredDebugObjects;
5324
+ if (deferredDebugObjects === null) {
5325
+ throw new Error(
5326
+ "resolveDebugMessage/closeDebugChannel should not be called for a Request that wasn't kept alive. This is a bug in React.",
5327
+ );
5328
+ }
5329
+ // This function lets the client ask for more data lazily through the debug channel.
5330
+ const command = message.charCodeAt(0);
5331
+ const ids = message.slice(2).split(',').map(fromHex);
5332
+ switch (command) {
5333
+ case 82 /* "R" */:
5334
+ // Release IDs
5335
+ for (let i = 0; i < ids.length; i++) {
5336
+ const id = ids[i];
5337
+ const retainedValue = deferredDebugObjects.retained.get(id);
5338
+ if (retainedValue !== undefined) {
5339
+ // We're no longer blocked on this. We won't emit it.
5340
+ request.pendingChunks--;
5341
+ deferredDebugObjects.retained.delete(id);
5342
+ deferredDebugObjects.existing.delete(retainedValue);
5343
+ enqueueFlush(request);
5344
+ }
5345
+ }
5346
+ break;
5347
+ case 81 /* "Q" */:
5348
+ // Query IDs
5349
+ for (let i = 0; i < ids.length; i++) {
5350
+ const id = ids[i];
5351
+ const retainedValue = deferredDebugObjects.retained.get(id);
5352
+ if (retainedValue !== undefined) {
5353
+ // If we still have this object, and haven't emitted it before, emit it on the stream.
5354
+ const counter = {objectLimit: 10};
5355
+ emitOutlinedDebugModelChunk(request, id, counter, retainedValue);
5356
+ enqueueFlush(request);
5357
+ }
5358
+ }
5359
+ break;
5360
+ default:
5361
+ throw new Error(
5362
+ 'Unknown command. The debugChannel was not wired up properly.',
5363
+ );
5364
+ }
5365
+}
5366
+
5367
+export function closeDebugChannel(request: Request): void {
5368
+ if (!__DEV__) {
5369
+ // These errors should never make it into a build so we don't need to encode them in codes.json
5370
+ // eslint-disable-next-line react-internal/prod-error-codes
5371
+ throw new Error(
5372
+ 'closeDebugChannel should never be called in production mode. This is a bug in React.',
5373
+ );
5374
+ }
5375
+ // This clears all remaining deferred objects, potentially resulting in the completion of the Request.
5376
+ const deferredDebugObjects = request.deferredDebugObjects;
5377
+ if (deferredDebugObjects === null) {
5378
+ throw new Error(
5379
+ "resolveDebugMessage/closeDebugChannel should not be called for a Request that wasn't kept alive. This is a bug in React.",
5380
+ );
5381
+ }
5382
+ deferredDebugObjects.retained.forEach((value, id) => {
5383
+ request.pendingChunks--;
5384
+ deferredDebugObjects.retained.delete(id);
5385
+ deferredDebugObjects.existing.delete(value);
5386
+ });
5387
+ enqueueFlush(request);
5388
+}