Skip to content

Commit 0580214

Browse files
authored
adopt a couple of rrweb proposed PRs (#82)
rrweb-io/rrweb#1686 rrweb-io/rrweb#1700
1 parent be1a665 commit 0580214

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

packages/rrweb-snapshot/src/utils.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,13 @@ export function stringifyStylesheet(s: CSSStyleSheet): string | null {
108108
if (!rules) {
109109
return null;
110110
}
111+
let sheetHref = s.href;
112+
if (!sheetHref && s.ownerNode && s.ownerNode.ownerDocument) {
113+
// an inline <style> element
114+
sheetHref = s.ownerNode.ownerDocument.baseURI;
115+
}
111116
const stringifiedRules = Array.from(rules, (rule: CSSRule) =>
112-
stringifyRule(rule, s.href),
117+
stringifyRule(rule, sheetHref),
113118
).join('');
114119
return fixBrowserCompatibilityIssuesInCSS(stringifiedRules);
115120
} catch (error) {
@@ -130,9 +135,17 @@ export function stringifyRule(rule: CSSRule, sheetHref: string | null): string {
130135
} catch (error) {
131136
importStringified = rule.cssText;
132137
}
133-
if (rule.styleSheet.href) {
134-
// url()s within the imported stylesheet are relative to _that_ sheet's href
135-
return absolutifyURLs(importStringified, rule.styleSheet.href);
138+
// if importStringified is not null,
139+
// there should be a stylesheet and a rule here,
140+
// but we avoid errors in this method by checking for null
141+
// see https://github.com/rrweb-io/rrweb/pull/1686
142+
try {
143+
if (importStringified && rule.styleSheet?.href) {
144+
// url()s within the imported stylesheet are relative to _that_ sheet's href
145+
return absolutifyURLs(importStringified, rule.styleSheet.href);
146+
}
147+
} catch {
148+
// swallow this, we'll return null
136149
}
137150
return importStringified;
138151
} else {

packages/rrweb-snapshot/test/utils.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
extractFileExtension,
88
fixSafariColons,
99
isNodeMetaEqual,
10+
stringifyStylesheet,
1011
} from '../src/utils';
1112
import { NodeType } from '@posthog/rrweb-types';
1213
import type {
@@ -283,4 +284,53 @@ describe('utils', () => {
283284
expect(out3).toEqual('[data-aa\\:other] { color: red; }');
284285
});
285286
});
287+
288+
describe('stringifyStylesheet', () => {
289+
it('returns null if rules are missing', () => {
290+
const mockSheet = {
291+
rules: null,
292+
cssRules: null,
293+
} as unknown as CSSStyleSheet;
294+
expect(stringifyStylesheet(mockSheet)).toBeNull();
295+
});
296+
297+
it('stringifies rules using .cssRules if .rules is missing', () => {
298+
const mockRule1 = { cssText: 'div { margin: 0; }' } as CSSRule;
299+
const mockSheet = {
300+
cssRules: [mockRule1],
301+
href: 'https://example.com/main.css',
302+
} as unknown as CSSStyleSheet;
303+
expect(stringifyStylesheet(mockSheet)).toBe('div { margin: 0; }');
304+
});
305+
306+
it('uses ownerNode.ownerDocument.baseURI for inline styles', () => {
307+
const mockFontFaceRule = {
308+
cssText: `
309+
@font-face {
310+
font-family: 'MockFont';
311+
src: url('../fonts/mockfont.woff2') format('woff2');
312+
font-weight: normal;
313+
font-style: normal;
314+
}
315+
`,
316+
} as CSSRule;
317+
const mockOwnerDocument = {
318+
location: { href: 'https://example.com/page.html' },
319+
baseURI: 'https://example.com/fonts/',
320+
} as unknown as Document;
321+
const mockOwnerNode = {
322+
ownerDocument: mockOwnerDocument,
323+
} as unknown as Node;
324+
const mockSheet = {
325+
cssRules: [mockFontFaceRule],
326+
href: null,
327+
ownerNode: mockOwnerNode,
328+
} as unknown as CSSStyleSheet;
329+
expect(
330+
stringifyStylesheet(mockSheet)?.replace(/\s+/g, ' ').trim(),
331+
).toEqual(
332+
"@font-face { font-family: 'MockFont'; src: url('https://example.com/fonts/mockfont.woff2') format('woff2'); font-weight: normal; font-style: normal; }",
333+
);
334+
});
335+
});
286336
});

0 commit comments

Comments
 (0)