From ffe6981c5eab69dc9874ec6f67d690728a18e633 Mon Sep 17 00:00:00 2001 From: godky Date: Sun, 7 Sep 2025 01:43:32 +0800 Subject: [PATCH 1/4] fix(rrweb-snapshot): absolutifyURLs function calculation error when processing inline style sheets in hash mode spa --- packages/rrweb-snapshot/src/utils.ts | 14 +- packages/rrweb-snapshot/test/snapshot.test.ts | 172 +++++++++--------- 2 files changed, 88 insertions(+), 98 deletions(-) diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts index 418ce8230a..29e7deafa6 100644 --- a/packages/rrweb-snapshot/src/utils.ts +++ b/packages/rrweb-snapshot/src/utils.ts @@ -429,19 +429,7 @@ export function absolutifyURLs(cssText: string | null, href: string): string { extractOrigin(href) + filePath }${maybeQuote})`; } - const stack = href.split('/'); - const parts = filePath.split('/'); - stack.pop(); - for (const part of parts) { - if (part === '.') { - continue; - } else if (part === '..') { - stack.pop(); - } else { - stack.push(part); - } - } - return `url(${maybeQuote}${stack.join('/')}${maybeQuote})`; + return `url(${maybeQuote}${new URL(filePath, href).toString()}${maybeQuote})`; }, ); } diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts index 5778eb0aff..df982508ad 100644 --- a/packages/rrweb-snapshot/test/snapshot.test.ts +++ b/packages/rrweb-snapshot/test/snapshot.test.ts @@ -28,103 +28,105 @@ const serializeNode = (node: Node): serializedNodeWithId | null => { }; describe('absolute url to stylesheet', () => { - const href = 'http://localhost/css/style.css'; - - it('can handle relative path', () => { - expect(absolutifyURLs('url(a.jpg)', href)).toEqual( - `url(http://localhost/css/a.jpg)`, - ); - }); + const styleSheetHref = 'http://localhost/css/style.css'; + const inlineStyleHref = 'http://localhost/css/#/spa-route'; // inline style fallback to node.baseURL + [styleSheetHref, inlineStyleHref].forEach((href) => { + it('can handle relative path', () => { + expect(absolutifyURLs('url(a.jpg)', href)).toEqual( + `url(http://localhost/css/a.jpg)`, + ); + }); - it('can handle same level path', () => { - expect(absolutifyURLs('url("./a.jpg")', href)).toEqual( - `url("http://localhost/css/a.jpg")`, - ); - }); + it('can handle same level path', () => { + expect(absolutifyURLs('url("./a.jpg")', href)).toEqual( + `url("http://localhost/css/a.jpg")`, + ); + }); - it('can handle parent level path', () => { - expect(absolutifyURLs('url("../a.jpg")', href)).toEqual( - `url("http://localhost/a.jpg")`, - ); - }); + it('can handle parent level path', () => { + expect(absolutifyURLs('url("../a.jpg")', href)).toEqual( + `url("http://localhost/a.jpg")`, + ); + }); - it('can handle absolute path', () => { - expect(absolutifyURLs('url("/a.jpg")', href)).toEqual( - `url("http://localhost/a.jpg")`, - ); - }); + it('can handle absolute path', () => { + expect(absolutifyURLs('url("/a.jpg")', href)).toEqual( + `url("http://localhost/a.jpg")`, + ); + }); - it('can handle external path', () => { - expect(absolutifyURLs('url("http://localhost/a.jpg")', href)).toEqual( - `url("http://localhost/a.jpg")`, - ); - }); + it('can handle external path', () => { + expect(absolutifyURLs('url("http://localhost/a.jpg")', href)).toEqual( + `url("http://localhost/a.jpg")`, + ); + }); - it('can handle single quote path', () => { - expect(absolutifyURLs(`url('./a.jpg')`, href)).toEqual( - `url('http://localhost/css/a.jpg')`, - ); - }); + it('can handle single quote path', () => { + expect(absolutifyURLs(`url('./a.jpg')`, href)).toEqual( + `url('http://localhost/css/a.jpg')`, + ); + }); - it('can handle no quote path', () => { - expect(absolutifyURLs('url(./a.jpg)', href)).toEqual( - `url(http://localhost/css/a.jpg)`, - ); - }); + it('can handle no quote path', () => { + expect(absolutifyURLs('url(./a.jpg)', href)).toEqual( + `url(http://localhost/css/a.jpg)`, + ); + }); - it('can handle multiple no quote paths', () => { - expect( - absolutifyURLs( - 'background-image: url(images/b.jpg);background: #aabbcc url(images/a.jpg) 50% 50% repeat;', - href, - ), - ).toEqual( - `background-image: url(http://localhost/css/images/b.jpg);` + - `background: #aabbcc url(http://localhost/css/images/a.jpg) 50% 50% repeat;`, - ); - }); + it('can handle multiple no quote paths', () => { + expect( + absolutifyURLs( + 'background-image: url(images/b.jpg);background: #aabbcc url(images/a.jpg) 50% 50% repeat;', + href, + ), + ).toEqual( + `background-image: url(http://localhost/css/images/b.jpg);` + + `background: #aabbcc url(http://localhost/css/images/a.jpg) 50% 50% repeat;`, + ); + }); - it('can handle data url image', () => { - expect(absolutifyURLs('url(data:image/gif;base64,ABC)', href)).toEqual( - 'url(data:image/gif;base64,ABC)', - ); - expect( - absolutifyURLs( - 'url(data:application/font-woff;base64,d09GMgABAAAAAAm)', - href, - ), - ).toEqual('url(data:application/font-woff;base64,d09GMgABAAAAAAm)'); - }); + it('can handle data url image', () => { + expect(absolutifyURLs('url(data:image/gif;base64,ABC)', href)).toEqual( + 'url(data:image/gif;base64,ABC)', + ); + expect( + absolutifyURLs( + 'url(data:application/font-woff;base64,d09GMgABAAAAAAm)', + href, + ), + ).toEqual('url(data:application/font-woff;base64,d09GMgABAAAAAAm)'); + }); - it('preserves quotes around inline svgs with spaces', () => { - expect( - absolutifyURLs( + it('preserves quotes around inline svgs with spaces', () => { + expect( + 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, + ), + ).toEqual( "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, - ), - ).toEqual( - "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( - absolutifyURLs( + ); + expect( + absolutifyURLs( + 'url(\'data:image/svg+xml;utf8,\')', + href, + ), + ).toEqual( 'url(\'data:image/svg+xml;utf8,\')', - href, - ), - ).toEqual( - 'url(\'data:image/svg+xml;utf8,\')', - ); - expect( - absolutifyURLs( + ); + expect( + absolutifyURLs( + 'url("data:image/svg+xml;utf8,")', + href, + ), + ).toEqual( 'url("data:image/svg+xml;utf8,")', - href, - ), - ).toEqual( - 'url("data:image/svg+xml;utf8,")', - ); - }); - it('can handle empty path', () => { - expect(absolutifyURLs(`url('')`, href)).toEqual(`url('')`); - }); + ); + }); + it('can handle empty path', () => { + expect(absolutifyURLs(`url('')`, href)).toEqual(`url('')`); + }); + }) }); describe('isBlockedElement()', () => { From ef293d00bb9abe0e6a3eefc59e068318508fd7cf Mon Sep 17 00:00:00 2001 From: godky Date: Sun, 7 Sep 2025 02:06:25 +0800 Subject: [PATCH 2/4] chore(rrweb-snapshot): code style --- packages/rrweb-snapshot/src/utils.ts | 5 ++++- packages/rrweb-snapshot/test/snapshot.test.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts index 29e7deafa6..02fdb89b67 100644 --- a/packages/rrweb-snapshot/src/utils.ts +++ b/packages/rrweb-snapshot/src/utils.ts @@ -429,7 +429,10 @@ export function absolutifyURLs(cssText: string | null, href: string): string { extractOrigin(href) + filePath }${maybeQuote})`; } - return `url(${maybeQuote}${new URL(filePath, href).toString()}${maybeQuote})`; + return `url(${maybeQuote}${new URL( + filePath, + href, + ).toString()}${maybeQuote})`; }, ); } diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts index df982508ad..ce31beb951 100644 --- a/packages/rrweb-snapshot/test/snapshot.test.ts +++ b/packages/rrweb-snapshot/test/snapshot.test.ts @@ -126,7 +126,7 @@ describe('absolute url to stylesheet', () => { it('can handle empty path', () => { expect(absolutifyURLs(`url('')`, href)).toEqual(`url('')`); }); - }) + }); }); describe('isBlockedElement()', () => { From 71c4c0ac9092f4ef7cb49fdfc97aacbbcd360bd1 Mon Sep 17 00:00:00 2001 From: godky Date: Wed, 17 Sep 2025 22:21:16 +0800 Subject: [PATCH 3/4] perf(rrweb-snapshot): fallback to the old method for performance --- packages/rrweb-snapshot/src/utils.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts index 02fdb89b67..7911bd5431 100644 --- a/packages/rrweb-snapshot/src/utils.ts +++ b/packages/rrweb-snapshot/src/utils.ts @@ -429,10 +429,19 @@ export function absolutifyURLs(cssText: string | null, href: string): string { extractOrigin(href) + filePath }${maybeQuote})`; } - return `url(${maybeQuote}${new URL( - filePath, - href, - ).toString()}${maybeQuote})`; + const stack = href.split('#')[0].split('/'); + const parts = filePath.split('/'); + stack.pop(); + for (const part of parts) { + if (part === '.') { + continue; + } else if (part === '..') { + stack.pop(); + } else { + stack.push(part); + } + } + return `url(${maybeQuote}${stack.join('/')}${maybeQuote})`; }, ); } From 2f0489ec3173b8cec8fa45a77bfcb83c004d3816 Mon Sep 17 00:00:00 2001 From: godky Date: Wed, 17 Sep 2025 22:38:53 +0800 Subject: [PATCH 4/4] chore(rrweb-snapshot): add changeset --- .changeset/few-trees-jog.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/few-trees-jog.md diff --git a/.changeset/few-trees-jog.md b/.changeset/few-trees-jog.md new file mode 100644 index 0000000000..df41c0bf18 --- /dev/null +++ b/.changeset/few-trees-jog.md @@ -0,0 +1,5 @@ +--- +"rrweb-snapshot": patch +--- + +fix absolutifyURLs function calculation error in some scenario