@@ -32,7 +32,7 @@ import { BufferedOutput, FileOutput, OutputMultiplexer, RecorderOutput } from '.
32
32
import { RecorderApp } from './recorder/recorderApp' ;
33
33
import { CallMetadata , internalCallMetadata , SdkObject } from '../instrumentation' ;
34
34
import { Point } from '../../common/types' ;
35
- import { EventData , Mode , PauseDetails , UIState } from './recorder/recorderTypes' ;
35
+ import { CallLog , EventData , Mode , Source , UIState } from './recorder/recorderTypes' ;
36
36
37
37
type BindingSource = { frame : Frame , page : Page } ;
38
38
@@ -45,18 +45,17 @@ export class RecorderSupplement {
45
45
private _lastDialogOrdinal = 0 ;
46
46
private _timers = new Set < NodeJS . Timeout > ( ) ;
47
47
private _context : BrowserContext ;
48
- private _resumeCallback : ( ( ) => void ) | null = null ;
49
48
private _mode : Mode ;
50
- private _pauseDetails : PauseDetails | null = null ;
51
49
private _output : OutputMultiplexer ;
52
50
private _bufferedOutput : BufferedOutput ;
53
51
private _recorderApp : RecorderApp | null = null ;
54
- private _highlighterType : string ;
55
52
private _params : channels . BrowserContextRecorderSupplementEnableParams ;
56
- private _callMetadata : CallMetadata | null = null ;
53
+ private _currentCallsMetadata = new Set < CallMetadata > ( ) ;
54
+ private _pausedCallsMetadata = new Map < CallMetadata , ( ) => void > ( ) ;
57
55
private _pauseOnNextStatement = true ;
58
- private _sourceCache = new Map < string , string > ( ) ;
59
56
private _sdkObject : SdkObject | null = null ;
57
+ private _recorderSource : Source ;
58
+ private _userSources = new Map < string , Source > ( ) ;
60
59
61
60
static getOrCreate ( context : BrowserContext , params : channels . BrowserContextRecorderSupplementEnableParams = { } ) : Promise < RecorderSupplement > {
62
61
let recorderPromise = ( context as any ) [ symbol ] as Promise < RecorderSupplement > ;
@@ -73,22 +72,22 @@ export class RecorderSupplement {
73
72
this . _params = params ;
74
73
this . _mode = params . startRecording ? 'recording' : 'none' ;
75
74
let languageGenerator : LanguageGenerator ;
76
- const language = params . language || context . _options . sdkLanguage ;
75
+ let language = params . language || context . _options . sdkLanguage ;
77
76
switch ( language ) {
78
77
case 'javascript' : languageGenerator = new JavaScriptLanguageGenerator ( ) ; break ;
79
78
case 'csharp' : languageGenerator = new CSharpLanguageGenerator ( ) ; break ;
80
79
case 'python' :
81
80
case 'python-async' : languageGenerator = new PythonLanguageGenerator ( params . language === 'python-async' ) ; break ;
82
81
default : throw new Error ( `Invalid target: '${ params . language } '` ) ;
83
82
}
84
- let highlighterType = language ;
85
- if ( highlighterType === 'python-async' )
86
- highlighterType = 'python' ;
83
+ if ( language === 'python-async' )
84
+ language = 'python' ;
87
85
88
- this . _highlighterType = highlighterType ;
86
+ this . _recorderSource = { file : '<recorder>' , text : '' , language , highlight : [ ] } ;
89
87
this . _bufferedOutput = new BufferedOutput ( async text => {
90
- if ( this . _recorderApp )
91
- this . _recorderApp . setSource ( text , highlighterType ) ;
88
+ this . _recorderSource . text = text ;
89
+ this . _recorderSource . revealLine = text . split ( '\n' ) . length - 1 ;
90
+ this . _pushAllSources ( ) ;
92
91
} ) ;
93
92
const outputs : RecorderOutput [ ] = [ this . _bufferedOutput ] ;
94
93
if ( params . outputFile )
@@ -136,8 +135,8 @@ export class RecorderSupplement {
136
135
137
136
await Promise . all ( [
138
137
recorderApp . setMode ( this . _mode ) ,
139
- recorderApp . setPaused ( this . _pauseDetails ) ,
140
- recorderApp . setSource ( this . _bufferedOutput . buffer ( ) , this . _highlighterType )
138
+ recorderApp . setPaused ( ! ! this . _pausedCallsMetadata . size ) ,
139
+ this . _pushAllSources ( )
141
140
] ) ;
142
141
143
142
this . _context . on ( BrowserContext . Events . Page , page => this . _onPage ( page ) ) ;
@@ -168,8 +167,11 @@ export class RecorderSupplement {
168
167
let actionPoint : Point | undefined = undefined ;
169
168
let actionSelector : string | undefined = undefined ;
170
169
if ( source . page === this . _sdkObject ?. attribution ?. page ) {
171
- actionPoint = this . _callMetadata ?. point ;
172
- actionSelector = this . _callMetadata ?. params . selector ;
170
+ if ( this . _currentCallsMetadata . size ) {
171
+ const metadata = this . _currentCallsMetadata . values ( ) . next ( ) . value ;
172
+ actionPoint = metadata . values ( ) . next ( ) . value ;
173
+ actionSelector = metadata . params . selector ;
174
+ }
173
175
}
174
176
const uiState : UIState = { mode : this . _mode , actionPoint, actionSelector } ;
175
177
return uiState ;
@@ -185,19 +187,26 @@ export class RecorderSupplement {
185
187
( this . _context as any ) . recorderAppForTest = recorderApp ;
186
188
}
187
189
188
- async pause ( ) {
189
- this . _pauseDetails = { message : 'paused' } ;
190
- this . _recorderApp ! . setPaused ( this . _pauseDetails ) ;
191
- return new Promise < void > ( f => this . _resumeCallback = f ) ;
190
+ async pause ( metadata : CallMetadata ) {
191
+ const result = new Promise < void > ( f => {
192
+ this . _pausedCallsMetadata . set ( metadata , f ) ;
193
+ } ) ;
194
+ this . _recorderApp ! . setPaused ( true ) ;
195
+ this . _updateUserSources ( ) ;
196
+ this . updateCallLog ( [ metadata ] ) ;
197
+ return result ;
192
198
}
193
199
194
200
private async _resume ( step : boolean ) {
195
201
this . _pauseOnNextStatement = step ;
196
- if ( this . _resumeCallback )
197
- this . _resumeCallback ( ) ;
198
- this . _resumeCallback = null ;
199
- this . _pauseDetails = null ;
200
- this . _recorderApp ?. setPaused ( null ) ;
202
+
203
+ for ( const callback of this . _pausedCallsMetadata . values ( ) )
204
+ callback ( ) ;
205
+ this . _pausedCallsMetadata . clear ( ) ;
206
+
207
+ this . _recorderApp ?. setPaused ( false ) ;
208
+ this . _updateUserSources ( ) ;
209
+ this . updateCallLog ( [ ...this . _currentCallsMetadata ] ) ;
201
210
}
202
211
203
212
private async _onPage ( page : Page ) {
@@ -318,47 +327,90 @@ export class RecorderSupplement {
318
327
319
328
async onBeforeCall ( sdkObject : SdkObject , metadata : CallMetadata ) : Promise < void > {
320
329
this . _sdkObject = sdkObject ;
321
- this . _callMetadata = metadata ;
322
- const { source , line } = this . _source ( metadata ) ;
323
- this . _recorderApp ?. setSource ( source , 'javascript' , line ) ;
330
+ this . _currentCallsMetadata . add ( metadata ) ;
331
+ this . _updateUserSources ( ) ;
332
+ this . updateCallLog ( [ metadata ] ) ;
324
333
if ( metadata . method === 'pause' || ( this . _pauseOnNextStatement && metadata . method === 'goto' ) )
325
- await this . pause ( ) ;
334
+ await this . pause ( metadata ) ;
326
335
}
327
336
328
- async onAfterCall ( sdkObject : SdkObject , metadata : CallMetadata ) : Promise < void > {
337
+ async onAfterCall ( metadata : CallMetadata ) : Promise < void > {
329
338
this . _sdkObject = null ;
330
- this . _callMetadata = null ;
339
+ this . _currentCallsMetadata . delete ( metadata ) ;
340
+ this . _pausedCallsMetadata . delete ( metadata ) ;
341
+ this . _updateUserSources ( ) ;
342
+ this . updateCallLog ( [ metadata ] ) ;
331
343
}
332
344
333
- async onBeforeInputAction ( sdkObject : SdkObject , metadata : CallMetadata ) : Promise < void > {
345
+ private _updateUserSources ( ) {
346
+ // Remove old decorations.
347
+ for ( const source of this . _userSources . values ( ) ) {
348
+ source . highlight = [ ] ;
349
+ source . revealLine = undefined ;
350
+ }
351
+
352
+ // Apply new decorations.
353
+ for ( const metadata of this . _currentCallsMetadata ) {
354
+ if ( ! metadata . stack || ! metadata . stack [ 0 ] )
355
+ continue ;
356
+ const { file, line } = metadata . stack [ 0 ] ;
357
+ let source = this . _userSources . get ( file ) ;
358
+ if ( ! source ) {
359
+ source = { file, text : this . _readSource ( file ) , highlight : [ ] , language : languageForFile ( file ) } ;
360
+ this . _userSources . set ( file , source ) ;
361
+ }
362
+ if ( line ) {
363
+ const paused = this . _pausedCallsMetadata . has ( metadata ) ;
364
+ source . highlight . push ( { line, type : paused ? 'paused' : 'running' } ) ;
365
+ if ( paused )
366
+ source . revealLine = line ;
367
+ }
368
+ }
369
+ this . _pushAllSources ( ) ;
370
+ }
371
+
372
+ private _pushAllSources ( ) {
373
+ this . _recorderApp ?. setSources ( [ this . _recorderSource , ...this . _userSources . values ( ) ] ) ;
374
+ }
375
+
376
+ async onBeforeInputAction ( metadata : CallMetadata ) : Promise < void > {
334
377
if ( this . _pauseOnNextStatement )
335
- await this . pause ( ) ;
378
+ await this . pause ( metadata ) ;
336
379
}
337
380
338
- private _source ( metadata : CallMetadata ) : { source : string , line : number | undefined } {
339
- let source = '// No source available' ;
340
- let line : number | undefined = undefined ;
341
- if ( metadata . stack && metadata . stack . length ) {
342
- try {
343
- source = this . _readAndCacheSource ( metadata . stack [ 0 ] . file ) ;
344
- line = metadata . stack [ 0 ] . line ? metadata . stack [ 0 ] . line - 1 : undefined ;
345
- } catch ( e ) {
346
- source = metadata . stack . join ( '\n' ) ;
347
- }
381
+ async updateCallLog ( metadatas : CallMetadata [ ] ) : Promise < void > {
382
+ const logs : CallLog [ ] = [ ] ;
383
+ for ( const metadata of metadatas ) {
384
+ if ( ! metadata . method )
385
+ continue ;
386
+ const title = metadata . stack ?. [ 0 ] ?. function || metadata . method ;
387
+ let status : 'done' | 'in-progress' | 'paused' | 'error' = 'done' ;
388
+ if ( this . _currentCallsMetadata . has ( metadata ) )
389
+ status = 'in-progress' ;
390
+ if ( this . _pausedCallsMetadata . has ( metadata ) )
391
+ status = 'paused' ;
392
+ if ( metadata . error )
393
+ status = 'error' ;
394
+ logs . push ( { id : metadata . id , messages : metadata . log , title, status } ) ;
348
395
}
349
- return { source , line } ;
396
+ this . _recorderApp ?. updateCallLogs ( logs ) ;
350
397
}
351
398
352
- private _readAndCacheSource ( fileName : string ) : string {
353
- let source = this . _sourceCache . get ( fileName ) ;
354
- if ( source )
355
- return source ;
399
+ private _readSource ( fileName : string ) : string {
356
400
try {
357
- source = fs . readFileSync ( fileName , 'utf-8' ) ;
401
+ return fs . readFileSync ( fileName , 'utf-8' ) ;
358
402
} catch ( e ) {
359
- source = '// No source available' ;
403
+ return '// No source available' ;
360
404
}
361
- this . _sourceCache . set ( fileName , source ) ;
362
- return source ;
363
405
}
364
406
}
407
+
408
+ function languageForFile ( file : string ) {
409
+ if ( file . endsWith ( '.py' ) )
410
+ return 'python' ;
411
+ if ( file . endsWith ( '.java' ) )
412
+ return 'java' ;
413
+ if ( file . endsWith ( '.cs' ) )
414
+ return 'csharp' ;
415
+ return 'javascript' ;
416
+ }
0 commit comments