@@ -10,6 +10,53 @@ type RrwebPlugin = NonNullable<recordOptions<RecordingEvent>['plugins']>[number]
1010
1111type ClickModifiers = Pick < MouseEvent , 'button' | 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey' > ;
1212
13+ const BLOCKED_ELEMENT_ATTRIBUTE = 'data-csr-blocked-element' ;
14+
15+ type SerializedNode = {
16+ type : number ;
17+ tagName ?: string ;
18+ attributes ?: Record < string , unknown > ;
19+ childNodes ?: SerializedNode [ ] ;
20+ } ;
21+
22+ function labelBlockedElement ( node : SerializedNode ) : void {
23+ const isBlockedElement =
24+ node . type === 2 &&
25+ node . tagName !== undefined &&
26+ node . attributes !== undefined &&
27+ typeof node . attributes . rr_width === 'string' &&
28+ typeof node . attributes . rr_height === 'string' ;
29+
30+ if ( isBlockedElement ) {
31+ node . attributes ! [ BLOCKED_ELEMENT_ATTRIBUTE ] = node . tagName ;
32+ node . tagName = 'div' ;
33+ }
34+
35+ node . childNodes ?. forEach ( labelBlockedElement ) ;
36+ }
37+
38+ /**
39+ * rrweb strips blocked elements down to their dimensions, but retains their
40+ * original tag name. Rebuild them as inert divs and keep the tag name as safe
41+ * metadata so players can render a useful placeholder label.
42+ */
43+ function blockedElementLabelsPlugin ( ) : RrwebPlugin {
44+ return {
45+ name : 'csr/blocked-element-labels@1' ,
46+ options : { } ,
47+ observer : ( ) => ( ) => { } ,
48+ eventProcessor : event => {
49+ if ( event . type === EventType . FullSnapshot ) {
50+ labelBlockedElement ( event . data . node as SerializedNode ) ;
51+ } else if ( event . type === EventType . IncrementalSnapshot && event . data . source === IncrementalSource . Mutation ) {
52+ event . data . adds . forEach ( add => labelBlockedElement ( add . node as SerializedNode ) ) ;
53+ }
54+
55+ return event ;
56+ } ,
57+ } ;
58+ }
59+
1360type ClipboardAction = 'copy' | 'cut' | 'paste' ;
1461
1562/**
@@ -102,7 +149,7 @@ export class RrwebEngine implements RecordingEngine {
102149 const maskSelectors = config . maskSelectors ?? DEFAULT_MASK_SELECTORS ;
103150 const blockSelectors = config . blockSelectors ?? DEFAULT_BLOCK_SELECTORS ;
104151
105- const plugins : RrwebPlugin [ ] = [ clickModifiersPlugin ( ) , clipboardActionsPlugin ( ) ] ;
152+ const plugins : RrwebPlugin [ ] = [ clickModifiersPlugin ( ) , clipboardActionsPlugin ( ) , blockedElementLabelsPlugin ( ) ] ;
106153 const { captureConsoleLogs } = config ;
107154 if ( captureConsoleLogs ) {
108155 const levels = captureConsoleLogs === true ? ALL_CONSOLE_LEVELS : captureConsoleLogs . levels ;
0 commit comments