Skip to content

Commit 456657c

Browse files
authored
Merge pull request #66 from pendo-io/jc-fix-nested-css-rules
fix: get correct index positions in nested CSS rules for StyleDeclara…
2 parents 9e0fa41 + ab0f2c3 commit 456657c

File tree

3 files changed

+102
-4
lines changed

3 files changed

+102
-4
lines changed

packages/rrweb/src/record/observer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ function getNestedCSSRulePositions(rule: CSSRule): number[] {
580580
);
581581
const index = rules.indexOf(childRule);
582582
pos.unshift(index);
583+
return recurse(childRule.parentRule, pos);
583584
} else if (childRule.parentStyleSheet) {
584585
const rules = Array.from(childRule.parentStyleSheet.cssRules);
585586
const index = rules.indexOf(childRule);

packages/rrweb/src/utils.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,10 +449,7 @@ export function getNestedRule(
449449
if (position.length === 1) {
450450
return rule;
451451
} else {
452-
return getNestedRule(
453-
(rule.cssRules[position[1]] as CSSGroupingRule).cssRules,
454-
position.slice(2),
455-
);
452+
return getNestedRule(rule.cssRules, position.slice(1));
456453
}
457454
}
458455

packages/rrweb/test/util.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
inDom,
88
shadowHostInDom,
99
getShadowHost,
10+
getNestedRule,
1011
} from '../src/utils';
1112

1213
describe('Utilities for other modules', () => {
@@ -143,4 +144,103 @@ describe('Utilities for other modules', () => {
143144
expect(inDom(a.childNodes[0])).toBeTruthy();
144145
});
145146
});
147+
148+
describe('getNestedRule()', () => {
149+
let styleElement: HTMLStyleElement;
150+
let stylesheet: CSSStyleSheet;
151+
152+
beforeEach(() => {
153+
// Create a style element with nested CSS rules for testing
154+
styleElement = document.createElement('style');
155+
document.head.appendChild(styleElement);
156+
stylesheet = styleElement.sheet as CSSStyleSheet;
157+
});
158+
159+
afterEach(() => {
160+
document.head.removeChild(styleElement);
161+
});
162+
163+
it('should return a top-level rule with single index [N]', () => {
164+
stylesheet.insertRule('.rule0 { color: red; }', 0);
165+
stylesheet.insertRule('.rule1 { color: blue; }', 1);
166+
stylesheet.insertRule('.rule2 { color: green; }', 2);
167+
168+
const rule0 = getNestedRule(stylesheet.cssRules, [0]);
169+
expect((rule0 as CSSStyleRule).selectorText).toBe('.rule0');
170+
171+
const rule1 = getNestedRule(stylesheet.cssRules, [1]);
172+
expect((rule1 as CSSStyleRule).selectorText).toBe('.rule1');
173+
174+
const rule2 = getNestedRule(stylesheet.cssRules, [2]);
175+
expect((rule2 as CSSStyleRule).selectorText).toBe('.rule2');
176+
});
177+
178+
it('should return a rule nested inside @media with index [0, N]', () => {
179+
// Insert @media rule with nested rules
180+
stylesheet.insertRule(
181+
'@media (min-width: 100px) { .rule0 { color: red; } .rule1 { color: blue; } .rule2 { color: green; } }',
182+
0,
183+
);
184+
185+
const mediaRule = stylesheet.cssRules[0] as CSSMediaRule;
186+
expect(mediaRule.cssRules.length).toBe(3);
187+
188+
// Access nested rules using [0, N] where 0 is the @media index
189+
const nestedRule0 = getNestedRule(stylesheet.cssRules, [0, 0]);
190+
expect((nestedRule0 as CSSStyleRule).selectorText).toBe('.rule0');
191+
192+
const nestedRule1 = getNestedRule(stylesheet.cssRules, [0, 1]);
193+
expect((nestedRule1 as CSSStyleRule).selectorText).toBe('.rule1');
194+
195+
const nestedRule2 = getNestedRule(stylesheet.cssRules, [0, 2]);
196+
expect((nestedRule2 as CSSStyleRule).selectorText).toBe('.rule2');
197+
});
198+
199+
it('should return a rule nested inside @supports with index [0, N]', () => {
200+
// Insert @supports rule with nested rules
201+
stylesheet.insertRule(
202+
'@supports (display: grid) { .grid-rule { display: grid; } }',
203+
0,
204+
);
205+
206+
const supportsRule = stylesheet.cssRules[0] as CSSSupportsRule;
207+
expect(supportsRule.cssRules.length).toBe(1);
208+
209+
const nestedRule = getNestedRule(stylesheet.cssRules, [0, 0]);
210+
expect((nestedRule as CSSStyleRule).selectorText).toBe('.grid-rule');
211+
});
212+
213+
it('should return a doubly nested rule with index [0, 0, N]', () => {
214+
// Insert @media containing @supports containing a rule
215+
stylesheet.insertRule(
216+
'@media (min-width: 100px) { @supports (display: grid) { .nested-rule { color: red; } } }',
217+
0,
218+
);
219+
220+
const mediaRule = stylesheet.cssRules[0] as CSSMediaRule;
221+
const supportsRule = mediaRule.cssRules[0] as CSSSupportsRule;
222+
expect(supportsRule.cssRules.length).toBe(1);
223+
224+
// Access doubly nested rule using [0, 0, 0]
225+
const nestedRule = getNestedRule(stylesheet.cssRules, [0, 0, 0]);
226+
expect((nestedRule as CSSStyleRule).selectorText).toBe('.nested-rule');
227+
});
228+
229+
it('should handle @media at different indices in stylesheet', () => {
230+
// Insert some top-level rules first
231+
stylesheet.insertRule('.top-level { color: black; }', 0);
232+
stylesheet.insertRule(
233+
'@media (min-width: 100px) { .inside-media { color: red; } }',
234+
1,
235+
);
236+
237+
// Top-level rule at index 0
238+
const topLevel = getNestedRule(stylesheet.cssRules, [0]);
239+
expect((topLevel as CSSStyleRule).selectorText).toBe('.top-level');
240+
241+
// Nested rule: @media at index 1, rule at index 0 inside
242+
const insideMedia = getNestedRule(stylesheet.cssRules, [1, 0]);
243+
expect((insideMedia as CSSStyleRule).selectorText).toBe('.inside-media');
244+
});
245+
});
146246
});

0 commit comments

Comments
 (0)