From 6bc30f89b4d984a93179aacad8befdbdf18dc654 Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Mon, 9 Jan 2023 20:14:05 +1100 Subject: [PATCH 1/6] fix: adoptedStyleSheets in shadow doms are missed when a full snapshot is checked out after recording has started --- packages/rrweb/src/record/index.ts | 2 + .../test/__snapshots__/record.test.ts.snap | 205 ++++++++++++++++++ packages/rrweb/test/record.test.ts | 45 +++- 3 files changed, 244 insertions(+), 8 deletions(-) diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 6228d54217..07319d4aca 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -335,6 +335,8 @@ function record( // When we take a full snapshot, old tracked StyleSheets need to be removed. stylesheetManager.reset(); + // Old tracked shadow doms need to be cleared. + shadowDomManager.reset(); mutationBuffers.forEach((buf) => buf.lock()); // don't allow any mirror modifications during snapshotting const node = snapshot(document, { diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap index 06964f5eff..c30c791bcb 100644 --- a/packages/rrweb/test/__snapshots__/record.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap @@ -1059,6 +1059,211 @@ exports[`record captures adopted stylesheets in shadow doms and iframe 1`] = ` ]" `; +exports[`record captures adopted stylesheets of shadow doms in checkout full snapshot 1`] = ` +"[ + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"id\\": \\"shadow-host-1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"entry\\", + \\"id\\": 8 + } + ], + \\"id\\": 7, + \\"isShadowHost\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 15, + \\"id\\": 7, + \\"styleIds\\": [ + 1 + ], + \\"styles\\": [ + { + \\"styleId\\": 1, + \\"rules\\": [ + { + \\"rule\\": \\"h1 { color: blue; }\\", + \\"index\\": 0 + } + ] + } + ] + } + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"id\\": \\"shadow-host-1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"entry\\", + \\"id\\": 8 + } + ], + \\"id\\": 7, + \\"isShadowHost\\": true + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 15, + \\"id\\": 7, + \\"styleIds\\": [ + 1 + ], + \\"styles\\": [ + { + \\"styleId\\": 1, + \\"rules\\": [ + { + \\"rule\\": \\"h1 { color: blue; }\\", + \\"index\\": 0 + } + ] + } + ] + } + } +]" +`; + exports[`record captures inserted style text nodes correctly 1`] = ` "[ { diff --git a/packages/rrweb/test/record.test.ts b/packages/rrweb/test/record.test.ts index 215067d2da..a146dc0c4f 100644 --- a/packages/rrweb/test/record.test.ts +++ b/packages/rrweb/test/record.test.ts @@ -29,9 +29,12 @@ interface ISuite { interface IWindow extends Window { rrweb: { - record: ( + record: (( options: recordOptions, - ) => listenerHandler | undefined; + ) => listenerHandler | undefined) & { + takeFullSnapshot: (isCheckout?: boolean | undefined) => void; + }; + addCustomEvent(tag: string, payload: T): void; }; emit: (e: eventWithTime) => undefined; @@ -491,9 +494,9 @@ describe('record', function (this: ISuite) { iframe!.contentDocument!.adoptedStyleSheets = [sheet2]; iframe!.contentDocument!.body.innerHTML = '

h1 in iframe

'; - const { record } = ((window as unknown) as IWindow).rrweb; - record({ - emit: ((window as unknown) as IWindow).emit, + const { rrweb, emit } = (window as unknown) as IWindow; + rrweb.record({ + emit, }); setTimeout(() => { @@ -565,9 +568,9 @@ describe('record', function (this: ISuite) { sheet2.replaceSync!('div {font-size: large;}'); shadowHost.shadowRoot!.adoptedStyleSheets = [sheet2]; - const { record } = ((window as unknown) as IWindow).rrweb; - record({ - emit: ((window as unknown) as IWindow).emit, + const { rrweb, emit } = (window as unknown) as IWindow; + rrweb.record({ + emit, }); setTimeout(() => { @@ -586,6 +589,32 @@ describe('record', function (this: ISuite) { assertSnapshot(ctx.events); }); + it('captures adopted stylesheets of shadow doms in checkout full snapshot', async () => { + await ctx.page.evaluate(() => { + document.body.innerHTML = ` +
entry
+ `; + + let shadowHost = document.querySelector('div')!; + shadowHost!.attachShadow({ mode: 'open' }); + const sheet = new CSSStyleSheet(); + sheet.replaceSync!('h1 {color: blue;}'); + shadowHost.shadowRoot!.adoptedStyleSheets = [sheet]; + + const { rrweb, emit } = (window as unknown) as IWindow; + rrweb.record({ + emit, + }); + + setTimeout(() => { + // When a full snapshot is checked out manually, all adoptedStylesheets should also be captured. + rrweb.record.takeFullSnapshot(true); + }, 10); + }); + await ctx.page.waitForTimeout(200); + assertSnapshot(ctx.events); + }); + it('captures stylesheets in iframes with `blob:` url', async () => { await ctx.page.evaluate(() => { const iframe = document.createElement('iframe'); From aecb7cfdfaf4dfb04aab85206983d424e4087741 Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Mon, 9 Jan 2023 21:10:39 +1100 Subject: [PATCH 2/6] fix: avoid removing monkey patch of all existed shadow doms when take a new full snapshot --- packages/rrweb/src/record/index.ts | 4 ++-- packages/rrweb/src/record/shadow-dom-manager.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 07319d4aca..779177967f 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -335,8 +335,8 @@ function record( // When we take a full snapshot, old tracked StyleSheets need to be removed. stylesheetManager.reset(); - // Old tracked shadow doms need to be cleared. - shadowDomManager.reset(); + // Old shadow doms cache need to be cleared. + shadowDomManager.clearCache(); mutationBuffers.forEach((buf) => buf.lock()); // don't allow any mirror modifications during snapshotting const node = snapshot(document, { diff --git a/packages/rrweb/src/record/shadow-dom-manager.ts b/packages/rrweb/src/record/shadow-dom-manager.ts index 807f6220c9..545127dde6 100644 --- a/packages/rrweb/src/record/shadow-dom-manager.ts +++ b/packages/rrweb/src/record/shadow-dom-manager.ts @@ -129,8 +129,12 @@ export class ShadowDomManager { } } + public clearCache(){ + this.shadowDoms = new WeakSet(); + } + public reset() { this.restorePatches.forEach((restorePatch) => restorePatch()); - this.shadowDoms = new WeakSet(); + this.clearCache(); } } From c4dc23715e8f3f42e930028708d3f4557f7fc247 Mon Sep 17 00:00:00 2001 From: Mark-Fenng Date: Mon, 9 Jan 2023 10:11:51 +0000 Subject: [PATCH 3/6] Apply formatting changes --- packages/rrweb/src/record/shadow-dom-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rrweb/src/record/shadow-dom-manager.ts b/packages/rrweb/src/record/shadow-dom-manager.ts index 545127dde6..a03a0c4232 100644 --- a/packages/rrweb/src/record/shadow-dom-manager.ts +++ b/packages/rrweb/src/record/shadow-dom-manager.ts @@ -129,7 +129,7 @@ export class ShadowDomManager { } } - public clearCache(){ + public clearCache() { this.shadowDoms = new WeakSet(); } From 0074c102d6d3ef7497a42fd3ab2ffc7470fdc6aa Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Mon, 9 Jan 2023 18:13:15 +0800 Subject: [PATCH 4/6] Update packages/rrweb/test/record.test.ts Co-authored-by: Justin Halsall --- packages/rrweb/test/record.test.ts | 37 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/rrweb/test/record.test.ts b/packages/rrweb/test/record.test.ts index a146dc0c4f..2bbe5af55e 100644 --- a/packages/rrweb/test/record.test.ts +++ b/packages/rrweb/test/record.test.ts @@ -591,27 +591,30 @@ describe('record', function (this: ISuite) { it('captures adopted stylesheets of shadow doms in checkout full snapshot', async () => { await ctx.page.evaluate(() => { - document.body.innerHTML = ` -
entry
- `; + return new Promise((resolve) => { + document.body.innerHTML = ` +
entry
+ `; - let shadowHost = document.querySelector('div')!; - shadowHost!.attachShadow({ mode: 'open' }); - const sheet = new CSSStyleSheet(); - sheet.replaceSync!('h1 {color: blue;}'); - shadowHost.shadowRoot!.adoptedStyleSheets = [sheet]; + let shadowHost = document.querySelector('div')!; + shadowHost!.attachShadow({ mode: 'open' }); + const sheet = new CSSStyleSheet(); + sheet.replaceSync!('h1 {color: blue;}'); + shadowHost.shadowRoot!.adoptedStyleSheets = [sheet]; - const { rrweb, emit } = (window as unknown) as IWindow; - rrweb.record({ - emit, - }); + const { rrweb, emit } = (window as unknown) as IWindow; + rrweb.record({ + emit, + }); - setTimeout(() => { - // When a full snapshot is checked out manually, all adoptedStylesheets should also be captured. - rrweb.record.takeFullSnapshot(true); - }, 10); + setTimeout(() => { + // When a full snapshot is checked out manually, all adoptedStylesheets should also be captured. + rrweb.record.takeFullSnapshot(true); + resolve(undefined); + }, 10); + }); }); - await ctx.page.waitForTimeout(200); + await waitForRAF(page); assertSnapshot(ctx.events); }); From adbec5ba52dcbf82d6fdccfec221d9e2c1f83df4 Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Mon, 9 Jan 2023 21:21:56 +1100 Subject: [PATCH 5/6] fix typo --- packages/rrweb/test/record.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rrweb/test/record.test.ts b/packages/rrweb/test/record.test.ts index 2bbe5af55e..46ad5e5c7e 100644 --- a/packages/rrweb/test/record.test.ts +++ b/packages/rrweb/test/record.test.ts @@ -614,7 +614,7 @@ describe('record', function (this: ISuite) { }, 10); }); }); - await waitForRAF(page); + await waitForRAF(ctx.page); assertSnapshot(ctx.events); }); From 81dc458375bd6b05d66b02e7a1ca929e9d2787d8 Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Mon, 9 Jan 2023 21:30:43 +1100 Subject: [PATCH 6/6] update outdated snapshot --- packages/rrweb/test/__snapshots__/record.test.ts.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap index c30c791bcb..ad9b438600 100644 --- a/packages/rrweb/test/__snapshots__/record.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap @@ -1101,7 +1101,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 6 }, { @@ -1122,7 +1122,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 9 } ], @@ -1201,7 +1201,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 6 }, { @@ -1222,7 +1222,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", + \\"textContent\\": \\"\\\\n \\", \\"id\\": 9 } ],