From 015327068085bb3338fbcb4650bda7d68c96c4a1 Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Wed, 1 Apr 2026 12:00:00 +0800 Subject: [PATCH] improve robustness of inlineImages feature (#812) * fix: add inlineImages option to rrrweb recorder and try to make the code more robust * Improve robustness --- packages/rrweb-snapshot/src/rebuild.ts | 9 +++-- packages/rrweb-snapshot/src/snapshot.ts | 39 +++++++++---------- .../rrweb-snapshot/test/integration.test.ts | 18 ++++----- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts index 1719569c..f736ffc2 100644 --- a/packages/rrweb-snapshot/src/rebuild.ts +++ b/packages/rrweb-snapshot/src/rebuild.ts @@ -227,10 +227,13 @@ function buildNode( } }; } else if (tagName === 'img' && name === 'rr_dataURL') { - const image = (node as HTMLImageElement); + const image = node as HTMLImageElement; if (!image.currentSrc.startsWith('data:')) { - // backup original img src - image.setAttribute('data-rrweb-src', image.currentSrc); + // Backup original img src. It may not have been set yet. + image.setAttribute( + 'rrweb-original-src', + n.attributes['src'] as string, + ); image.src = value; } image.removeAttribute(name); diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts index e666eb4a..91f6400a 100644 --- a/packages/rrweb-snapshot/src/snapshot.ts +++ b/packages/rrweb-snapshot/src/snapshot.ts @@ -78,17 +78,6 @@ function extractOrigin(url: string): string { let canvasService: HTMLCanvasElement | null; let canvasCtx: CanvasRenderingContext2D | null; -function initCanvasService(doc: Document) { - if (!canvasService) { - canvasService = doc.createElement('canvas'); - } - if (!canvasCtx) { - canvasCtx = canvasService.getContext('2d'); - } - canvasService.width = 0; - canvasService.height = 0; -} - const URL_IN_CSS_REF = /url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm; const RELATIVE_PATH = /^(?!www\.|(?:http|ftp)s?:\/\/|[A-Za-z]:\\|\/\/|#).*/; const DATA_URI = /^(data:)([^,]*),(.*)/i; @@ -515,14 +504,27 @@ function serializeNode( attributes.rr_dataURL = (n as HTMLCanvasElement).toDataURL(); } // save image offline - if (tagName === 'img' && inlineImages && canvasService && canvasCtx) { - const image = (n as HTMLImageElement); + if (tagName === 'img' && inlineImages) { + if (!canvasService) { + canvasService = doc.createElement('canvas'); + canvasCtx = canvasService.getContext('2d'); + } + const image = n as HTMLImageElement; + const oldValue = image.crossOrigin; image.crossOrigin = 'anonymous'; try { - canvasService.width = image.naturalWidth; - canvasService.height = image.naturalHeight; - canvasCtx.drawImage(image, 0, 0); - attributes.rr_dataURL = canvasService.toDataURL(); + const recordInlineImage = () => { + canvasService!.width = image.naturalWidth; + canvasService!.height = image.naturalHeight; + canvasCtx!.drawImage(image, 0, 0); + attributes.rr_dataURL = canvasService!.toDataURL(); + oldValue + ? (attributes.crossOrigin = oldValue) + : delete attributes.crossOrigin; + }; + // The image content may not have finished loading yet. + if (image.complete && image.naturalWidth !== 0) recordInlineImage(); + else image.onload = recordInlineImage; } catch { // ignore error } @@ -832,9 +834,6 @@ export function serializeNodeWithId( ) { preserveWhiteSpace = false; } - if (inlineImages) { - initCanvasService(doc); - } const bypassOptions = { doc, map, diff --git a/packages/rrweb-snapshot/test/integration.test.ts b/packages/rrweb-snapshot/test/integration.test.ts index 55daa59c..c9aa91b9 100644 --- a/packages/rrweb-snapshot/test/integration.test.ts +++ b/packages/rrweb-snapshot/test/integration.test.ts @@ -194,22 +194,20 @@ iframe.contentDocument.querySelector('center').clientHeight it('correctly saves images offline', async () => { const page: puppeteer.Page = await browser.newPage(); - // console for debug - // tslint:disable-next-line: no-console - page.on('console', (msg) => console.log(msg.text())); - await page.goto('http://localhost:3030/html/picture.html', { waitUntil: 'load' }); + await page.goto('http://localhost:3030/html/picture.html', { + waitUntil: 'load', + }); await page.waitForSelector('img', { timeout: 1000 }); - - const snapshot = await page.evaluate(`${code} - const [snap] = rrweb.snapshot(document, {inlineImages: true, inlineStylesheet: false}); - JSON.stringify(snap, null, 2); + await page.evaluate(`${code}var snapshot = rrweb.snapshot(document, {inlineImages: true, inlineStylesheet: false}); `); - + await page.waitFor(100); + const snapshot = await page.evaluate( + 'JSON.stringify(snapshot[0], null, 2);', + ); assert(snapshot.includes('"rr_dataURL"')); assert(snapshot.includes('data:image/png;base64,')); }); - }); describe('iframe integration tests', function (this: ISuite) {