@spotify-confidence/csr-recorder 0.17.18 → 0.17.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +38 -4
- package/dist/index.js +38 -4
- package/package.json +2 -2
- package/src/engine/rrweb-engine.test.ts +137 -0
- package/src/engine/rrweb-engine.ts +52 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.17.20](https://github.com/spotify/confidence-sdk-js/compare/csr-recorder-v0.17.19...csr-recorder-v0.17.20) (2026-09-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **csr-recorder:** ignore blocked iframe attachments ([#463](https://github.com/spotify/confidence-sdk-js/issues/463)) ([bf549d7](https://github.com/spotify/confidence-sdk-js/commit/bf549d745f62fd280139dbfd00b9e664838c95a7))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @spotify-confidence/csr-common bumped to 0.18.12
|
|
16
|
+
|
|
17
|
+
## [0.17.19](https://github.com/spotify/confidence-sdk-js/compare/csr-recorder-v0.17.18...csr-recorder-v0.17.19) (2026-09-11)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
* The following workspace dependencies were updated
|
|
23
|
+
* dependencies
|
|
24
|
+
* @spotify-confidence/csr-common bumped to 0.18.11
|
|
25
|
+
|
|
3
26
|
## [0.17.18](https://github.com/spotify/confidence-sdk-js/compare/csr-recorder-v0.17.17...csr-recorder-v0.17.18) (2026-09-09)
|
|
4
27
|
|
|
5
28
|
|
package/dist/index.cjs
CHANGED
|
@@ -11127,12 +11127,33 @@ const ALL_CONSOLE_LEVELS = [
|
|
|
11127
11127
|
"info"
|
|
11128
11128
|
];
|
|
11129
11129
|
const BLOCKED_ELEMENT_ATTRIBUTE = "data-csr-blocked-element";
|
|
11130
|
-
function labelBlockedElement(node) {
|
|
11130
|
+
function labelBlockedElement(node, tracker, parentId) {
|
|
11131
|
+
if (node.id !== void 0 && parentId !== void 0) {
|
|
11132
|
+
const previousParentId = tracker.parentIdByNode.get(node.id);
|
|
11133
|
+
if (previousParentId !== void 0 && previousParentId !== parentId) tracker.childIdsByParent.get(previousParentId)?.delete(node.id);
|
|
11134
|
+
tracker.parentIdByNode.set(node.id, parentId);
|
|
11135
|
+
const childIds = tracker.childIdsByParent.get(parentId) ?? /* @__PURE__ */ new Set();
|
|
11136
|
+
childIds.add(node.id);
|
|
11137
|
+
tracker.childIdsByParent.set(parentId, childIds);
|
|
11138
|
+
}
|
|
11131
11139
|
if (node.type === 2 && node.tagName !== void 0 && node.attributes !== void 0 && typeof node.attributes.rr_width === "string" && typeof node.attributes.rr_height === "string") {
|
|
11140
|
+
if (node.tagName === "iframe" && node.id !== void 0) tracker.blockedIframeIds.add(node.id);
|
|
11132
11141
|
node.attributes[BLOCKED_ELEMENT_ATTRIBUTE] = node.tagName;
|
|
11133
11142
|
node.tagName = "div";
|
|
11134
11143
|
}
|
|
11135
|
-
node.childNodes?.forEach(labelBlockedElement);
|
|
11144
|
+
node.childNodes?.forEach((child) => labelBlockedElement(child, tracker, node.id ?? parentId));
|
|
11145
|
+
}
|
|
11146
|
+
function removeSerializedSubtree(rootId, parentId, tracker) {
|
|
11147
|
+
tracker.childIdsByParent.get(parentId)?.delete(rootId);
|
|
11148
|
+
const pendingIds = [rootId];
|
|
11149
|
+
while (pendingIds.length > 0) {
|
|
11150
|
+
const id = pendingIds.pop();
|
|
11151
|
+
const childIds = tracker.childIdsByParent.get(id);
|
|
11152
|
+
if (childIds) pendingIds.push(...childIds);
|
|
11153
|
+
tracker.blockedIframeIds.delete(id);
|
|
11154
|
+
tracker.childIdsByParent.delete(id);
|
|
11155
|
+
tracker.parentIdByNode.delete(id);
|
|
11156
|
+
}
|
|
11136
11157
|
}
|
|
11137
11158
|
/**
|
|
11138
11159
|
* rrweb strips blocked elements down to their dimensions, but retains their
|
|
@@ -11140,13 +11161,26 @@ function labelBlockedElement(node) {
|
|
|
11140
11161
|
* metadata so players can render a useful placeholder label.
|
|
11141
11162
|
*/
|
|
11142
11163
|
function blockedElementLabelsPlugin() {
|
|
11164
|
+
const tracker = {
|
|
11165
|
+
blockedIframeIds: /* @__PURE__ */ new Set(),
|
|
11166
|
+
childIdsByParent: /* @__PURE__ */ new Map(),
|
|
11167
|
+
parentIdByNode: /* @__PURE__ */ new Map()
|
|
11168
|
+
};
|
|
11143
11169
|
return {
|
|
11144
11170
|
name: _spotify_confidence_csr_common.RecordingPluginName.BlockedElementLabels,
|
|
11145
11171
|
options: {},
|
|
11146
11172
|
observer: () => () => {},
|
|
11147
11173
|
eventProcessor: (event) => {
|
|
11148
|
-
if (event.type === EventType.FullSnapshot)
|
|
11149
|
-
|
|
11174
|
+
if (event.type === EventType.FullSnapshot) {
|
|
11175
|
+
tracker.blockedIframeIds.clear();
|
|
11176
|
+
tracker.childIdsByParent.clear();
|
|
11177
|
+
tracker.parentIdByNode.clear();
|
|
11178
|
+
labelBlockedElement(event.data.node, tracker);
|
|
11179
|
+
} else if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation) {
|
|
11180
|
+
if (event.data.isAttachIframe) event.data.adds = event.data.adds.filter((add) => !tracker.blockedIframeIds.has(add.parentId));
|
|
11181
|
+
event.data.removes.forEach((remove) => removeSerializedSubtree(remove.id, remove.parentId, tracker));
|
|
11182
|
+
event.data.adds.forEach((add) => labelBlockedElement(add.node, tracker, add.parentId));
|
|
11183
|
+
}
|
|
11150
11184
|
return event;
|
|
11151
11185
|
}
|
|
11152
11186
|
};
|
package/dist/index.js
CHANGED
|
@@ -11126,12 +11126,33 @@ const ALL_CONSOLE_LEVELS = [
|
|
|
11126
11126
|
"info"
|
|
11127
11127
|
];
|
|
11128
11128
|
const BLOCKED_ELEMENT_ATTRIBUTE = "data-csr-blocked-element";
|
|
11129
|
-
function labelBlockedElement(node) {
|
|
11129
|
+
function labelBlockedElement(node, tracker, parentId) {
|
|
11130
|
+
if (node.id !== void 0 && parentId !== void 0) {
|
|
11131
|
+
const previousParentId = tracker.parentIdByNode.get(node.id);
|
|
11132
|
+
if (previousParentId !== void 0 && previousParentId !== parentId) tracker.childIdsByParent.get(previousParentId)?.delete(node.id);
|
|
11133
|
+
tracker.parentIdByNode.set(node.id, parentId);
|
|
11134
|
+
const childIds = tracker.childIdsByParent.get(parentId) ?? /* @__PURE__ */ new Set();
|
|
11135
|
+
childIds.add(node.id);
|
|
11136
|
+
tracker.childIdsByParent.set(parentId, childIds);
|
|
11137
|
+
}
|
|
11130
11138
|
if (node.type === 2 && node.tagName !== void 0 && node.attributes !== void 0 && typeof node.attributes.rr_width === "string" && typeof node.attributes.rr_height === "string") {
|
|
11139
|
+
if (node.tagName === "iframe" && node.id !== void 0) tracker.blockedIframeIds.add(node.id);
|
|
11131
11140
|
node.attributes[BLOCKED_ELEMENT_ATTRIBUTE] = node.tagName;
|
|
11132
11141
|
node.tagName = "div";
|
|
11133
11142
|
}
|
|
11134
|
-
node.childNodes?.forEach(labelBlockedElement);
|
|
11143
|
+
node.childNodes?.forEach((child) => labelBlockedElement(child, tracker, node.id ?? parentId));
|
|
11144
|
+
}
|
|
11145
|
+
function removeSerializedSubtree(rootId, parentId, tracker) {
|
|
11146
|
+
tracker.childIdsByParent.get(parentId)?.delete(rootId);
|
|
11147
|
+
const pendingIds = [rootId];
|
|
11148
|
+
while (pendingIds.length > 0) {
|
|
11149
|
+
const id = pendingIds.pop();
|
|
11150
|
+
const childIds = tracker.childIdsByParent.get(id);
|
|
11151
|
+
if (childIds) pendingIds.push(...childIds);
|
|
11152
|
+
tracker.blockedIframeIds.delete(id);
|
|
11153
|
+
tracker.childIdsByParent.delete(id);
|
|
11154
|
+
tracker.parentIdByNode.delete(id);
|
|
11155
|
+
}
|
|
11135
11156
|
}
|
|
11136
11157
|
/**
|
|
11137
11158
|
* rrweb strips blocked elements down to their dimensions, but retains their
|
|
@@ -11139,13 +11160,26 @@ function labelBlockedElement(node) {
|
|
|
11139
11160
|
* metadata so players can render a useful placeholder label.
|
|
11140
11161
|
*/
|
|
11141
11162
|
function blockedElementLabelsPlugin() {
|
|
11163
|
+
const tracker = {
|
|
11164
|
+
blockedIframeIds: /* @__PURE__ */ new Set(),
|
|
11165
|
+
childIdsByParent: /* @__PURE__ */ new Map(),
|
|
11166
|
+
parentIdByNode: /* @__PURE__ */ new Map()
|
|
11167
|
+
};
|
|
11142
11168
|
return {
|
|
11143
11169
|
name: RecordingPluginName.BlockedElementLabels,
|
|
11144
11170
|
options: {},
|
|
11145
11171
|
observer: () => () => {},
|
|
11146
11172
|
eventProcessor: (event) => {
|
|
11147
|
-
if (event.type === EventType.FullSnapshot)
|
|
11148
|
-
|
|
11173
|
+
if (event.type === EventType.FullSnapshot) {
|
|
11174
|
+
tracker.blockedIframeIds.clear();
|
|
11175
|
+
tracker.childIdsByParent.clear();
|
|
11176
|
+
tracker.parentIdByNode.clear();
|
|
11177
|
+
labelBlockedElement(event.data.node, tracker);
|
|
11178
|
+
} else if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation) {
|
|
11179
|
+
if (event.data.isAttachIframe) event.data.adds = event.data.adds.filter((add) => !tracker.blockedIframeIds.has(add.parentId));
|
|
11180
|
+
event.data.removes.forEach((remove) => removeSerializedSubtree(remove.id, remove.parentId, tracker));
|
|
11181
|
+
event.data.adds.forEach((add) => labelBlockedElement(add.node, tracker, add.parentId));
|
|
11182
|
+
}
|
|
11149
11183
|
return event;
|
|
11150
11184
|
}
|
|
11151
11185
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spotify-confidence/csr-recorder",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "0.17.
|
|
4
|
+
"version": "0.17.20",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/spotify/confidence-sdk-js.git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@rrweb/rrweb-plugin-console-record": "^2.0.1",
|
|
38
|
-
"@spotify-confidence/csr-common": "0.18.
|
|
38
|
+
"@spotify-confidence/csr-common": "0.18.12",
|
|
39
39
|
"rrweb": "^2.0.1"
|
|
40
40
|
},
|
|
41
41
|
"module": "./dist/index.js",
|
|
@@ -13,6 +13,52 @@ vi.mock('rrweb', async importOriginal => ({
|
|
|
13
13
|
takeFullSnapshot: (isCheckout: boolean) => takeFullSnapshotSpy(isCheckout),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
+
function blockedElementLabelsPlugin() {
|
|
17
|
+
new RrwebEngine().start({}, () => {});
|
|
18
|
+
return recordSpy.mock.calls[0][0].plugins.find(
|
|
19
|
+
({ name }: { name: string }) => name === RecordingPluginName.BlockedElementLabels,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fullSnapshotWithNode(node: unknown) {
|
|
24
|
+
return {
|
|
25
|
+
type: 2,
|
|
26
|
+
timestamp: 1,
|
|
27
|
+
data: {
|
|
28
|
+
node: {
|
|
29
|
+
type: 0,
|
|
30
|
+
id: 1,
|
|
31
|
+
childNodes: [node],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fullSnapshotWithIframe(id: number, attributes: Record<string, string>) {
|
|
38
|
+
return fullSnapshotWithNode({ type: 2, id, tagName: 'iframe', attributes, childNodes: [] });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function attachIframeDocument(parentId: number, childNodes: unknown[] = []) {
|
|
42
|
+
return {
|
|
43
|
+
type: 3,
|
|
44
|
+
timestamp: 2,
|
|
45
|
+
data: {
|
|
46
|
+
source: 0,
|
|
47
|
+
adds: [
|
|
48
|
+
{
|
|
49
|
+
parentId,
|
|
50
|
+
nextId: null,
|
|
51
|
+
node: { type: 0, id: parentId + 1, childNodes },
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
removes: [],
|
|
55
|
+
texts: [],
|
|
56
|
+
attributes: [],
|
|
57
|
+
isAttachIframe: true,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
16
62
|
describe('RrwebEngine', () => {
|
|
17
63
|
beforeEach(() => {
|
|
18
64
|
recordSpy.mockClear();
|
|
@@ -108,6 +154,97 @@ describe('RrwebEngine', () => {
|
|
|
108
154
|
});
|
|
109
155
|
});
|
|
110
156
|
|
|
157
|
+
it('drops iframe document attachments for blocked iframe placeholders', () => {
|
|
158
|
+
const plugin = blockedElementLabelsPlugin();
|
|
159
|
+
plugin.eventProcessor(fullSnapshotWithIframe(2, { rr_width: '640px', rr_height: '360px' }));
|
|
160
|
+
const attachIframeEvent = attachIframeDocument(2);
|
|
161
|
+
|
|
162
|
+
expect(plugin.eventProcessor(attachIframeEvent)).toEqual({
|
|
163
|
+
...attachIframeEvent,
|
|
164
|
+
data: { ...attachIframeEvent.data, adds: [] },
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('keeps iframe document attachments for unblocked iframes', () => {
|
|
169
|
+
const plugin = blockedElementLabelsPlugin();
|
|
170
|
+
plugin.eventProcessor(fullSnapshotWithIframe(2, {}));
|
|
171
|
+
const attachIframeEvent = attachIframeDocument(2);
|
|
172
|
+
const result = plugin.eventProcessor(attachIframeEvent);
|
|
173
|
+
|
|
174
|
+
expect(result.data.adds).toHaveLength(1);
|
|
175
|
+
expect(result.data.adds[0].parentId).toBe(2);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('stops filtering attachments after a blocked iframe is removed', () => {
|
|
179
|
+
const plugin = blockedElementLabelsPlugin();
|
|
180
|
+
plugin.eventProcessor(fullSnapshotWithIframe(2, { rr_width: '640px', rr_height: '360px' }));
|
|
181
|
+
plugin.eventProcessor({
|
|
182
|
+
type: 3,
|
|
183
|
+
timestamp: 2,
|
|
184
|
+
data: {
|
|
185
|
+
source: 0,
|
|
186
|
+
adds: [],
|
|
187
|
+
removes: [{ parentId: 1, id: 2 }],
|
|
188
|
+
texts: [],
|
|
189
|
+
attributes: [],
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(plugin.eventProcessor(attachIframeDocument(2)).data.adds).toHaveLength(1);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('stops filtering attachments after an ancestor of a blocked iframe is removed', () => {
|
|
197
|
+
const plugin = blockedElementLabelsPlugin();
|
|
198
|
+
plugin.eventProcessor(
|
|
199
|
+
fullSnapshotWithNode({
|
|
200
|
+
type: 2,
|
|
201
|
+
id: 2,
|
|
202
|
+
tagName: 'div',
|
|
203
|
+
attributes: {},
|
|
204
|
+
childNodes: [
|
|
205
|
+
{
|
|
206
|
+
type: 2,
|
|
207
|
+
id: 3,
|
|
208
|
+
tagName: 'iframe',
|
|
209
|
+
attributes: { rr_width: '640px', rr_height: '360px' },
|
|
210
|
+
childNodes: [],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
plugin.eventProcessor({
|
|
216
|
+
type: 3,
|
|
217
|
+
timestamp: 2,
|
|
218
|
+
data: {
|
|
219
|
+
source: 0,
|
|
220
|
+
adds: [],
|
|
221
|
+
removes: [{ parentId: 1, id: 2 }],
|
|
222
|
+
texts: [],
|
|
223
|
+
attributes: [],
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(plugin.eventProcessor(attachIframeDocument(3)).data.adds).toHaveLength(1);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('does not track blocked iframes inside discarded iframe documents', () => {
|
|
231
|
+
const plugin = blockedElementLabelsPlugin();
|
|
232
|
+
plugin.eventProcessor(fullSnapshotWithIframe(2, { rr_width: '640px', rr_height: '360px' }));
|
|
233
|
+
plugin.eventProcessor(
|
|
234
|
+
attachIframeDocument(2, [
|
|
235
|
+
{
|
|
236
|
+
type: 2,
|
|
237
|
+
id: 4,
|
|
238
|
+
tagName: 'iframe',
|
|
239
|
+
attributes: { rr_width: '640px', rr_height: '360px' },
|
|
240
|
+
childNodes: [],
|
|
241
|
+
},
|
|
242
|
+
]),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(plugin.eventProcessor(attachIframeDocument(4)).data.adds).toHaveLength(1);
|
|
246
|
+
});
|
|
247
|
+
|
|
111
248
|
it('throttles mousemove to 100ms and records only last input value', () => {
|
|
112
249
|
new RrwebEngine().start({}, () => {});
|
|
113
250
|
const opts = recordSpy.mock.calls[0][0];
|
|
@@ -13,13 +13,31 @@ type ClickModifiers = Pick<MouseEvent, 'button' | 'altKey' | 'ctrlKey' | 'metaKe
|
|
|
13
13
|
const BLOCKED_ELEMENT_ATTRIBUTE = 'data-csr-blocked-element';
|
|
14
14
|
|
|
15
15
|
type SerializedNode = {
|
|
16
|
+
id?: number;
|
|
16
17
|
type: number;
|
|
17
18
|
tagName?: string;
|
|
18
19
|
attributes?: Record<string, unknown>;
|
|
19
20
|
childNodes?: SerializedNode[];
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
type SerializedNodeTracker = {
|
|
24
|
+
blockedIframeIds: Set<number>;
|
|
25
|
+
childIdsByParent: Map<number, Set<number>>;
|
|
26
|
+
parentIdByNode: Map<number, number>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function labelBlockedElement(node: SerializedNode, tracker: SerializedNodeTracker, parentId?: number): void {
|
|
30
|
+
if (node.id !== undefined && parentId !== undefined) {
|
|
31
|
+
const previousParentId = tracker.parentIdByNode.get(node.id);
|
|
32
|
+
if (previousParentId !== undefined && previousParentId !== parentId) {
|
|
33
|
+
tracker.childIdsByParent.get(previousParentId)?.delete(node.id);
|
|
34
|
+
}
|
|
35
|
+
tracker.parentIdByNode.set(node.id, parentId);
|
|
36
|
+
const childIds = tracker.childIdsByParent.get(parentId) ?? new Set<number>();
|
|
37
|
+
childIds.add(node.id);
|
|
38
|
+
tracker.childIdsByParent.set(parentId, childIds);
|
|
39
|
+
}
|
|
40
|
+
|
|
23
41
|
const isBlockedElement =
|
|
24
42
|
node.type === 2 &&
|
|
25
43
|
node.tagName !== undefined &&
|
|
@@ -28,11 +46,28 @@ function labelBlockedElement(node: SerializedNode): void {
|
|
|
28
46
|
typeof node.attributes.rr_height === 'string';
|
|
29
47
|
|
|
30
48
|
if (isBlockedElement) {
|
|
49
|
+
if (node.tagName === 'iframe' && node.id !== undefined) {
|
|
50
|
+
tracker.blockedIframeIds.add(node.id);
|
|
51
|
+
}
|
|
31
52
|
node.attributes![BLOCKED_ELEMENT_ATTRIBUTE] = node.tagName;
|
|
32
53
|
node.tagName = 'div';
|
|
33
54
|
}
|
|
34
55
|
|
|
35
|
-
node.childNodes?.forEach(labelBlockedElement);
|
|
56
|
+
node.childNodes?.forEach(child => labelBlockedElement(child, tracker, node.id ?? parentId));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function removeSerializedSubtree(rootId: number, parentId: number, tracker: SerializedNodeTracker): void {
|
|
60
|
+
tracker.childIdsByParent.get(parentId)?.delete(rootId);
|
|
61
|
+
const pendingIds = [rootId];
|
|
62
|
+
|
|
63
|
+
while (pendingIds.length > 0) {
|
|
64
|
+
const id = pendingIds.pop()!;
|
|
65
|
+
const childIds = tracker.childIdsByParent.get(id);
|
|
66
|
+
if (childIds) pendingIds.push(...childIds);
|
|
67
|
+
tracker.blockedIframeIds.delete(id);
|
|
68
|
+
tracker.childIdsByParent.delete(id);
|
|
69
|
+
tracker.parentIdByNode.delete(id);
|
|
70
|
+
}
|
|
36
71
|
}
|
|
37
72
|
|
|
38
73
|
/**
|
|
@@ -41,15 +76,28 @@ function labelBlockedElement(node: SerializedNode): void {
|
|
|
41
76
|
* metadata so players can render a useful placeholder label.
|
|
42
77
|
*/
|
|
43
78
|
function blockedElementLabelsPlugin(): RrwebPlugin {
|
|
79
|
+
const tracker: SerializedNodeTracker = {
|
|
80
|
+
blockedIframeIds: new Set<number>(),
|
|
81
|
+
childIdsByParent: new Map<number, Set<number>>(),
|
|
82
|
+
parentIdByNode: new Map<number, number>(),
|
|
83
|
+
};
|
|
84
|
+
|
|
44
85
|
return {
|
|
45
86
|
name: RecordingPluginName.BlockedElementLabels,
|
|
46
87
|
options: {},
|
|
47
88
|
observer: () => () => {},
|
|
48
89
|
eventProcessor: event => {
|
|
49
90
|
if (event.type === EventType.FullSnapshot) {
|
|
50
|
-
|
|
91
|
+
tracker.blockedIframeIds.clear();
|
|
92
|
+
tracker.childIdsByParent.clear();
|
|
93
|
+
tracker.parentIdByNode.clear();
|
|
94
|
+
labelBlockedElement(event.data.node as SerializedNode, tracker);
|
|
51
95
|
} else if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation) {
|
|
52
|
-
event.data.
|
|
96
|
+
if (event.data.isAttachIframe) {
|
|
97
|
+
event.data.adds = event.data.adds.filter(add => !tracker.blockedIframeIds.has(add.parentId));
|
|
98
|
+
}
|
|
99
|
+
event.data.removes.forEach(remove => removeSerializedSubtree(remove.id, remove.parentId, tracker));
|
|
100
|
+
event.data.adds.forEach(add => labelBlockedElement(add.node as SerializedNode, tracker, add.parentId));
|
|
53
101
|
}
|
|
54
102
|
|
|
55
103
|
return event;
|