@@ -196,6 +196,34 @@ export function _isBlockedElement(
196
196
return false ;
197
197
}
198
198
199
+ // https://stackoverflow.com/a/36155560
200
+ function onceIframeLoaded (
201
+ iframeEl : HTMLIFrameElement ,
202
+ listener : ( ) => unknown ,
203
+ ) {
204
+ const win = iframeEl . contentWindow ;
205
+ if ( ! win ) {
206
+ return ;
207
+ }
208
+ // document is loading
209
+ if ( win . document . readyState !== 'complete' ) {
210
+ iframeEl . addEventListener ( 'load' , listener ) ;
211
+ return ;
212
+ }
213
+ // check blank frame for Chrome
214
+ const blankUrl = 'about:blank' ;
215
+ if (
216
+ win . location . href !== blankUrl ||
217
+ iframeEl . src === blankUrl ||
218
+ iframeEl . src === ''
219
+ ) {
220
+ listener ( ) ;
221
+ return ;
222
+ }
223
+ // use default listener
224
+ iframeEl . addEventListener ( 'load' , listener ) ;
225
+ }
226
+
199
227
function serializeNode (
200
228
n : Node ,
201
229
options : {
@@ -215,18 +243,26 @@ function serializeNode(
215
243
maskInputOptions = { } ,
216
244
recordCanvas,
217
245
} = options ;
246
+ // Only record root id when document object is not the base document
247
+ let rootId : number | undefined ;
248
+ if ( ( ( doc as unknown ) as INode ) . __sn ) {
249
+ const docId = ( ( doc as unknown ) as INode ) . __sn . id ;
250
+ rootId = docId === 1 ? undefined : docId ;
251
+ }
218
252
switch ( n . nodeType ) {
219
253
case n . DOCUMENT_NODE :
220
254
return {
221
255
type : NodeType . Document ,
222
256
childNodes : [ ] ,
257
+ rootId,
223
258
} ;
224
259
case n . DOCUMENT_TYPE_NODE :
225
260
return {
226
261
type : NodeType . DocumentType ,
227
262
name : ( n as DocumentType ) . name ,
228
263
publicId : ( n as DocumentType ) . publicId ,
229
264
systemId : ( n as DocumentType ) . systemId ,
265
+ rootId,
230
266
} ;
231
267
case n . ELEMENT_NODE :
232
268
const needBlock = _isBlockedElement (
@@ -318,6 +354,7 @@ function serializeNode(
318
354
if ( ( n as HTMLElement ) . scrollTop ) {
319
355
attributes . rr_scrollTop = ( n as HTMLElement ) . scrollTop ;
320
356
}
357
+ // block element
321
358
if ( needBlock ) {
322
359
const { width, height } = ( n as HTMLElement ) . getBoundingClientRect ( ) ;
323
360
attributes = {
@@ -326,13 +363,18 @@ function serializeNode(
326
363
rr_height : `${ height } px` ,
327
364
} ;
328
365
}
366
+ // iframe
367
+ if ( tagName === 'iframe' ) {
368
+ delete attributes . src ;
369
+ }
329
370
return {
330
371
type : NodeType . Element ,
331
372
tagName,
332
373
attributes,
333
374
childNodes : [ ] ,
334
375
isSVG : isSVGElement ( n as Element ) || undefined ,
335
376
needBlock,
377
+ rootId,
336
378
} ;
337
379
case n . TEXT_NODE :
338
380
// The parent node may not be a html element which has a tagName attribute.
@@ -351,16 +393,19 @@ function serializeNode(
351
393
type : NodeType . Text ,
352
394
textContent : textContent || '' ,
353
395
isStyle,
396
+ rootId,
354
397
} ;
355
398
case n . CDATA_SECTION_NODE :
356
399
return {
357
400
type : NodeType . CDATA ,
358
401
textContent : '' ,
402
+ rootId,
359
403
} ;
360
404
case n . COMMENT_NODE :
361
405
return {
362
406
type : NodeType . Comment ,
363
407
textContent : ( n as Comment ) . textContent || '' ,
408
+ rootId,
364
409
} ;
365
410
default :
366
411
return false ;
@@ -472,6 +517,8 @@ export function serializeNodeWithId(
472
517
slimDOMOptions : SlimDOMOptions ;
473
518
recordCanvas ?: boolean ;
474
519
preserveWhiteSpace ?: boolean ;
520
+ onSerialize ?: ( n : INode ) => unknown ;
521
+ onIframeLoad ?: ( iframeINode : INode , node : serializedNodeWithId ) => unknown ;
475
522
} ,
476
523
) : serializedNodeWithId | null {
477
524
const {
@@ -484,6 +531,8 @@ export function serializeNodeWithId(
484
531
maskInputOptions = { } ,
485
532
slimDOMOptions,
486
533
recordCanvas = false ,
534
+ onSerialize,
535
+ onIframeLoad,
487
536
} = options ;
488
537
let { preserveWhiteSpace = true } = options ;
489
538
const _serializedNode = serializeNode ( n , {
@@ -521,6 +570,9 @@ export function serializeNodeWithId(
521
570
return null ; // slimDOM
522
571
}
523
572
map [ id ] = n as INode ;
573
+ if ( onSerialize ) {
574
+ onSerialize ( n as INode ) ;
575
+ }
524
576
let recordChild = ! skipChild ;
525
577
if ( serializedNode . type === NodeType . Element ) {
526
578
recordChild = recordChild && ! serializedNode . needBlock ;
@@ -552,12 +604,44 @@ export function serializeNodeWithId(
552
604
slimDOMOptions,
553
605
recordCanvas,
554
606
preserveWhiteSpace,
607
+ onSerialize,
608
+ onIframeLoad,
555
609
} ) ;
556
610
if ( serializedChildNode ) {
557
611
serializedNode . childNodes . push ( serializedChildNode ) ;
558
612
}
559
613
}
560
614
}
615
+
616
+ if (
617
+ serializedNode . type === NodeType . Element &&
618
+ serializedNode . tagName === 'iframe'
619
+ ) {
620
+ onceIframeLoaded ( n as HTMLIFrameElement , ( ) => {
621
+ const iframeDoc = ( n as HTMLIFrameElement ) . contentDocument ;
622
+ if ( iframeDoc && onIframeLoad ) {
623
+ const serializedIframeNode = serializeNodeWithId ( iframeDoc , {
624
+ doc : iframeDoc ,
625
+ map,
626
+ blockClass,
627
+ blockSelector,
628
+ skipChild : false ,
629
+ inlineStylesheet,
630
+ maskInputOptions,
631
+ slimDOMOptions,
632
+ recordCanvas,
633
+ preserveWhiteSpace,
634
+ onSerialize,
635
+ onIframeLoad,
636
+ } ) ;
637
+
638
+ if ( serializedIframeNode ) {
639
+ onIframeLoad ( n as INode , serializedIframeNode ) ;
640
+ }
641
+ }
642
+ } ) ;
643
+ }
644
+
561
645
return serializedNode ;
562
646
}
563
647
@@ -570,6 +654,9 @@ function snapshot(
570
654
slimDOM ?: boolean | SlimDOMOptions ;
571
655
recordCanvas ?: boolean ;
572
656
blockSelector ?: string | null ;
657
+ preserveWhiteSpace ?: boolean ;
658
+ onSerialize ?: ( n : INode ) => unknown ;
659
+ onIframeLoad ?: ( iframeINode : INode , node : serializedNodeWithId ) => unknown ;
573
660
} ,
574
661
) : [ serializedNodeWithId | null , idNodeMap ] {
575
662
const {
@@ -579,6 +666,9 @@ function snapshot(
579
666
blockSelector = null ,
580
667
maskAllInputs = false ,
581
668
slimDOM = false ,
669
+ preserveWhiteSpace,
670
+ onSerialize,
671
+ onIframeLoad,
582
672
} = options || { } ;
583
673
const idNodeMap : idNodeMap = { } ;
584
674
const maskInputOptions : MaskInputOptions =
@@ -632,6 +722,9 @@ function snapshot(
632
722
maskInputOptions,
633
723
slimDOMOptions,
634
724
recordCanvas,
725
+ preserveWhiteSpace,
726
+ onSerialize,
727
+ onIframeLoad,
635
728
} ) ,
636
729
idNodeMap ,
637
730
] ;
0 commit comments