@@ -16,6 +16,7 @@ import type {
16
16
attributes ,
17
17
mediaAttributes ,
18
18
DataURLOptions ,
19
+ listenerHandler ,
19
20
} from '@rrweb/types' ;
20
21
import {
21
22
Mirror ,
@@ -312,6 +313,7 @@ function onceIframeLoaded(
312
313
iframeEl : HTMLIFrameElement ,
313
314
listener : ( ) => unknown ,
314
315
iframeLoadTimeout : number ,
316
+ signal : AbortSignal ,
315
317
) {
316
318
const win = iframeEl . contentWindow ;
317
319
if ( ! win ) {
@@ -326,41 +328,64 @@ function onceIframeLoaded(
326
328
} catch ( error ) {
327
329
return ;
328
330
}
331
+
332
+ if ( signal . aborted ) return ;
333
+
334
+ const handlers : listenerHandler [ ] = [ ] ;
335
+ const removeEventListener = ( ) => handlers . forEach ( ( h ) => h ( ) )
336
+ handlers . push ( ( ) => signal . removeEventListener ( 'abort' , removeEventListener ) ) ;
337
+ signal . addEventListener ( 'abort' , removeEventListener ) ;
338
+
329
339
if ( readyState !== 'complete' ) {
330
340
const timer = setTimeout ( ( ) => {
341
+ removeEventListener ( ) ;
331
342
if ( ! fired ) {
332
343
listener ( ) ;
333
344
fired = true ;
334
345
}
335
346
} , iframeLoadTimeout ) ;
336
- iframeEl . addEventListener ( 'load' , ( ) => {
337
- clearTimeout ( timer ) ;
347
+ handlers . push ( ( ) => clearTimeout ( timer ) ) ;
348
+
349
+ const onIframeLoaded = ( ) => {
350
+ removeEventListener ( ) ;
338
351
fired = true ;
339
352
listener ( ) ;
340
- } ) ;
353
+ } ;
354
+ handlers . push ( ( ) => iframeEl . removeEventListener ( 'load' , onIframeLoaded ) ) ;
355
+
356
+ iframeEl . addEventListener ( 'load' , onIframeLoaded ) ;
341
357
return ;
342
358
}
343
359
// check blank frame for Chrome
344
360
const blankUrl = 'about:blank' ;
361
+ const onIframeLoaded = ( ) => {
362
+ removeEventListener ( ) ;
363
+ listener ( ) ;
364
+ } ;
345
365
if (
346
366
win . location . href !== blankUrl ||
347
367
iframeEl . src === blankUrl ||
348
368
iframeEl . src === ''
349
369
) {
350
370
// iframe was already loaded, make sure we wait to trigger the listener
351
371
// till _after_ the mutation that found this iframe has had time to process
352
- setTimeout ( listener , 0 ) ;
372
+ const timer = setTimeout ( ( ) => {
373
+ removeEventListener ( ) ;
374
+ listener ( ) ;
375
+ } , 0 ) ;
376
+ handlers . push ( ( ) => clearTimeout ( timer ) ) ;
353
377
354
- return iframeEl . addEventListener ( 'load' , listener ) ; // keep listing for future loads
378
+ return iframeEl . addEventListener ( 'load' , onIframeLoaded ) ; // keep listing for future loads
355
379
}
356
380
// use default listener
357
- iframeEl . addEventListener ( 'load' , listener ) ;
381
+ iframeEl . addEventListener ( 'load' , onIframeLoaded ) ;
358
382
}
359
383
360
384
function onceStylesheetLoaded (
361
385
link : HTMLLinkElement ,
362
386
listener : ( ) => unknown ,
363
387
styleSheetLoadTimeout : number ,
388
+ signal : AbortSignal ,
364
389
) {
365
390
let fired = false ;
366
391
let styleSheetLoaded : StyleSheet | null ;
@@ -371,23 +396,30 @@ function onceStylesheetLoaded(
371
396
}
372
397
373
398
if ( styleSheetLoaded ) return ;
399
+ if ( signal . aborted ) return ;
374
400
375
- const onStylesheetLoaded = ( ) => {
376
- link . removeEventListener ( 'load' , onStylesheetLoaded ) ;
377
- clearTimeout ( timer ) ;
378
- fired = true ;
379
- listener ( ) ;
380
- } ;
401
+ const handlers : listenerHandler [ ] = [ ] ;
402
+ const removeEventListener = ( ) => handlers . forEach ( ( h ) => h ( ) )
403
+ handlers . push ( ( ) => signal . removeEventListener ( 'abort' , removeEventListener ) ) ;
404
+ signal . addEventListener ( 'abort' , removeEventListener ) ;
381
405
382
406
const timer = setTimeout ( ( ) => {
383
- link . removeEventListener ( 'load' , onStylesheetLoaded ) ;
407
+ removeEventListener ( ) ;
384
408
if ( ! fired ) {
385
409
listener ( ) ;
386
410
fired = true ;
387
411
}
388
412
} , styleSheetLoadTimeout ) ;
413
+ handlers . push ( ( ) => clearTimeout ( timer ) ) ;
414
+
415
+ const onStylesheetLoaded = ( ) => {
416
+ removeEventListener ( ) ;
417
+ fired = true ;
418
+ listener ( ) ;
419
+ } ;
389
420
390
421
link . addEventListener ( 'load' , onStylesheetLoaded ) ;
422
+ handlers . push ( ( ) => link . removeEventListener ( 'load' , onStylesheetLoaded ) ) ;
391
423
}
392
424
393
425
function serializeNode (
@@ -911,6 +943,7 @@ export function serializeNodeWithId(
911
943
maskTextSelector : string | null ;
912
944
skipChild : boolean ;
913
945
inlineStylesheet : boolean ;
946
+ signal : AbortSignal ;
914
947
newlyAddedElement ?: boolean ;
915
948
maskInputOptions ?: MaskInputOptions ;
916
949
needsMask ?: boolean ;
@@ -960,6 +993,7 @@ export function serializeNodeWithId(
960
993
keepIframeSrcFn = ( ) => false ,
961
994
newlyAddedElement = false ,
962
995
cssCaptured = false ,
996
+ signal,
963
997
} = options ;
964
998
let { needsMask } = options ;
965
999
let { preserveWhiteSpace = true } = options ;
@@ -1056,6 +1090,7 @@ export function serializeNodeWithId(
1056
1090
maskTextSelector,
1057
1091
skipChild,
1058
1092
inlineStylesheet,
1093
+ signal,
1059
1094
maskInputOptions,
1060
1095
maskTextFn,
1061
1096
maskInputFn,
@@ -1132,6 +1167,7 @@ export function serializeNodeWithId(
1132
1167
maskTextSelector,
1133
1168
skipChild : false ,
1134
1169
inlineStylesheet,
1170
+ signal,
1135
1171
maskInputOptions,
1136
1172
maskTextFn,
1137
1173
maskInputFn,
@@ -1157,6 +1193,7 @@ export function serializeNodeWithId(
1157
1193
}
1158
1194
} ,
1159
1195
iframeLoadTimeout ,
1196
+ signal ,
1160
1197
) ;
1161
1198
}
1162
1199
@@ -1184,6 +1221,7 @@ export function serializeNodeWithId(
1184
1221
maskTextSelector,
1185
1222
skipChild : false ,
1186
1223
inlineStylesheet,
1224
+ signal,
1187
1225
maskInputOptions,
1188
1226
maskTextFn,
1189
1227
maskInputFn,
@@ -1209,6 +1247,7 @@ export function serializeNodeWithId(
1209
1247
}
1210
1248
} ,
1211
1249
stylesheetLoadTimeout ,
1250
+ signal ,
1212
1251
) ;
1213
1252
}
1214
1253
@@ -1244,6 +1283,7 @@ function snapshot(
1244
1283
) => unknown ;
1245
1284
stylesheetLoadTimeout ?: number ;
1246
1285
keepIframeSrcFn ?: KeepIframeSrcFn ;
1286
+ signal ?: AbortSignal ;
1247
1287
} ,
1248
1288
) : serializedNodeWithId | null {
1249
1289
const {
@@ -1253,6 +1293,7 @@ function snapshot(
1253
1293
maskTextClass = 'rr-mask' ,
1254
1294
maskTextSelector = null ,
1255
1295
inlineStylesheet = true ,
1296
+ signal = new AbortController ( ) . signal ,
1256
1297
inlineImages = false ,
1257
1298
recordCanvas = false ,
1258
1299
maskAllInputs = false ,
@@ -1320,6 +1361,7 @@ function snapshot(
1320
1361
maskTextSelector,
1321
1362
skipChild : false ,
1322
1363
inlineStylesheet,
1364
+ signal,
1323
1365
maskInputOptions,
1324
1366
maskTextFn,
1325
1367
maskInputFn,
0 commit comments