@@ -40,24 +40,46 @@ export class Snapshotter {
40
40
private _context : BrowserContext ;
41
41
private _delegate : SnapshotterDelegate ;
42
42
private _eventListeners : RegisteredListener [ ] = [ ] ;
43
- private _interval = 0 ;
44
43
private _snapshotStreamer : string ;
45
44
private _snapshotBinding : string ;
45
+ private _initialized = false ;
46
+ private _started = false ;
47
+ private _fetchedResponses = new Map < network . Response , string > ( ) ;
46
48
47
49
constructor ( context : BrowserContext , delegate : SnapshotterDelegate ) {
48
50
this . _context = context ;
49
51
this . _delegate = delegate ;
50
- for ( const page of context . pages ( ) )
51
- this . _onPage ( page ) ;
52
- this . _eventListeners = [
53
- helper . addEventListener ( this . _context , BrowserContext . Events . Page , this . _onPage . bind ( this ) ) ,
54
- ] ;
55
52
const guid = createGuid ( ) ;
56
53
this . _snapshotStreamer = '__playwright_snapshot_streamer_' + guid ;
57
54
this . _snapshotBinding = '__playwright_snapshot_binding_' + guid ;
58
55
}
59
56
60
- async initialize ( ) {
57
+ async start ( ) {
58
+ this . _started = true ;
59
+ if ( ! this . _initialized ) {
60
+ this . _initialized = true ;
61
+ await this . _initialize ( ) ;
62
+ }
63
+ this . _runInAllFrames ( `window["${ this . _snapshotStreamer } "].reset()` ) ;
64
+
65
+ // Replay resources loaded in all pages.
66
+ for ( const page of this . _context . pages ( ) ) {
67
+ for ( const response of page . _frameManager . _responses )
68
+ this . _saveResource ( page , response ) . catch ( e => debugLogger . log ( 'error' , e ) ) ;
69
+ }
70
+ }
71
+
72
+ async stop ( ) {
73
+ this . _started = false ;
74
+ }
75
+
76
+ async _initialize ( ) {
77
+ for ( const page of this . _context . pages ( ) )
78
+ this . _onPage ( page ) ;
79
+ this . _eventListeners = [
80
+ helper . addEventListener ( this . _context , BrowserContext . Events . Page , this . _onPage . bind ( this ) ) ,
81
+ ] ;
82
+
61
83
await this . _context . exposeBinding ( this . _snapshotBinding , false , ( source , data : SnapshotData ) => {
62
84
const snapshot : FrameSnapshot = {
63
85
snapshotName : data . snapshotName ,
@@ -87,11 +109,15 @@ export class Snapshotter {
87
109
} ) ;
88
110
const initScript = `(${ frameSnapshotStreamer } )("${ this . _snapshotStreamer } ", "${ this . _snapshotBinding } ")` ;
89
111
await this . _context . _doAddInitScript ( initScript ) ;
112
+ this . _runInAllFrames ( initScript ) ;
113
+ }
114
+
115
+ private _runInAllFrames ( expression : string ) {
90
116
const frames = [ ] ;
91
117
for ( const page of this . _context . pages ( ) )
92
118
frames . push ( ...page . frames ( ) ) ;
93
119
frames . map ( frame => {
94
- frame . _existingMainContext ( ) ?. rawEvaluate ( initScript ) . catch ( debugExceptionHandler ) ;
120
+ frame . _existingMainContext ( ) ?. rawEvaluate ( expression ) . catch ( debugExceptionHandler ) ;
95
121
} ) ;
96
122
}
97
123
@@ -112,37 +138,20 @@ export class Snapshotter {
112
138
page . frames ( ) . map ( frame => snapshotFrame ( frame ) ) ;
113
139
}
114
140
115
- async setAutoSnapshotInterval ( interval : number ) : Promise < void > {
116
- this . _interval = interval ;
117
- const frames = [ ] ;
118
- for ( const page of this . _context . pages ( ) )
119
- frames . push ( ...page . frames ( ) ) ;
120
- await Promise . all ( frames . map ( frame => this . _setIntervalInFrame ( frame , interval ) ) ) ;
121
- }
122
-
123
141
private _onPage ( page : Page ) {
124
- const processNewFrame = ( frame : Frame ) => {
125
- this . _annotateFrameHierarchy ( frame ) ;
126
- this . _setIntervalInFrame ( frame , this . _interval ) ;
127
- const initScript = `(${ frameSnapshotStreamer } )("${ this . _snapshotStreamer } ", "${ this . _snapshotBinding } ")` ;
128
- frame . _existingMainContext ( ) ?. rawEvaluate ( initScript ) . catch ( debugExceptionHandler ) ;
129
- } ;
142
+ // Annotate frame hierarchy so that snapshots could include frame ids.
130
143
for ( const frame of page . frames ( ) )
131
- processNewFrame ( frame ) ;
132
- this . _eventListeners . push ( helper . addEventListener ( page , Page . Events . FrameAttached , processNewFrame ) ) ;
133
-
134
- // Push streamer interval on navigation.
135
- this . _eventListeners . push ( helper . addEventListener ( page , Page . Events . InternalFrameNavigatedToNewDocument , frame => {
136
- this . _setIntervalInFrame ( frame , this . _interval ) ;
137
- } ) ) ;
144
+ this . _annotateFrameHierarchy ( frame ) ;
145
+ this . _eventListeners . push ( helper . addEventListener ( page , Page . Events . FrameAttached , frame => this . _annotateFrameHierarchy ( frame ) ) ) ;
138
146
139
- // Capture resources.
140
147
this . _eventListeners . push ( helper . addEventListener ( page , Page . Events . Response , ( response : network . Response ) => {
141
148
this . _saveResource ( page , response ) . catch ( e => debugLogger . log ( 'error' , e ) ) ;
142
149
} ) ) ;
143
150
}
144
151
145
152
private async _saveResource ( page : Page , response : network . Response ) {
153
+ if ( ! this . _started )
154
+ return ;
146
155
const isRedirect = response . status ( ) >= 300 && response . status ( ) <= 399 ;
147
156
if ( isRedirect )
148
157
return ;
@@ -163,9 +172,25 @@ export class Snapshotter {
163
172
const status = response . status ( ) ;
164
173
const requestBody = original . postDataBuffer ( ) ;
165
174
const requestSha1 = requestBody ? calculateSha1 ( requestBody ) : '' ;
175
+ if ( requestBody )
176
+ this . _delegate . onBlob ( { sha1 : requestSha1 , buffer : requestBody } ) ;
166
177
const requestHeaders = original . headers ( ) ;
167
- const body = await response . body ( ) . catch ( e => debugLogger . log ( 'error' , e ) ) ;
168
- const responseSha1 = body ? calculateSha1 ( body ) : '' ;
178
+
179
+ // Only fetch response bodies once.
180
+ let responseSha1 = this . _fetchedResponses . get ( response ) ;
181
+ {
182
+ if ( responseSha1 === undefined ) {
183
+ const body = await response . body ( ) . catch ( e => debugLogger . log ( 'error' , e ) ) ;
184
+ // Bail out after each async hop.
185
+ if ( ! this . _started )
186
+ return ;
187
+ responseSha1 = body ? calculateSha1 ( body ) : '' ;
188
+ if ( body )
189
+ this . _delegate . onBlob ( { sha1 : responseSha1 , buffer : body } ) ;
190
+ this . _fetchedResponses . set ( response , responseSha1 ) ;
191
+ }
192
+ }
193
+
169
194
const resource : ResourceSnapshot = {
170
195
pageId : page . guid ,
171
196
frameId : response . frame ( ) . guid ,
@@ -181,17 +206,6 @@ export class Snapshotter {
181
206
timestamp : monotonicTime ( )
182
207
} ;
183
208
this . _delegate . onResourceSnapshot ( resource ) ;
184
- if ( requestBody )
185
- this . _delegate . onBlob ( { sha1 : requestSha1 , buffer : requestBody } ) ;
186
- if ( body )
187
- this . _delegate . onBlob ( { sha1 : responseSha1 , buffer : body } ) ;
188
- }
189
-
190
- private async _setIntervalInFrame ( frame : Frame , interval : number ) {
191
- const context = frame . _existingMainContext ( ) ;
192
- await context ?. evaluate ( ( { snapshotStreamer, interval } ) => {
193
- ( window as any ) [ snapshotStreamer ] . setSnapshotInterval ( interval ) ;
194
- } , { snapshotStreamer : this . _snapshotStreamer , interval } ) . catch ( debugExceptionHandler ) ;
195
209
}
196
210
197
211
private async _annotateFrameHierarchy ( frame : Frame ) {
0 commit comments