diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 6228d54217..779177967f 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 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..a03a0c4232 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(); } } diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap index 06964f5eff..ad9b438600 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..46ad5e5c7e 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,35 @@ describe('record', function (this: ISuite) { assertSnapshot(ctx.events); }); + it('captures adopted stylesheets of shadow doms in checkout full snapshot', async () => { + await ctx.page.evaluate(() => { + 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]; + + 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); + resolve(undefined); + }, 10); + }); + }); + await waitForRAF(ctx.page); + assertSnapshot(ctx.events); + }); + it('captures stylesheets in iframes with `blob:` url', async () => { await ctx.page.evaluate(() => { const iframe = document.createElement('iframe');