@@ -1400,7 +1400,10 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
14001400 traceAsString = cpuprofileJsonGenerator ( profile ) ;
14011401 }
14021402 } else {
1403- const formattedTraceIter = traceJsonGenerator ( traceEvents , metadata ) ;
1403+ const formattedTraceIter = traceJsonGenerator ( traceEvents , {
1404+ ...metadata ,
1405+ sourceMaps : isEnhancedTraces ? metadata ?. sourceMaps : undefined ,
1406+ } ) ;
14041407 traceAsString = Array . from ( formattedTraceIter ) . join ( '' ) ;
14051408 }
14061409 if ( ! traceAsString ) {
@@ -2416,7 +2419,59 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
24162419 } ) ;
24172420 }
24182421
2419- #createSourceMapResolver( isFreshRecording : boolean ) : Trace . TraceModel . ParseConfig [ 'resolveSourceMap' ] {
2422+ /**
2423+ * Store source maps on trace metadata (but just the non-data url ones).
2424+ *
2425+ * Many raw source maps are already in memory, but there are some cases where they may
2426+ * not be and have to be fetched here:
2427+ *
2428+ * 1. If the trace processor (via `#createSourceMapResolver`) never fetched it,
2429+ * due to `ScriptHandler` skipping the script if it could not find an associated frame.
2430+ * 2. If the initial fetch failed (perhaps the failure was intermittent and a
2431+ * subsequent attempt will work).
2432+ */
2433+ async #retainSourceMapsForEnhancedTrace(
2434+ parsedTrace : Trace . Handlers . Types . ParsedTrace , metadata : Trace . Types . File . MetaData ) : Promise < void > {
2435+ const handleScript = async ( script : Trace . Handlers . ModelHandlers . Scripts . Script ) : Promise < void > => {
2436+ if ( ! script . sourceMapUrl || script . sourceMapUrl . startsWith ( 'data:' ) ) {
2437+ return ;
2438+ }
2439+
2440+ if ( metadata . sourceMaps ?. find ( m => m . sourceMapUrl === script . sourceMapUrl ) ) {
2441+ return ;
2442+ }
2443+
2444+ // TimelineController sets `SDK.SourceMap.SourceMap.retainRawSourceMaps` to true,
2445+ // which means the raw source map is present (assuming `script.sourceMap` is too).
2446+ let rawSourceMap = script . sourceMap ?. json ( ) ;
2447+
2448+ // If the raw map is not present for some reason, fetch it again.
2449+ if ( ! rawSourceMap ) {
2450+ const initiator = {
2451+ target : null ,
2452+ frameId : script . frame as Protocol . Page . FrameId ,
2453+ initiatorUrl : script . url as Platform . DevToolsPath . UrlString
2454+ } ;
2455+ rawSourceMap = await SDK . SourceMapManager . tryLoadSourceMap (
2456+ script . sourceMapUrl as Platform . DevToolsPath . UrlString , initiator ) ;
2457+ }
2458+
2459+ if ( script . url && rawSourceMap ) {
2460+ metadata . sourceMaps ?. push ( { url : script . url , sourceMapUrl : script . sourceMapUrl , sourceMap : rawSourceMap } ) ;
2461+ }
2462+ } ;
2463+
2464+ metadata . sourceMaps = [ ] ;
2465+
2466+ const promises = [ ] ;
2467+ for ( const script of parsedTrace ?. Scripts . scripts . values ( ) ?? [ ] ) {
2468+ promises . push ( handleScript ( script ) ) ;
2469+ }
2470+ await Promise . all ( promises ) ;
2471+ }
2472+
2473+ #createSourceMapResolver( isFreshRecording : boolean , metadata : Trace . Types . File . MetaData | null ) :
2474+ Trace . TraceModel . ParseConfig [ 'resolveSourceMap' ] {
24202475 // Currently, only experimental insights need source maps.
24212476 if ( ! Root . Runtime . experiments . isEnabled ( Root . Runtime . ExperimentName . TIMELINE_EXPERIMENTAL_INSIGHTS ) ) {
24222477 return ;
@@ -2462,38 +2517,63 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
24622517 }
24632518 }
24642519
2465- // Else... fetch it!
2466- if ( ! scriptUrl ) {
2467- return null ;
2520+ // If loading from disk, check the metadata for source maps.
2521+ // The metadata doesn't store data url source maps.
2522+ const isDataUrl = sourceMapUrl . startsWith ( 'data:' ) ;
2523+ if ( ! isFreshRecording && metadata ?. sourceMaps && ! isDataUrl ) {
2524+ const cachedSourceMap = metadata . sourceMaps . find ( m => m . sourceMapUrl === sourceMapUrl ) ;
2525+ if ( cachedSourceMap ) {
2526+ return new SDK . SourceMap . SourceMap ( scriptUrl , sourceMapUrl , cachedSourceMap . sourceMap ) ;
2527+ }
24682528 }
24692529
2470- // In all other cases, we must fetch the source map.
2471- // For example, since the debugger model is disable during recording, any non-final navigations during
2472- // the trace will never have their source maps fetched by the debugger model. That's only ever done here.
2530+ // Never fetch source maps if the trace is not fresh - the source maps may not
2531+ // reflect what was actually loaded by the page for this trace on disk.
2532+ if ( ! isFreshRecording && ! isDataUrl ) {
2533+ return null ;
2534+ }
24732535
2474- try {
2475- const initiator = { target : null , frameId : frame , initiatorUrl : scriptUrl } ;
2476- const payload = await SDK . SourceMapManager . loadSourceMap ( sourceMapUrl , initiator ) ;
2477- return new SDK . SourceMap . SourceMap ( scriptUrl , sourceMapUrl , payload ) ;
2478- } catch ( cause ) {
2479- console . error ( `Could not load content for ${ sourceMapUrl } : ${ cause . message } ` , { cause} ) ;
2536+ if ( ! scriptUrl ) {
2537+ return null ;
24802538 }
24812539
2482- return null ;
2540+ // In all other cases, fetch the source map.
2541+ //
2542+ // 1) data urls
2543+ // 2) fresh recording + source map not for active frame
2544+ //
2545+ // For example, since the debugger model is disable during recording, any
2546+ // non-final navigations during the trace will never have their source maps
2547+ // fetched by the debugger model. That's only ever done here.
2548+
2549+ const initiator = { target : null , frameId : frame , initiatorUrl : scriptUrl } ;
2550+ const payload = await SDK . SourceMapManager . tryLoadSourceMap ( sourceMapUrl , initiator ) ;
2551+ return payload ? new SDK . SourceMap . SourceMap ( scriptUrl , sourceMapUrl , payload ) : null ;
24832552 } ;
24842553 }
24852554
24862555 async #executeNewTrace(
24872556 collectedEvents : Trace . Types . Events . Event [ ] , isFreshRecording : boolean ,
24882557 metadata : Trace . Types . File . MetaData | null ) : Promise < void > {
2489- return await this . #traceEngineModel. parse (
2558+ await this . #traceEngineModel. parse (
24902559 collectedEvents ,
24912560 {
24922561 metadata : metadata ?? undefined ,
24932562 isFreshRecording,
2494- resolveSourceMap : this . #createSourceMapResolver( isFreshRecording ) ,
2563+ resolveSourceMap : this . #createSourceMapResolver( isFreshRecording , metadata ) ,
24952564 } ,
24962565 ) ;
2566+
2567+ // Store all source maps on the trace metadata.
2568+ // If not fresh, we can't validate the maps are still accurate.
2569+ if ( isFreshRecording && metadata &&
2570+ Root . Runtime . experiments . isEnabled ( Root . Runtime . ExperimentName . TIMELINE_ENHANCED_TRACES ) ) {
2571+ const traceIndex = this . #traceEngineModel. lastTraceIndex ( ) ;
2572+ const parsedTrace = this . #traceEngineModel. parsedTrace ( traceIndex ) ;
2573+ if ( parsedTrace ) {
2574+ await this . #retainSourceMapsForEnhancedTrace( parsedTrace , metadata ) ;
2575+ }
2576+ }
24972577 }
24982578
24992579 loadingCompleteForTest ( ) : void {
0 commit comments