687
__DEV__ ? task.debugStack : null,
688
__DEV__ ? task.debugTask : null,
689
);
690
- if (__DEV__) {
691
- // If this came from Flight, forward any debug info into this new row.
692
- const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
693
- if (debugInfo) {
694
- forwardDebugInfo(request, newTask.id, debugInfo);
695
- }
696
- }
690
691
switch (thenable.status) {
692
case 'fulfilled': {
693
+ if (__DEV__) {
694
+ // If this came from Flight, forward any debug info into this new row.
695
+ const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
696
+ if (debugInfo) {
697
+ forwardDebugInfo(request, newTask, debugInfo);
698
+ }
699
+ }
700
// We have the resolved value, we can go ahead and schedule it for serialization.
701
newTask.model = thenable.value;
702
pingTask(request, newTask);
703
return newTask.id;
704
}
705
case 'rejected': {
706
+ if (__DEV__) {
707
+ // If this came from Flight, forward any debug info into this new row.
708
+ const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
709
+ if (debugInfo) {
710
+ forwardDebugInfo(request, newTask, debugInfo);
711
+ }
712
+ }
713
const x = thenable.reason;
714
erroredTask(request, newTask, x);
715
return newTask.id;
758
759
thenable.then(
760
value => {
761
+ if (__DEV__) {
762
+ // If this came from Flight, forward any debug info into this new row.
763
+ const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
764
+ if (debugInfo) {
765
+ forwardDebugInfo(request, newTask, debugInfo);
766
+ }
767
+ }
768
newTask.model = value;
769
pingTask(request, newTask);
770
},
771
reason => {
772
+ if (__DEV__) {
773
+ // If this came from Flight, forward any debug info into this new row.
774
+ const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
775
+ if (debugInfo) {
776
+ forwardDebugInfo(request, newTask, debugInfo);
777
+ }
778
+ }
779
if (newTask.status === PENDING) {
780
// We expect that the only status it might be otherwise is ABORTED.
781
// When we abort we emit chunks in each pending task slot and don't need
932
if (__DEV__) {
933
const debugInfo: ?ReactDebugInfo = (iterable: any)._debugInfo;
934
if (debugInfo) {
914
- forwardDebugInfo(request, streamTask.id, debugInfo);
935
+ forwardDebugInfo(request, streamTask, debugInfo);
936
}
937
}
938
1299
1300
let componentDebugInfo: ReactComponentInfo;
1301
if (__DEV__) {
1281
- if (debugID === null) {
1302
+ if (!canEmitDebugInfo) {
1303
// We don't have a chunk to assign debug info. We need to outline this
1304
// component to assign it an ID.
1305
return outlineTask(request, task);
1310
componentDebugInfo = (prevThenableState: any)._componentDebugInfo;
1311
} else {
1312
// This is a new component in the same task so we can emit more debug info.
1292
- const componentDebugID = debugID;
1313
+ const componentDebugID = task.id;
1314
const componentName =
1315
(Component: any).displayName || Component.name || '';
1316
const componentEnv = (0, request.environmentName)();
1564
const debugInfo: ?ReactDebugInfo = (children: any)._debugInfo;
1565
if (debugInfo) {
1566
// If this came from Flight, forward any debug info into this new row.
1546
- if (debugID === null) {
1567
+ if (!canEmitDebugInfo) {
1568
// We don't have a chunk to assign debug info. We need to outline this
1569
// component to assign it an ID.
1570
return outlineTask(request, task);
1572
// Forward any debug info we have the first time we see it.
1573
// We do this after init so that we have received all the debug info
1574
// from the server by the time we emit it.
1554
- forwardDebugInfo(request, debugID, debugInfo);
1575
+ forwardDebugInfo(request, task, debugInfo);
1576
}
1577
// Since we're rendering this array again, create a copy that doesn't
1578
// have the debug info so we avoid outlining or emitting debug info again.
1680
return element;
1681
}
1682
1662
-// The chunk ID we're currently rendering that we can assign debug data to.
1663
-let debugID: null | number = null;
1683
+// Determines if we're currently rendering at the top level of a task and therefore
1684
+// is safe to emit debug info associated with that task. Otherwise, if we're in
1685
+// a nested context, we need to first outline.
1686
+let canEmitDebugInfo: boolean = false;
1687
1688
// Approximate string length of the currently serializing row.
1689
// Used to power outlining heuristics.
1902
// First visit anything that blocked this sequence to start in the first place.
1903
if (node.previous !== null) {
1904
// We ignore the return value here because if it wasn't awaited in user space, then we don't log it.
1905
+ // It also means that it can just have been part of a previous component's render.
1906
// TODO: This means that some I/O can get lost that was still blocking the sequence.
1907
visitAsyncNode(request, task, node.previous, cutOff, visited);
1908
}
1914
return null;
1915
}
1916
case PROMISE_NODE: {
1893
- if (node.end < cutOff) {
1894
- // This was already resolved when we started this sequence. It must have been
1895
- // part of a different component.
1896
- // TODO: Think of some other way to exclude irrelevant data since if we awaited
1897
- // a cached promise, we should still log this component as being dependent on that data.
1917
+ if (node.end <= request.timeOrigin) {
1918
+ // This was already resolved when we started this render. It must have been either something
1919
+ // that's part of a start up sequence or externally cached data. We exclude that information.
1920
+ // The technique for debugging the effects of uncached data on the render is to simply uncache it.
1921
return null;
1922
}
1923
const awaited = node.awaited;
1951
// the thing that generated this node and its virtual children.
1952
const debugInfo = node.debugInfo;
1953
if (debugInfo !== null) {
1931
- forwardDebugInfo(request, task.id, debugInfo);
1954
+ forwardDebugInfo(request, task, debugInfo);
1955
}
1956
return match;
1957
}
1965
if (awaited !== null) {
1966
const ioNode = visitAsyncNode(request, task, awaited, cutOff, visited);
1967
if (ioNode !== null) {
1968
+ const startTime: number = node.start;
1969
let endTime: number;
1970
if (node.tag === UNRESOLVED_AWAIT_NODE) {
1971
// If we haven't defined an end time, use the resolve of the inner Promise.
1979
} else {
1980
endTime = node.end;
1981
}
1958
- if (endTime < cutOff) {
1959
- // This was already resolved when we started this sequence. It must have been
1960
- // part of a different component.
1961
- // TODO: Think of some other way to exclude irrelevant data since if we awaited
1962
- // a cached promise, we should still log this component as being dependent on that data.
1982
+ if (endTime <= request.timeOrigin) {
1983
+ // This was already resolved when we started this render. It must have been either something
1984
+ // that's part of a start up sequence or externally cached data. We exclude that information.
1985
+ return null;
1986
+ } else if (startTime < cutOff) {
1987
+ // We started awaiting this node before we started rendering this sequence.
1988
+ // This means that this particular await was never part of the current sequence.
1989
+ // If we have another await higher up in the chain it might have a more actionable stack
1990
+ // from the perspective of this component. If we end up here from the "previous" path,
1991
+ // then this gets I/O ignored, which is what we want because it means it was likely
1992
+ // just part of a previous component's rendering.
1993
+ match = ioNode;
1994
} else {
1995
const stack = filterStackTrace(
1996
request,
2009
// We log the environment at the time when the last promise pigned ping which may
2010
// be later than what the environment was when we actually started awaiting.
2011
const env = (0, request.environmentName)();
1981
- if (node.start <= cutOff) {
1982
- // If this was an await that started before this sequence but finished after,
1983
- // then we clamp it to the start of this sequence. We don't need to emit a time
1984
- // TODO: Typically we'll already have a previous time stamp with the cutOff time
1985
- // so we shouldn't need to emit another one. But not always.
1986
- emitTimingChunk(request, task.id, cutOff);
1987
- } else {
1988
- emitTimingChunk(request, task.id, node.start);
1989
- }
2012
+ emitTimingChunk(request, task.id, startTime);
2013
// Then emit a reference to us awaiting it in the current task.
2014
request.pendingChunks++;
2015
emitDebugChunk(request, task.id, {
2018
owner: node.owner,
2019
stack: stack,
2020
});
1998
- emitTimingChunk(request, task.id, node.end);
2021
+ emitTimingChunk(request, task.id, endTime);
2022
}
2023
}
2024
}
2036
debugInfo = node.debugInfo;
2037
}
2038
if (debugInfo !== null) {
2016
- forwardDebugInfo(request, task.id, debugInfo);
2039
+ forwardDebugInfo(request, task, debugInfo);
2040
}
2041
return match;
2042
}
2072
const env = (0, request.environmentName)();
2073
// If we don't have any thing awaited, the time we started awaiting was internal
2074
// when we yielded after rendering. The cutOff time is basically that.
2052
- emitTimingChunk(request, task.id, cutOff);
2075
+ const awaitStartTime = cutOff;
2076
+ // If the end time finished before we started, it could've been a cached thing so
2077
+ // we clamp it to the cutOff time. Effectively leading to a zero-time await.
2078
+ const awaitEndTime = awaitedNode.end < cutOff ? cutOff : awaitedNode.end;
2079
+ emitTimingChunk(request, task.id, awaitStartTime);
2080
emitDebugChunk(request, task.id, {
2081
awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference.
2082
env: env,
2083
});
2057
- emitTimingChunk(request, task.id, awaitedNode.end);
2084
+ emitTimingChunk(request, task.id, awaitEndTime);
2085
}
2086
}
2087
2790
const debugInfo: ?ReactDebugInfo = (value: any)._debugInfo;
2791
if (debugInfo) {
2792
// If this came from Flight, forward any debug info into this new row.
2766
- if (debugID === null) {
2793
+ if (!canEmitDebugInfo) {
2794
// We don't have a chunk to assign debug info. We need to outline this
2795
// component to assign it an ID.
2796
return outlineTask(request, task);
2797
} else {
2798
// Forward any debug info we have the first time we see it.
2772
- forwardDebugInfo(request, debugID, debugInfo);
2799
+ forwardDebugInfo(request, task, debugInfo);
2800
}
2801
}
2802
}
2872
const debugInfo: ?ReactDebugInfo = lazy._debugInfo;
2873
if (debugInfo) {
2874
// If this came from Flight, forward any debug info into this new row.
2848
- if (debugID === null) {
2875
+ if (!canEmitDebugInfo) {
2876
// We don't have a chunk to assign debug info. We need to outline this
2877
// component to assign it an ID.
2878
return outlineTask(request, task);
2880
// Forward any debug info we have the first time we see it.
2881
// We do this after init so that we have received all the debug info
2882
// from the server by the time we emit it.
2856
- forwardDebugInfo(request, debugID, debugInfo);
2883
+ forwardDebugInfo(request, task, debugInfo);
2884
}
2885
}
2886
}
4291
4292
function forwardDebugInfo(
4293
request: Request,
4267
- id: number,
4294
+ task: Task,
4295
debugInfo: ReactDebugInfo,
4296
) {
4297
+ const id = task.id;
4298
+ const minimumTime =
4299
+ enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0;
4300
for (let i = 0; i < debugInfo.length; i++) {
4301
const info = debugInfo[i];
4302
if (typeof info.time === 'number') {
4303
// When forwarding time we need to ensure to convert it to the time space of the payload.
4274
- emitTimingChunk(request, id, info.time);
4304
+ // We clamp the time to the starting render of the current component. It's as if it took
4305
+ // no time to render and await if we reuse cached content.
4306
+ emitTimingChunk(
4307
+ request,
4308
+ id,
4309
+ info.time < minimumTime ? minimumTime : info.time,
4310
+ );
4311
} else {
4276
- request.pendingChunks++;
4312
if (typeof info.name === 'string') {
4313
// We outline this model eagerly so that we can refer to by reference as an owner.
4314
// If we had a smarter way to dedupe we might not have to do this if there ends up
4315
// being no references to this as an owner.
4316
outlineComponentInfo(request, (info: any));
4317
// Emit a reference to the outlined one.
4318
+ request.pendingChunks++;
4319
emitDebugChunk(request, id, info);
4320
} else if (info.awaited) {
4321
const ioInfo = info.awaited;
4286
- // Outline the IO info in case the same I/O is awaited in more than one place.
4287
- outlineIOInfo(request, ioInfo);
4288
- // We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
4289
- let debugStack;
4290
- if (info.stack == null && info.debugStack != null) {
4291
- // If we have a debugStack but no parsed stack we should parse it.
4292
- debugStack = filterStackTrace(
4293
- request,
4294
- parseStackTrace(info.debugStack, 1),
4295
- );
4322
+ if (ioInfo.end <= request.timeOrigin) {
4323
+ // This was already resolved when we started this render. It must have been some
4324
+ // externally cached data. We exclude that information but we keep components and
4325
+ // awaits that happened inside this render but might have been deduped within the
4326
+ // render.
4327
} else {
4297
- debugStack = info.stack;
4298
- }
4299
- const debugAsyncInfo: Omit<ReactAsyncInfo, 'debugTask' | 'debugStack'> =
4300
- {
4328
+ // Outline the IO info in case the same I/O is awaited in more than one place.
4329
+ outlineIOInfo(request, ioInfo);
4330
+ // We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
4331
+ let debugStack;
4332
+ if (info.stack == null && info.debugStack != null) {
4333
+ // If we have a debugStack but no parsed stack we should parse it.
4334
+ debugStack = filterStackTrace(
4335
+ request,
4336
+ parseStackTrace(info.debugStack, 1),
4337
+ );
4338
+ } else {
4339
+ debugStack = info.stack;
4340
+ }
4341
+ const debugAsyncInfo: Omit<
4342
+ ReactAsyncInfo,
4343
+ 'debugTask' | 'debugStack',
4344
+ > = {
4345
awaited: ioInfo,
4346
};
4303
- if (info.env != null) {
4304
- // $FlowFixMe[cannot-write]
4305
- debugAsyncInfo.env = info.env;
4306
- }
4307
- if (info.owner != null) {
4308
- // $FlowFixMe[cannot-write]
4309
- debugAsyncInfo.owner = info.owner;
4310
- }
4311
- if (debugStack != null) {
4312
- // $FlowFixMe[cannot-write]
4313
- debugAsyncInfo.stack = debugStack;
4347
+ if (info.env != null) {
4348
+ // $FlowFixMe[cannot-write]
4349
+ debugAsyncInfo.env = info.env;
4350
+ }
4351
+ if (info.owner != null) {
4352
+ // $FlowFixMe[cannot-write]
4353
+ debugAsyncInfo.owner = info.owner;
4354
+ }
4355
+ if (debugStack != null) {
4356
+ // $FlowFixMe[cannot-write]
4357
+ debugAsyncInfo.stack = debugStack;
4358
+ }
4359
+ request.pendingChunks++;
4360
+ emitDebugChunk(request, id, debugAsyncInfo);
4361
}
4315
- emitDebugChunk(request, id, debugAsyncInfo);
4362
} else {
4317
- emitDebugChunk(request, id, debugInfo[i]);
4363
+ request.pendingChunks++;
4364
+ emitDebugChunk(request, id, info);
4365
}
4366
}
4367
}
4504
return;
4505
}
4506
4460
- const prevDebugID = debugID;
4507
+ const prevCanEmitDebugInfo = canEmitDebugInfo;
4508
task.status = RENDERING;
4509
4510
// We stash the outer parent size so we can restore it when we exit.
4519
modelRoot = task.model;
4520
4521
if (__DEV__) {
4475
- // Track the ID of the current task so we can assign debug info to this id.
4476
- debugID = task.id;
4522
+ // Track that we can emit debug info for the current task.
4523
+ canEmitDebugInfo = true;
4524
}
4525
4526
// We call the destructive form that mutates this task. That way if something
4536
if (__DEV__) {
4537
// We're now past rendering this task and future renders will spawn new tasks for their
4538
// debug info.
4492
- debugID = null;
4539
+ canEmitDebugInfo = false;
4540
}
4541
4542
// Track the root again for the resolved object.
4621
erroredTask(request, task, x);
4622
} finally {
4623
if (__DEV__) {
4577
- debugID = prevDebugID;
4624
+ canEmitDebugInfo = prevCanEmitDebugInfo;
4625
}
4626
serializedSize = parentSerializedSize;
4627
}
4630
function tryStreamTask(request: Request, task: Task): void {
4631
// This is used to try to emit something synchronously but if it suspends,
4632
// we emit a reference to a new outlined task immediately instead.
4586
- const prevDebugID = debugID;
4633
+ const prevCanEmitDebugInfo = canEmitDebugInfo;
4634
if (__DEV__) {
4588
- // We don't use the id of the stream task for debugID. Instead we leave it null
4589
- // so that we instead outline the row to get a new debugID if needed.
4590
- debugID = null;
4635
+ // We can't emit debug into to a specific row of a stream task. Instead we leave
4636
+ // it false so that we instead outline the row to get a new canEmitDebugInfo if needed.
4637
+ canEmitDebugInfo = false;
4638
}
4639
const parentSerializedSize = serializedSize;
4640
try {
4642
} finally {
4643
serializedSize = parentSerializedSize;
4644
if (__DEV__) {
4598
- debugID = prevDebugID;
4645
+ canEmitDebugInfo = prevCanEmitDebugInfo;
4646
}
4647
}
4648
}