Skip to content

Commit ae3f445

Browse files
committed
fix: swallow CSS insertRule parse errors
1 parent 3ae040d commit ae3f445

3 files changed

Lines changed: 90 additions & 2 deletions

File tree

.changeset/wicked-sheep-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"rrweb": patch
3+
---
4+
5+
Swallow insertRule parse errors for vendor-prefixed CSS rules not supported by the current browser.

packages/rrweb/src/record/observer.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,12 @@ function initStyleSheetObserver(
623623
adds: [{ rule, index }],
624624
});
625625
}
626-
return target.apply(thisArg, argumentsList);
626+
try {
627+
return target.apply(thisArg, argumentsList);
628+
} catch (e) {
629+
// Rules valid in one browser may be rejected by another.
630+
// Consume the parse error
631+
}
627632
},
628633
),
629634
});
@@ -809,7 +814,12 @@ function initStyleSheetObserver(
809814
],
810815
});
811816
}
812-
return target.apply(thisArg, argumentsList);
817+
try {
818+
return target.apply(thisArg, argumentsList);
819+
} catch (e) {
820+
// Rules valid in one browser may be rejected by another.
821+
// Consume the parse error
822+
}
813823
},
814824
),
815825
},

packages/rrweb/test/record.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,79 @@ describe('record', function (this: ISuite) {
338338
await assertSnapshot(ctx.events);
339339
});
340340

341+
it('skips vendor-prefixed CSS rules not supported by the current browser', async () => {
342+
// ::-moz-focus-inner is valid in Firefox but rejected by other browsers.
343+
// The insertRule proxy catches any parse error and swallows it, so recording
344+
// continues without throwing. The rule IS still emitted as a StyleSheetRule
345+
// event so it can be replayed in a browser that supports it.
346+
await ctx.page.evaluate(() => {
347+
const { record } = (window as unknown as IWindow).rrweb;
348+
record({
349+
emit: (window as unknown as IWindow).emit,
350+
});
351+
352+
const styleElement = document.createElement('style');
353+
document.head.appendChild(styleElement);
354+
355+
const styleSheet = <CSSStyleSheet>styleElement.sheet;
356+
// insertRule calls must be deferred past initial serialization
357+
setTimeout(() => {
358+
styleSheet.insertRule('.foo::-moz-focus-inner { border-style: none; }');
359+
styleSheet.insertRule('body { color: #fff; }');
360+
}, 0);
361+
});
362+
await ctx.page.waitForTimeout(50);
363+
364+
const styleSheetRuleEvents = ctx.events.filter(
365+
(e) =>
366+
e.type === EventType.IncrementalSnapshot &&
367+
e.data.source === IncrementalSource.StyleSheetRule,
368+
);
369+
const addRules = styleSheetRuleEvents.filter((e) =>
370+
Boolean((e.data as styleSheetRuleData).adds),
371+
);
372+
// Both rules captured as events (for replay in the browser that supports them)
373+
expect(addRules.length).toEqual(2);
374+
expect((addRules[0].data as styleSheetRuleData).adds).toEqual([
375+
{ rule: '.foo::-moz-focus-inner { border-style: none; }' },
376+
]);
377+
expect((addRules[1].data as styleSheetRuleData).adds).toEqual([
378+
{ rule: 'body { color: #fff; }' },
379+
]);
380+
});
381+
382+
it('skips vendor-prefixed CSS rules inside nested rules not supported by the current browser', async () => {
383+
await ctx.page.evaluate(() => {
384+
const { record } = (window as unknown as IWindow).rrweb;
385+
record({
386+
emit: (window as unknown as IWindow).emit,
387+
});
388+
389+
const styleElement = document.createElement('style');
390+
document.head.appendChild(styleElement);
391+
392+
const styleSheet = <CSSStyleSheet>styleElement.sheet;
393+
styleSheet.insertRule('@media {}');
394+
const atMediaRule = styleSheet.cssRules[0] as CSSMediaRule;
395+
396+
setTimeout(() => {
397+
atMediaRule.insertRule('.foo::-moz-focus-inner { border-style: none; }', 0);
398+
atMediaRule.insertRule('body { color: #fff; }', 0);
399+
}, 0);
400+
});
401+
await ctx.page.waitForTimeout(50);
402+
403+
const styleSheetRuleEvents = ctx.events.filter(
404+
(e) =>
405+
e.type === EventType.IncrementalSnapshot &&
406+
e.data.source === IncrementalSource.StyleSheetRule,
407+
);
408+
const addRuleCount = styleSheetRuleEvents.filter((e) =>
409+
Boolean((e.data as styleSheetRuleData).adds),
410+
).length;
411+
expect(addRuleCount).toEqual(2);
412+
});
413+
341414
it('captures stylesheet rules with deprecated addRule & removeRule properties', async () => {
342415
await ctx.page.evaluate(() => {
343416
const { record } = (window as unknown as IWindow).rrweb;

0 commit comments

Comments
 (0)