From 3d9b037cad47eadb1eaf34fff0ac7473cfe6d20d Mon Sep 17 00:00:00 2001 From: Justin Halsall Date: Fri, 25 Mar 2022 16:06:36 +0100 Subject: [PATCH 1/3] Apply textContent on flush --- packages/rrweb/src/replay/index.ts | 18 ++++++++++++- .../test/__snapshots__/replayer.test.ts.snap | 12 ++++----- .../test/events/style-sheet-rule-events.ts | 26 ++++++++++++++++--- packages/rrweb/test/replayer.test.ts | 15 +++++++++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts index 794a61b34c..1e8d17de17 100644 --- a/packages/rrweb/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -40,6 +40,7 @@ import { mouseMovePos, IWindow, canvasMutationCommand, + textMutation, } from '../types'; import { createMirror, @@ -171,7 +172,7 @@ export class Replayer { this.virtualStyleRulesMap = new Map(); this.emitter.on(ReplayerEvents.Flush, () => { - const { scrollMap, inputMap } = this.treeIndex.flush(); + const { scrollMap, inputMap, mutationData } = this.treeIndex.flush(); this.fragmentParentMap.forEach((parent, frag) => this.restoreRealParent(frag, parent), @@ -190,6 +191,9 @@ export class Replayer { for (const d of inputMap.values()) { this.applyInput(d); } + for (const d of mutationData.texts) { + this.applyText(d, mutationData); + } }); this.emitter.on(ReplayerEvents.PlayBack, () => { this.firstFullSnapshot = null; @@ -1677,6 +1681,18 @@ export class Replayer { } } + private applyText(d: textMutation, mutation: mutationData) { + const target = this.mirror.getNode(d.id); + if (!target) { + return this.debugNodeNotFound(mutation, d.id); + } + try { + ((target as Node) as HTMLElement).textContent = d.value; + } catch (error) { + // for safe + } + } + private legacy_resolveMissingNode( map: missingNodeMap, parent: Node, diff --git a/packages/rrweb/test/__snapshots__/replayer.test.ts.snap b/packages/rrweb/test/__snapshots__/replayer.test.ts.snap index 677562cfae..c471f9d958 100644 --- a/packages/rrweb/test/__snapshots__/replayer.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/replayer.test.ts.snap @@ -56,27 +56,27 @@ html.rrweb-paused *, html.rrweb-paused ::before, html.rrweb-paused ::after { ani file-cid-1 @charset \\"utf-8\\"; -.c011xx { padding: 1.3125rem; flex: 0 0 auto; width: 100%; } +.css-added-at-500 { padding: 1.3125rem; flex: 0 0 auto; width: 100%; } file-cid-2 @charset \\"utf-8\\"; -.c01x { opacity: 1; transform: translateX(0px); } +.css-added-at-200-overwritten-at-3000 { opacity: 1; transform: translateX(0px); } -.css-added-at-400 { border: 1px solid blue; } +.css-added-at-400-overwritten-at-3000 { border: 1px solid blue; } file-cid-3 @charset \\"utf-8\\"; -.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-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-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; } +.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-1000-deleted-at-2500 { display: flex; flex-direction: column; min-width: 60rem; min-height: 100vh; color: blue; } -.css-lsxxx { padding-left: 4rem; } +.css-added-at-200.alt2 { padding-left: 4rem; } " `; diff --git a/packages/rrweb/test/events/style-sheet-rule-events.ts b/packages/rrweb/test/events/style-sheet-rule-events.ts index 0e26673916..77cd004cc8 100644 --- a/packages/rrweb/test/events/style-sheet-rule-events.ts +++ b/packages/rrweb/test/events/style-sheet-rule-events.ts @@ -54,7 +54,7 @@ const events: eventWithTime[] = [ type: 3, isStyle: true, textContent: - '\n.c01x {\n opacity: 1;\n transform: translateX(0);\n}\n', + '\n.css-added-at-200-overwritten-at-3000 {\n opacity: 1;\n transform: translateX(0);\n}\n', }, ], }, @@ -64,7 +64,7 @@ const events: eventWithTime[] = [ tagName: 'style', attributes: { _cssText: - '.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; }', + '.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; }', 'data-emotion': 'css', }, childNodes: [ @@ -111,7 +111,8 @@ const events: eventWithTime[] = [ id: 101, adds: [ { - rule: '.css-added-at-400{border: 1px solid blue;}', + rule: + '.css-added-at-400-overwritten-at-3000 {border: 1px solid blue;}', index: 1, }, ], @@ -141,7 +142,7 @@ const events: eventWithTime[] = [ type: 3, isStyle: true, textContent: - '\n.c011xx {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n', + '\n.css-added-at-500 {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n', }, nextId: null, parentId: 255, @@ -184,6 +185,23 @@ const events: eventWithTime[] = [ type: EventType.IncrementalSnapshot, timestamp: now + 2500, }, + // overwrite all contents of stylesheet + { + data: { + texts: [ + { + id: 102, + value: '.all-css-overwritten-at-3000 { color: indigo; }', + }, + ], + attributes: [], + removes: [], + adds: [], + source: IncrementalSource.Mutation, + }, + type: EventType.IncrementalSnapshot, + timestamp: now + 3000, + }, ]; export default events; diff --git a/packages/rrweb/test/replayer.test.ts b/packages/rrweb/test/replayer.test.ts index cd35fb2a51..f57123a5e5 100644 --- a/packages/rrweb/test/replayer.test.ts +++ b/packages/rrweb/test/replayer.test.ts @@ -223,6 +223,21 @@ describe('replayer', function () { expect(result).toEqual(false); }); + it("should overwrite all StyleSheetRules by replacing style element's textContent while fast-forwarding", async () => { + await page.evaluate(`events = ${JSON.stringify(styleSheetRuleEvents)}`); + const result = await page.evaluate(` + const { Replayer } = rrweb; + const replayer = new Replayer(events); + replayer.pause(3500); + const rules = [...replayer.iframe.contentDocument.styleSheets].map( + (sheet) => [...sheet.rules], + ).flat(); + rules.some((x) => x.selectorText === '.added-at-200-overwritten-at-3000'); + `); + + expect(result).toEqual(false); + }); + it('can fast-forward mutation events containing nested iframe elements', async () => { await page.evaluate(` events = ${JSON.stringify(iframeEvents)}; From 06b7d837fc1b129a091e0e2aff039742ad465bb8 Mon Sep 17 00:00:00 2001 From: Justin Halsall Date: Fri, 25 Mar 2022 16:23:21 +0100 Subject: [PATCH 2/3] fix typo --- packages/rrweb/test/replayer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rrweb/test/replayer.test.ts b/packages/rrweb/test/replayer.test.ts index f57123a5e5..4b69582423 100644 --- a/packages/rrweb/test/replayer.test.ts +++ b/packages/rrweb/test/replayer.test.ts @@ -232,7 +232,7 @@ describe('replayer', function () { const rules = [...replayer.iframe.contentDocument.styleSheets].map( (sheet) => [...sheet.rules], ).flat(); - rules.some((x) => x.selectorText === '.added-at-200-overwritten-at-3000'); + rules.some((x) => x.selectorText === '.css-added-at-200-overwritten-at-3000'); `); expect(result).toEqual(false); From ef54b3a8bce72de634aeb37892ce8b650fd9aa17 Mon Sep 17 00:00:00 2001 From: Justin Halsall Date: Fri, 25 Mar 2022 16:53:45 +0100 Subject: [PATCH 3/3] Style sheet rules applied after