Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
const url = devirtualizeURL(callsite[1]);
const lineNumber = callsite[2];
const columnNumber = callsite[3];
if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {
if (
filterStackFrame(url, functionName, lineNumber, columnNumber) &&
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
url !== ''
) {
if (bestMatch === '') {
// If we had no good stack frames for internal calls, just use the last
// first party function name.
Expand Down Expand Up @@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
const isAsync = callsite[6];
if (
!isAsync &&
filterStackFrame(url, functionName, lineNumber, columnNumber)
filterStackFrame(url, functionName, lineNumber, columnNumber) &&
// Ignore anonymous stack frames like internals. They are also not in first party
// code even though it might be useful to include them in the final stack.
url !== ''
) {
return true;
}
Expand Down Expand Up @@ -367,7 +374,10 @@ export function isAwaitInUserspace(
const url = devirtualizeURL(callsite[1]);
const lineNumber = callsite[2];
const columnNumber = callsite[3];
return filterStackFrame(url, functionName, lineNumber, columnNumber);
return (
filterStackFrame(url, functionName, lineNumber, columnNumber) &&
url !== ''
);
}
return false;
}
Expand Down Expand Up @@ -2347,6 +2357,7 @@ function visitAsyncNode(
}
const awaited = node.awaited;
let match: void | null | PromiseNode | IONode = previousIONode;
const promise = node.promise.deref();
if (awaited !== null) {
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
if (ioNode === undefined) {
Expand All @@ -2361,26 +2372,39 @@ function visitAsyncNode(
if (ioNode.tag === PROMISE_NODE) {
// If the ioNode was a Promise, then that means we found one in user space since otherwise
// we would've returned an IO node. We assume this has the best stack.
// Note: This might also be a Promise with a displayName but potentially a worse stack.
// We could potentially favor the outer Promise if it has a stack but not the inner.
match = ioNode;
} else if (
node.stack === null ||
!hasUnfilteredFrame(request, node.stack)
(node.stack !== null && hasUnfilteredFrame(request, node.stack)) ||
(promise !== undefined &&
// $FlowFixMe[prop-missing]
typeof promise.displayName === 'string' &&
(ioNode.stack === null ||
!hasUnfilteredFrame(request, ioNode.stack)))
) {
// If this Promise has a stack trace then we favor that over the I/O node since we're
// mainly dealing with Promises as the abstraction.
// If it has no stack but at least has a displayName and the io doesn't have a better
// stack anyway, then also use this Promise instead since at least it has a name.
match = node;
} else {
// If this Promise was created inside only third party code, then try to use
// the inner I/O node instead. This could happen if third party calls into first
// party to perform some I/O.
match = ioNode;
} else {
match = node;
}
} else if (request.status === ABORTING) {
if (node.start < request.abortTime && node.end > request.abortTime) {
// We aborted this render. If this Promise spanned the abort time it was probably the
// Promise that was aborted. This won't necessarily have I/O associated with it but
// it's a point of interest.
if (
node.stack !== null &&
hasUnfilteredFrame(request, node.stack)
(node.stack !== null &&
hasUnfilteredFrame(request, node.stack)) ||
(promise !== undefined &&
// $FlowFixMe[prop-missing]
typeof promise.displayName === 'string')
) {
match = node;
}
Expand All @@ -2389,7 +2413,6 @@ function visitAsyncNode(
}
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
// the thing that generated this node and its virtual children.
const promise = node.promise.deref();
if (promise !== undefined) {
const debugInfo = promise._debugInfo;
if (debugInfo != null && !visited.has(debugInfo)) {
Expand Down Expand Up @@ -4497,17 +4520,33 @@ function serializeIONode(

let stack = null;
let name = '';
if (ioNode.promise !== null) {
// Pick an explicit name from the Promise itself if it exists.
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
// which is the value observed but it's likely not the one with the name on it.
const promise = ioNode.promise.deref();
if (
promise !== undefined &&
// $FlowFixMe[prop-missing]
typeof promise.displayName === 'string'
) {
name = promise.displayName;
}
}
if (ioNode.stack !== null) {
// The stack can contain some leading internal frames for the construction of the promise that we skip.
const fullStack = stripLeadingPromiseCreationFrames(ioNode.stack);
stack = filterStackTrace(request, fullStack);
name = findCalledFunctionNameFromStackTrace(request, fullStack);
// The name can include the object that this was called on but sometimes that's
// just unnecessary context.
if (name.startsWith('Window.')) {
name = name.slice(7);
} else if (name.startsWith('<anonymous>.')) {
name = name.slice(7);
if (name === '') {
// If we didn't have an explicit name, try finding one from the stack.
name = findCalledFunctionNameFromStackTrace(request, fullStack);
// The name can include the object that this was called on but sometimes that's
// just unnecessary context.
if (name.startsWith('Window.')) {
name = name.slice(7);
} else if (name.startsWith('<anonymous>.')) {
name = name.slice(7);
}
}
}
const owner = ioNode.owner;
Expand Down
Loading
Loading