Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [Flight] Respect displayName of Promise instances on the server (#34825) · react/react@026abea · GitHub
Skip to content

Commit 026abea

Browse files
authored
[Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party code from third party since it otherwise would have no other stack frame to indicate its name since the whole creation stack would be in third party. We already respect the `displayName` on the client but it's more complicated on the server because we don't only consider the exact instance passed to `use()` but the whole await sequence and we can pick any Promise along the way for consideration. Therefore this also adds a change where we pick the Promise node for consideration if it has a name but no stack. Where we otherwise would've picked the I/O node. Another thing that this PR does is treat anonymous stack frames (empty url) as third party for purposes of heuristics like "hasUnfilteredFrame" and the name assignment. This lets you include these in the actual generated stacks (by overriding `filterStackFrame`) but we don't actually want them to be considered first party code in the heuristics since it ends up favoring those stacks and using internals like `Function.all` in name assignment.
1 parent d7215b4 commit 026abea

2 files changed

Lines changed: 415 additions & 248 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ function findCalledFunctionNameFromStackTrace(
252252
consturl=devirtualizeURL(callsite[1]);
253253
constlineNumber=callsite[2];
254254
constcolumnNumber=callsite[3];
255-
if(filterStackFrame(url,functionName,lineNumber,columnNumber)){
255+
if(
256+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
257+
// Don't consider anonymous code first party even if the filter wants to include them in the stack.
258+
url!==''
259+
){
256260
if(bestMatch===''){
257261
// If we had no good stack frames for internal calls, just use the last
258262
// first party function name.
@@ -308,7 +312,10 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
308312
constisAsync=callsite[6];
309313
if(
310314
!isAsync&&
311-
filterStackFrame(url,functionName,lineNumber,columnNumber)
315+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
316+
// Ignore anonymous stack frames like internals. They are also not in first party
317+
// code even though it might be useful to include them in the final stack.
318+
url!==''
312319
){
313320
return true;
314321
}
@@ -367,7 +374,10 @@ export function isAwaitInUserspace(
367374
consturl=devirtualizeURL(callsite[1]);
368375
constlineNumber=callsite[2];
369376
constcolumnNumber=callsite[3];
370-
returnfilterStackFrame(url,functionName,lineNumber,columnNumber);
377+
return(
378+
filterStackFrame(url,functionName,lineNumber,columnNumber)&&
379+
url!==''
380+
);
371381
}
372382
return false;
373383
}
@@ -2347,6 +2357,7 @@ function visitAsyncNode(
23472357
}
23482358
const awaited = node.awaited;
23492359
let match: void | null | PromiseNode | IONode = previousIONode;
2360+
const promise = node.promise.deref();
23502361
if (awaited !== null) {
23512362
constioNode=visitAsyncNode(request,task,awaited,visited,cutOff);
23522363
if(ioNode===undefined){
@@ -2361,26 +2372,39 @@ function visitAsyncNode(
23612372
if(ioNode.tag===PROMISE_NODE){
23622373
// If the ioNode was a Promise, then that means we found one in user space since otherwise
23632374
// we would've returned an IO node. We assume this has the best stack.
2375+
// Note: This might also be a Promise with a displayName but potentially a worse stack.
2376+
// We could potentially favor the outer Promise if it has a stack but not the inner.
23642377
match=ioNode;
23652378
}elseif(
2366-
node.stack===null||
2367-
!hasUnfilteredFrame(request,node.stack)
2379+
(node.stack!==null&&hasUnfilteredFrame(request,node.stack))||
2380+
(promise!==undefined&&
2381+
// $FlowFixMe[prop-missing]
2382+
typeofpromise.displayName==='string'&&
2383+
(ioNode.stack===null||
2384+
!hasUnfilteredFrame(request,ioNode.stack)))
23682385
){
2386+
// If this Promise has a stack trace then we favor that over the I/O node since we're
2387+
// mainly dealing with Promises as the abstraction.
2388+
// If it has no stack but at least has a displayName and the io doesn't have a better
2389+
// stack anyway, then also use this Promise instead since at least it has a name.
2390+
match=node;
2391+
} else {
23692392
// If this Promise was created inside only third party code, then try to use
23702393
// the inner I/O node instead. This could happen if third party calls into first
23712394
// party to perform some I/O.
23722395
match=ioNode;
2373-
}else{
2374-
match =node;
23752396
}
23762397
}elseif(request.status===ABORTING){
23772398
if(node.start<request.abortTime&&node.end>request.abortTime){
23782399
// We aborted this render. If this Promise spanned the abort time it was probably the
23792400
// Promise that was aborted. This won't necessarily have I/O associated with it but
23802401
// it's a point of interest.
23812402
if(
2382-
node.stack!==null&&
2383-
hasUnfilteredFrame(request,node.stack)
2403+
(node.stack!==null&&
2404+
hasUnfilteredFrame(request,node.stack))||
2405+
(promise!==undefined&&
2406+
// $FlowFixMe[prop-missing]
2407+
typeofpromise.displayName==='string')
23842408
){
23852409
match=node;
23862410
}
@@ -2389,7 +2413,6 @@ function visitAsyncNode(
23892413
}
23902414
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
23912415
// the thing that generated this node and its virtual children.
2392-
constpromise=node.promise.deref();
23932416
if(promise!==undefined){
23942417
constdebugInfo=promise._debugInfo;
23952418
if(debugInfo!=null&&!visited.has(debugInfo)){
@@ -4497,17 +4520,33 @@ function serializeIONode(
44974520

44984521
letstack=null;
44994522
letname='';
4523+
if(ioNode.promise!==null){
4524+
// Pick an explicit name from the Promise itself if it exists.
4525+
// Note that we don't use the promiseRef passed in since that's sometimes the awaiting Promise
4526+
// which is the value observed but it's likely not the one with the name on it.
4527+
const promise =ioNode.promise.deref();
4528+
if(
4529+
promise!==undefined&&
4530+
// $FlowFixMe[prop-missing]
4531+
typeofpromise.displayName==='string'
4532+
){
4533+
name =promise.displayName;
4534+
}
4535+
}
45004536
if(ioNode.stack!==null){
45014537
// The stack can contain some leading internal frames for the construction of the promise that we skip.
45024538
const fullStack =stripLeadingPromiseCreationFrames(ioNode.stack);
45034539
stack=filterStackTrace(request,fullStack);
4504-
name=findCalledFunctionNameFromStackTrace(request,fullStack);
4505-
// The name can include the object that this was called on but sometimes that's
4506-
// just unnecessary context.
4507-
if(name.startsWith('Window.')){
4508-
name =name.slice(7);
4509-
}elseif(name.startsWith('<anonymous>.')){
4510-
name =name.slice(7);
4540+
if(name===''){
4541+
// If we didn't have an explicit name, try finding one from the stack.
4542+
name =findCalledFunctionNameFromStackTrace(request,fullStack);
4543+
// The name can include the object that this was called on but sometimes that's
4544+
// just unnecessary context.
4545+
if(name.startsWith('Window.')){
4546+
name =name.slice(7);
4547+
}elseif(name.startsWith('<anonymous>.')){
4548+
name =name.slice(7);
4549+
}
45114550
}
45124551
}
45134552
constowner=ioNode.owner;

0 commit comments

Comments
 (0)