From f26eacf06c64365d33a3fd41709e56731935727c Mon Sep 17 00:00:00 2001 From: Jeff Nguyen Date: Tue, 16 Jul 2024 12:43:20 -0400 Subject: [PATCH] Rename `absoluteToStylesheet` to `absolutifyURLs` and call it once after stringifying imported stylesheet --- packages/rrweb-snapshot/src/snapshot.ts | 6 ++-- packages/rrweb-snapshot/src/utils.ts | 21 +++++++----- packages/rrweb-snapshot/test/snapshot.test.ts | 34 +++++++++---------- tsconfig.base.json | 2 +- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts index 44f2a2963b..7ceea14096 100644 --- a/packages/rrweb-snapshot/src/snapshot.ts +++ b/packages/rrweb-snapshot/src/snapshot.ts @@ -25,7 +25,7 @@ import { getInputType, toLowerCase, extractFileExtension, - absoluteToStylesheet, + absolutifyURLs, } from './utils'; let _id = 1; @@ -193,7 +193,7 @@ export function transformAttribute( } else if (name === 'srcset') { return getAbsoluteSrcsetString(doc, value); } else if (name === 'style') { - return absoluteToStylesheet(value, getHref(doc)); + return absolutifyURLs(value, getHref(doc)); } else if (tagName === 'object' && name === 'data') { return absoluteToDoc(doc, value); } @@ -523,7 +523,7 @@ function serializeTextNode( n, ); } - textContent = absoluteToStylesheet(textContent, getHref(options.doc)); + textContent = absolutifyURLs(textContent, getHref(options.doc)); } if (isScript) { textContent = 'SCRIPT_PLACEHOLDER'; diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts index 6b7700e2a4..408d913a6f 100644 --- a/packages/rrweb-snapshot/src/utils.ts +++ b/packages/rrweb-snapshot/src/utils.ts @@ -96,10 +96,12 @@ export function escapeImportStatement(rule: CSSImportRule): string { export function stringifyStylesheet(s: CSSStyleSheet): string | null { try { const rules = s.rules || s.cssRules; - const stringifiedRules = Array.from(rules, stringifyRule) - .map((rule) => { - return s.href ? absoluteToStylesheet(rule, s.href) : rule; - }) + // const stringifiedRules = []; + // for (let i = 0; i < rules.length; i++) { + // stringifiedRules.push(stringifyRule(rules[i], s.href)); + // } + const stringifiedRules = [...rules] + .map((rule: CSSRule) => stringifyRule(rule, s.href)) .join(''); return rules ? fixBrowserCompatibilityIssuesInCSS(stringifiedRules) : null; @@ -108,7 +110,7 @@ export function stringifyStylesheet(s: CSSStyleSheet): string | null { } } -export function stringifyRule(rule: CSSRule): string { +export function stringifyRule(rule: CSSRule, sheetHref: string | null): string { let importStringified; if (isCSSImportRule(rule)) { try { @@ -118,6 +120,10 @@ export function stringifyRule(rule: CSSRule): string { stringifyStylesheet(rule.styleSheet) || // work around browser issues with the raw string `@import url(...)` statement escapeImportStatement(rule); + + if (sheetHref) { + importStringified = absolutifyURLs(importStringified, sheetHref); + } } catch (error) { // ignore } @@ -369,10 +375,7 @@ const URL_IN_CSS_REF = /url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm; const URL_PROTOCOL_MATCH = /^(?:[a-z+]+:)?\/\//i; const URL_WWW_MATCH = /^www\..*/i; const DATA_URI = /^(data:)([^,]*),(.*)/i; -export function absoluteToStylesheet( - cssText: string | null, - href: string, -): string { +export function absolutifyURLs(cssText: string | null, href: string): string { return (cssText || '').replace( URL_IN_CSS_REF, ( diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts index bfab523940..7bf6141e44 100644 --- a/packages/rrweb-snapshot/test/snapshot.test.ts +++ b/packages/rrweb-snapshot/test/snapshot.test.ts @@ -6,56 +6,56 @@ import { describe, it, expect } from 'vitest'; import { serializeNodeWithId, _isBlockedElement } from '../src/snapshot'; import snapshot from '../src/snapshot'; import { serializedNodeWithId, elementNode } from '../src/types'; -import { Mirror, absoluteToStylesheet } from '../src/utils'; +import { Mirror, absolutifyURLs } from '../src/utils'; describe('absolute url to stylesheet', () => { const href = 'http://localhost/css/style.css'; it('can handle relative path', () => { - expect(absoluteToStylesheet('url(a.jpg)', href)).toEqual( + expect(absolutifyURLs('url(a.jpg)', href)).toEqual( `url(http://localhost/css/a.jpg)`, ); }); it('can handle same level path', () => { - expect(absoluteToStylesheet('url("./a.jpg")', href)).toEqual( + expect(absolutifyURLs('url("./a.jpg")', href)).toEqual( `url("http://localhost/css/a.jpg")`, ); }); it('can handle parent level path', () => { - expect(absoluteToStylesheet('url("../a.jpg")', href)).toEqual( + expect(absolutifyURLs('url("../a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle absolute path', () => { - expect(absoluteToStylesheet('url("/a.jpg")', href)).toEqual( + expect(absolutifyURLs('url("/a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle external path', () => { - expect(absoluteToStylesheet('url("http://localhost/a.jpg")', href)).toEqual( + expect(absolutifyURLs('url("http://localhost/a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle single quote path', () => { - expect(absoluteToStylesheet(`url('./a.jpg')`, href)).toEqual( + expect(absolutifyURLs(`url('./a.jpg')`, href)).toEqual( `url('http://localhost/css/a.jpg')`, ); }); it('can handle no quote path', () => { - expect(absoluteToStylesheet('url(./a.jpg)', href)).toEqual( + expect(absolutifyURLs('url(./a.jpg)', href)).toEqual( `url(http://localhost/css/a.jpg)`, ); }); it('can handle multiple no quote paths', () => { expect( - absoluteToStylesheet( + absolutifyURLs( 'background-image: url(images/b.jpg);background: #aabbcc url(images/a.jpg) 50% 50% repeat;', href, ), @@ -66,11 +66,11 @@ describe('absolute url to stylesheet', () => { }); it('can handle data url image', () => { + expect(absolutifyURLs('url(data:image/gif;base64,ABC)', href)).toEqual( + 'url(data:image/gif;base64,ABC)', + ); expect( - absoluteToStylesheet('url(data:image/gif;base64,ABC)', href), - ).toEqual('url(data:image/gif;base64,ABC)'); - expect( - absoluteToStylesheet( + absolutifyURLs( 'url(data:application/font-woff;base64,d09GMgABAAAAAAm)', href, ), @@ -79,7 +79,7 @@ describe('absolute url to stylesheet', () => { it('preserves quotes around inline svgs with spaces', () => { expect( - absoluteToStylesheet( + absolutifyURLs( "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")", href, ), @@ -87,7 +87,7 @@ describe('absolute url to stylesheet', () => { "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")", ); expect( - absoluteToStylesheet( + absolutifyURLs( 'url(\'data:image/svg+xml;utf8,\')', href, ), @@ -95,7 +95,7 @@ describe('absolute url to stylesheet', () => { 'url(\'data:image/svg+xml;utf8,\')', ); expect( - absoluteToStylesheet( + absolutifyURLs( 'url("data:image/svg+xml;utf8,")', href, ), @@ -104,7 +104,7 @@ describe('absolute url to stylesheet', () => { ); }); it('can handle empty path', () => { - expect(absoluteToStylesheet(`url('')`, href)).toEqual(`url('')`); + expect(absolutifyURLs(`url('')`, href)).toEqual(`url('')`); }); }); diff --git a/tsconfig.base.json b/tsconfig.base.json index bcee1c7337..557bfa46ae 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,7 +10,7 @@ "moduleResolution": "Node", "rootDir": "src", "outDir": "dist", - "lib": ["es6", "dom"], + "lib": ["es6", "dom", "dom.iterable"], "sourceMap": true, "skipLibCheck": true, "declaration": true,