@@ -522,9 +522,10 @@ function extractLocationFromStack(
522522 }
523523 if ( frame . uiSourceCode ) {
524524 const uiLocation = frame . uiSourceCode . uiLocation ( frame . line , frame . column ) ;
525+ const url = uiLocation . uiSourceCode . url ( ) ;
525526 return {
526- url : uiLocation . uiSourceCode . url ( ) ,
527- displayName : uiLocation . uiSourceCode . displayName ( ) ,
527+ url,
528+ displayName : shortUrl ( url ) || uiLocation . uiSourceCode . displayName ( ) ,
528529 // UILocation stores 0-based line/column indices.
529530 lineNumber : uiLocation . lineNumber + 1 ,
530531 columnNumber : ( uiLocation . columnNumber ?? 0 ) + 1 ,
@@ -593,7 +594,7 @@ async function withResolveTimeout<T>(
593594 promise : Promise < T > ,
594595 fetchDetailedData : boolean | undefined ,
595596) : Promise < T | undefined > {
596- const timeoutMs = fetchDetailedData ? 10_000 : 10_000 ;
597+ const timeoutMs = fetchDetailedData ? 10_000 : 5_000 ;
597598 let timeoutId : ReturnType < typeof setTimeout > | undefined ;
598599 const timeoutPromise = new Promise < undefined > ( resolve => {
599600 timeoutId = setTimeout ( ( ) => resolve ( undefined ) , timeoutMs ) ;
@@ -610,21 +611,53 @@ async function withResolveTimeout<T>(
610611/**
611612 * Extracts a short, human-readable name from a URL by returning the last path
612613 * segment. Used as a fallback when no source-mapped UI location is available.
614+ *
615+ * Handles a few special cases that come up in practice:
616+ * - puppeteer-injected scripts use a URL of the form
617+ * `pptr:evaluateHandle;performEvaluation (file:///path/to/file.js:104:34)`
618+ * or the URL-encoded variant
619+ * `pptr:evaluate;file%3A%2F%2F%2Fpath%2Fto%2Ffile.js%3A104%3A18`,
620+ * from which we extract the inner file's basename.
621+ * - URLs that already have a trailing `:line:column` suffix are stripped
622+ * before extracting the basename.
613623 */
614624function shortUrl ( url : string ) : string {
615625 if ( ! url ) {
616626 return '' ;
617627 }
628+ // Decode percent-encoded characters so embedded paths are extractable.
629+ let decoded = url ;
618630 try {
619- const parsed = new URL ( url ) ;
620- const segments = parsed . pathname . split ( '/' ) . filter ( s => s . length > 0 ) ;
621- if ( segments . length > 0 ) {
622- return segments [ segments . length - 1 ] ;
631+ decoded = decodeURIComponent ( url ) ;
632+ } catch {
633+ // Leave url as-is when it is not validly percent-encoded.
634+ }
635+ // Recursively unwrap parenthesised inner URLs (puppeteer-style).
636+ const parenMatch = decoded . match ( / \( ( [ ^ ( ) ] + ) \) / ) ;
637+ if ( parenMatch ) {
638+ const inner = shortUrl ( parenMatch [ 1 ] ) ;
639+ if ( inner ) {
640+ return inner ;
641+ }
642+ }
643+ // Strip trailing :line:col or :line so URL parsing succeeds.
644+ const cleaned = decoded . replace ( / : \d + (?: : \d + ) ? $ / , '' ) ;
645+ try {
646+ const parsed = new URL ( cleaned ) ;
647+ const pathSegments = parsed . pathname . split ( '/' ) . filter ( s => s . length > 0 ) ;
648+ if ( pathSegments . length > 0 ) {
649+ return pathSegments [ pathSegments . length - 1 ] ;
650+ }
651+ // Some custom schemes (e.g. `pptr:`) leave the path in the opaque part.
652+ // Fall back to the last `/`-separated segment of the trimmed URL.
653+ const lastSlash = cleaned . lastIndexOf ( '/' ) ;
654+ if ( lastSlash >= 0 && lastSlash < cleaned . length - 1 ) {
655+ return cleaned . slice ( lastSlash + 1 ) ;
623656 }
624- return parsed . host || url ;
657+ return parsed . host || cleaned ;
625658 } catch {
626659 // Not a valid URL (e.g. VM-backed inline scripts).
627- const lastSlash = url . lastIndexOf ( '/' ) ;
628- return lastSlash >= 0 ? url . slice ( lastSlash + 1 ) : url ;
660+ const lastSlash = cleaned . lastIndexOf ( '/' ) ;
661+ return lastSlash >= 0 ? cleaned . slice ( lastSlash + 1 ) : cleaned ;
629662 }
630663}
0 commit comments