diff --git a/.changeset/great-cows-camp.md b/.changeset/great-cows-camp.md new file mode 100644 index 0000000000..88508ff6e0 --- /dev/null +++ b/.changeset/great-cows-camp.md @@ -0,0 +1,6 @@ +--- +"@rrweb/record": patch +"rrweb": patch +--- + +Added support for deprecated addRule & removeRule methods diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index ebd5abef7e..ab1f958589 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -661,6 +661,17 @@ function initStyleSheetObserver( ), }); + // Support for deprecated addRule method + win.CSSStyleSheet.prototype.addRule = function ( + this: CSSStyleSheet, + selector: string, + styleBlock: string, + index: number = this.cssRules.length, + ) { + const rule = `${selector} { ${styleBlock} }`; + return win.CSSStyleSheet.prototype.insertRule.apply(this, [rule, index]); + }; + // eslint-disable-next-line @typescript-eslint/unbound-method const deleteRule = win.CSSStyleSheet.prototype.deleteRule; win.CSSStyleSheet.prototype.deleteRule = new Proxy(deleteRule, { @@ -692,6 +703,14 @@ function initStyleSheetObserver( ), }); + // Support for deprecated removeRule method + win.CSSStyleSheet.prototype.removeRule = function ( + this: CSSStyleSheet, + index: number, + ) { + return win.CSSStyleSheet.prototype.deleteRule.apply(this, [index]); + }; + let replace: (text: string) => Promise; if (win.CSSStyleSheet.prototype.replace) { diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap index 2333d8f251..8d540c0e2d 100644 --- a/packages/rrweb/test/__snapshots__/record.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap @@ -2196,6 +2196,146 @@ exports[`record > captures stylesheet rules 1`] = ` ]" `; +exports[`record > captures stylesheet rules with deprecated addRule & removeRule properties 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\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"size\\": \\"40\\" + }, + \\"childNodes\\": [], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", + \\"id\\": 8 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 4, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": { + \\"_cssText\\": \\"body { background: rgb(0, 0, 0); }\\" + }, + \\"childNodes\\": [], + \\"id\\": 9 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #fff; }\\", + \\"index\\": 1 + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"removes\\": [ + { + \\"index\\": 0 + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 8, + \\"id\\": 9, + \\"adds\\": [ + { + \\"rule\\": \\"body { color: #ccc; }\\", + \\"index\\": 1 + } + ] + } + } +]" +`; + exports[`record > captures stylesheets in iframes with \`blob:\` url 1`] = ` "[ { diff --git a/packages/rrweb/test/record.test.ts b/packages/rrweb/test/record.test.ts index 2eb3ef9da1..2c0f07adf7 100644 --- a/packages/rrweb/test/record.test.ts +++ b/packages/rrweb/test/record.test.ts @@ -297,6 +297,7 @@ describe('record', function (this: ISuite) { // begin: pre-serialization const ruleIdx0 = styleSheet.insertRule('body { background: #000; }'); const ruleIdx1 = styleSheet.insertRule('body { background: #111; }'); + styleSheet.deleteRule(ruleIdx1); // end: pre-serialization setTimeout(() => { @@ -328,6 +329,69 @@ describe('record', function (this: ISuite) { rule: 'body { color: #fff; }', }, ]); + expect((addRules[1].data as styleSheetRuleData).adds).toEqual([ + { + rule: 'body { color: #ccc; }', + }, + ]); + expect(removeRuleCount).toEqual(1); + await assertSnapshot(ctx.events); + }); + + it('captures stylesheet rules with deprecated addRule & removeRule properties', async () => { + await ctx.page.evaluate(() => { + const { record } = (window as unknown as IWindow).rrweb; + + record({ + emit: (window as unknown as IWindow).emit, + }); + + const styleElement = document.createElement('style'); + document.head.appendChild(styleElement); + + const styleSheet = styleElement.sheet; + // begin: pre-serialization + const ruleIdx0 = styleSheet.addRule('body', 'background: #000;'); + const ruleIdx1 = styleSheet.addRule('body', 'background: #111;'); + + styleSheet.removeRule(ruleIdx1); + // end: pre-serialization + setTimeout(() => { + styleSheet.addRule('body', 'color: #fff;'); + }, 0); + setTimeout(() => { + styleSheet.removeRule(ruleIdx0); + }, 5); + setTimeout(() => { + styleSheet.addRule('body', 'color: #ccc;'); + }, 10); + }); + await ctx.page.waitForTimeout(50); + const styleSheetRuleEvents = ctx.events.filter( + (e) => + e.type === EventType.IncrementalSnapshot && + e.data.source === IncrementalSource.StyleSheetRule, + ); + const addRules = styleSheetRuleEvents.filter((e) => + Boolean((e.data as styleSheetRuleData).adds), + ); + const removeRuleCount = styleSheetRuleEvents.filter((e) => + Boolean((e.data as styleSheetRuleData).removes), + ).length; + // pre-serialization insert/delete should be ignored + expect(addRules.length).toEqual(2); + expect((addRules[0].data as styleSheetRuleData).adds).toEqual([ + { + index: 1, + rule: 'body { color: #fff; }', + }, + ]); + expect((addRules[1].data as styleSheetRuleData).adds).toEqual([ + { + index: 1, + rule: 'body { color: #ccc; }', + }, + ]); expect(removeRuleCount).toEqual(1); await assertSnapshot(ctx.events); });