Skip to content

Commit 072b81b

Browse files
authored
Apply textContent on flush (#865)
* Apply textContent on flush * fix typo * Style sheet rules applied after <style>'s textContent override should work
1 parent 423372b commit 072b81b

File tree

4 files changed

+102
-12
lines changed

4 files changed

+102
-12
lines changed

packages/rrweb/src/replay/index.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
mouseMovePos,
4141
IWindow,
4242
canvasMutationCommand,
43+
textMutation,
4344
} from '../types';
4445
import {
4546
createMirror,
@@ -171,11 +172,17 @@ export class Replayer {
171172
this.virtualStyleRulesMap = new Map();
172173

173174
this.emitter.on(ReplayerEvents.Flush, () => {
174-
const { scrollMap, inputMap } = this.treeIndex.flush();
175+
const { scrollMap, inputMap, mutationData } = this.treeIndex.flush();
175176

176177
this.fragmentParentMap.forEach((parent, frag) =>
177178
this.restoreRealParent(frag, parent),
178179
);
180+
// apply text needs to happen before virtual style rules gets applied
181+
// as it can overwrite the contents of a stylesheet
182+
for (const d of mutationData.texts) {
183+
this.applyText(d, mutationData);
184+
}
185+
179186
for (const node of this.virtualStyleRulesMap.keys()) {
180187
// restore css rules of style elements after they are mounted
181188
this.restoreNodeSheet(node);
@@ -896,7 +903,16 @@ export class Replayer {
896903
case IncrementalSource.Mutation: {
897904
if (isSync) {
898905
d.adds.forEach((m) => this.treeIndex.add(m));
899-
d.texts.forEach((m) => this.treeIndex.text(m));
906+
d.texts.forEach((m) => {
907+
const target = this.mirror.getNode(m.id);
908+
const parent = (target?.parentNode as unknown) as INode | null;
909+
// remove any style rules that pending
910+
// for stylesheets where the contents get replaced
911+
if (parent && this.virtualStyleRulesMap.has(parent))
912+
this.virtualStyleRulesMap.delete(parent);
913+
914+
this.treeIndex.text(m);
915+
});
900916
d.attributes.forEach((m) => this.treeIndex.attribute(m));
901917
d.removes.forEach((m) => this.treeIndex.remove(m, this.mirror));
902918
}
@@ -1677,6 +1693,18 @@ export class Replayer {
16771693
}
16781694
}
16791695

1696+
private applyText(d: textMutation, mutation: mutationData) {
1697+
const target = this.mirror.getNode(d.id);
1698+
if (!target) {
1699+
return this.debugNodeNotFound(mutation, d.id);
1700+
}
1701+
try {
1702+
((target as Node) as HTMLElement).textContent = d.value;
1703+
} catch (error) {
1704+
// for safe
1705+
}
1706+
}
1707+
16801708
private legacy_resolveMissingNode(
16811709
map: missingNodeMap,
16821710
parent: Node,

packages/rrweb/test/__snapshots__/replayer.test.ts.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,27 @@ html.rrweb-paused *, html.rrweb-paused ::before, html.rrweb-paused ::after { ani
5656
file-cid-1
5757
@charset \\"utf-8\\";
5858
59-
.c011xx { padding: 1.3125rem; flex: 0 0 auto; width: 100%; }
59+
.css-added-at-500 { padding: 1.3125rem; flex: 0 0 auto; width: 100%; }
6060
6161
6262
file-cid-2
6363
@charset \\"utf-8\\";
6464
65-
.c01x { opacity: 1; transform: translateX(0px); }
65+
.css-added-at-200-overwritten-at-3000 { opacity: 1; transform: translateX(0px); }
6666
67-
.css-added-at-400 { border: 1px solid blue; }
67+
.css-added-at-400-overwritten-at-3000 { border: 1px solid blue; }
6868
6969
7070
file-cid-3
7171
@charset \\"utf-8\\";
7272
73-
.css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }
73+
.css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }
7474
75-
.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }
75+
.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }
7676
7777
.css-added-at-1000-deleted-at-2500 { display: flex; flex-direction: column; min-width: 60rem; min-height: 100vh; color: blue; }
7878
79-
.css-lsxxx { padding-left: 4rem; }
79+
.css-added-at-200.alt2 { padding-left: 4rem; }
8080
"
8181
`;
8282

packages/rrweb/test/events/style-sheet-rule-events.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const events: eventWithTime[] = [
5454
type: 3,
5555
isStyle: true,
5656
textContent:
57-
'\n.c01x {\n opacity: 1;\n transform: translateX(0);\n}\n',
57+
'\n.css-added-at-200-overwritten-at-3000 {\n opacity: 1;\n transform: translateX(0);\n}\n',
5858
},
5959
],
6060
},
@@ -64,7 +64,7 @@ const events: eventWithTime[] = [
6464
tagName: 'style',
6565
attributes: {
6666
_cssText:
67-
'.css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-lsxxx { padding-left: 4rem; }',
67+
'.css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-added-at-200.alt2 { padding-left: 4rem; }',
6868
'data-emotion': 'css',
6969
},
7070
childNodes: [
@@ -111,7 +111,8 @@ const events: eventWithTime[] = [
111111
id: 101,
112112
adds: [
113113
{
114-
rule: '.css-added-at-400{border: 1px solid blue;}',
114+
rule:
115+
'.css-added-at-400-overwritten-at-3000 {border: 1px solid blue;}',
115116
index: 1,
116117
},
117118
],
@@ -141,7 +142,7 @@ const events: eventWithTime[] = [
141142
type: 3,
142143
isStyle: true,
143144
textContent:
144-
'\n.c011xx {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n',
145+
'\n.css-added-at-500 {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n',
145146
},
146147
nextId: null,
147148
parentId: 255,
@@ -184,6 +185,37 @@ const events: eventWithTime[] = [
184185
type: EventType.IncrementalSnapshot,
185186
timestamp: now + 2500,
186187
},
188+
// overwrite all contents of stylesheet
189+
{
190+
data: {
191+
texts: [
192+
{
193+
id: 102,
194+
value: '.all-css-overwritten-at-3000 { color: indigo; }',
195+
},
196+
],
197+
attributes: [],
198+
removes: [],
199+
adds: [],
200+
source: IncrementalSource.Mutation,
201+
},
202+
type: EventType.IncrementalSnapshot,
203+
timestamp: now + 3000,
204+
},
205+
{
206+
data: {
207+
id: 101,
208+
adds: [
209+
{
210+
rule: '.css-added-at-3100{color:blue;}',
211+
index: 1,
212+
},
213+
],
214+
source: IncrementalSource.StyleSheetRule,
215+
},
216+
type: EventType.IncrementalSnapshot,
217+
timestamp: now + 3100,
218+
},
187219
];
188220

189221
export default events;

packages/rrweb/test/replayer.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,36 @@ describe('replayer', function () {
223223
expect(result).toEqual(false);
224224
});
225225

226+
it("should overwrite all StyleSheetRules by replacing style element's textContent while fast-forwarding", async () => {
227+
await page.evaluate(`events = ${JSON.stringify(styleSheetRuleEvents)}`);
228+
const result = await page.evaluate(`
229+
const { Replayer } = rrweb;
230+
const replayer = new Replayer(events);
231+
replayer.pause(3500);
232+
const rules = [...replayer.iframe.contentDocument.styleSheets].map(
233+
(sheet) => [...sheet.rules],
234+
).flat();
235+
rules.some((x) => x.selectorText === '.css-added-at-200-overwritten-at-3000');
236+
`);
237+
238+
expect(result).toEqual(false);
239+
});
240+
241+
it('should apply fast-forwarded StyleSheetRules that came after stylesheet textContent overwrite', async () => {
242+
await page.evaluate(`events = ${JSON.stringify(styleSheetRuleEvents)}`);
243+
const result = await page.evaluate(`
244+
const { Replayer } = rrweb;
245+
const replayer = new Replayer(events);
246+
replayer.pause(3500);
247+
const rules = [...replayer.iframe.contentDocument.styleSheets].map(
248+
(sheet) => [...sheet.rules],
249+
).flat();
250+
rules.some((x) => x.selectorText === '.css-added-at-3100');
251+
`);
252+
253+
expect(result).toEqual(true);
254+
});
255+
226256
it('can fast-forward mutation events containing nested iframe elements', async () => {
227257
await page.evaluate(`
228258
events = ${JSON.stringify(iframeEvents)};

0 commit comments

Comments
 (0)