diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts index 1d82cc13..08541760 100644 --- a/packages/rrweb-snapshot/src/snapshot.ts +++ b/packages/rrweb-snapshot/src/snapshot.ts @@ -7,6 +7,7 @@ import { idNodeMap, MaskInputOptions, SlimDOMOptions, + DataURLOptions, MaskTextFn, MaskInputFn, KeepIframeSrcFn, @@ -378,6 +379,7 @@ function serializeNode( maskInputOptions: MaskInputOptions; maskTextFn: MaskTextFn | undefined; maskInputFn: MaskInputFn | undefined; + dataURLOptions?: DataURLOptions, inlineImages: boolean; recordCanvas: boolean; keepIframeSrcFn: KeepIframeSrcFn; @@ -393,6 +395,7 @@ function serializeNode( maskInputOptions = {}, maskTextFn, maskInputFn, + dataURLOptions = {}, inlineImages, recordCanvas, keepIframeSrcFn, @@ -513,17 +516,17 @@ function serializeNode( if ((n as ICanvas).__context === '2d') { // only record this on 2d canvas if (!is2DCanvasBlank(n as HTMLCanvasElement)) { - attributes.rr_dataURL = (n as HTMLCanvasElement).toDataURL(); + attributes.rr_dataURL = (n as HTMLCanvasElement).toDataURL(dataURLOptions.type, dataURLOptions.quality); } } else if (!('__context' in n)) { // context is unknown, better not call getContext to trigger it - const canvasDataURL = (n as HTMLCanvasElement).toDataURL(); + const canvasDataURL = (n as HTMLCanvasElement).toDataURL(dataURLOptions.type, dataURLOptions.quality); // create blank canvas of same dimensions const blankCanvas = document.createElement('canvas'); blankCanvas.width = (n as HTMLCanvasElement).width; blankCanvas.height = (n as HTMLCanvasElement).height; - const blankCanvasDataURL = blankCanvas.toDataURL(); + const blankCanvasDataURL = blankCanvas.toDataURL(dataURLOptions.type, dataURLOptions.quality); // no need to save dataURL if it's the same as blank canvas if (canvasDataURL !== blankCanvasDataURL) { @@ -545,7 +548,7 @@ function serializeNode( canvasService!.width = image.naturalWidth; canvasService!.height = image.naturalHeight; canvasCtx!.drawImage(image, 0, 0); - attributes.rr_dataURL = canvasService!.toDataURL(); + attributes.rr_dataURL = canvasService!.toDataURL(dataURLOptions.type, dataURLOptions.quality); } catch (err) { console.warn( `Cannot inline img src=${image.currentSrc}! Error: ${err}`, @@ -774,6 +777,7 @@ export function serializeNodeWithId( maskTextFn: MaskTextFn | undefined; maskInputFn: MaskInputFn | undefined; slimDOMOptions: SlimDOMOptions; + dataURLOptions?: DataURLOptions; keepIframeSrcFn?: KeepIframeSrcFn; inlineImages?: boolean; recordCanvas?: boolean; @@ -796,6 +800,7 @@ export function serializeNodeWithId( maskTextFn, maskInputFn, slimDOMOptions, + dataURLOptions = {}, inlineImages = false, recordCanvas = false, onSerialize, @@ -814,6 +819,7 @@ export function serializeNodeWithId( maskInputOptions, maskTextFn, maskInputFn, + dataURLOptions, inlineImages, recordCanvas, keepIframeSrcFn, @@ -880,6 +886,7 @@ export function serializeNodeWithId( maskTextFn, maskInputFn, slimDOMOptions, + dataURLOptions, inlineImages, recordCanvas, preserveWhiteSpace, @@ -933,6 +940,7 @@ export function serializeNodeWithId( maskTextFn, maskInputFn, slimDOMOptions, + dataURLOptions, inlineImages, recordCanvas, preserveWhiteSpace, @@ -966,6 +974,7 @@ function snapshot( maskTextFn?: MaskTextFn; maskInputFn?: MaskTextFn; slimDOM?: boolean | SlimDOMOptions; + dataURLOptions?: DataURLOptions, inlineImages?: boolean; recordCanvas?: boolean; preserveWhiteSpace?: boolean; @@ -987,6 +996,7 @@ function snapshot( maskTextFn, maskInputFn, slimDOM = false, + dataURLOptions, preserveWhiteSpace, onSerialize, onIframeLoad, @@ -1051,6 +1061,7 @@ function snapshot( maskTextFn, maskInputFn, slimDOMOptions, + dataURLOptions, inlineImages, recordCanvas, preserveWhiteSpace, diff --git a/packages/rrweb-snapshot/src/types.ts b/packages/rrweb-snapshot/src/types.ts index f239213d..eedd4925 100644 --- a/packages/rrweb-snapshot/src/types.ts +++ b/packages/rrweb-snapshot/src/types.ts @@ -112,6 +112,11 @@ export type SlimDOMOptions = Partial<{ headMetaVerification: boolean; }>; +export type DataURLOptions = Partial<{ + type: string; + quality: number; +}>; + export type MaskTextFn = (text: string) => string; export type MaskInputFn = (text: string) => string; diff --git a/packages/rrweb-snapshot/test/integration.test.ts b/packages/rrweb-snapshot/test/integration.test.ts index c9aa91b9..0d5af440 100644 --- a/packages/rrweb-snapshot/test/integration.test.ts +++ b/packages/rrweb-snapshot/test/integration.test.ts @@ -199,14 +199,17 @@ iframe.contentDocument.querySelector('center').clientHeight waitUntil: 'load', }); await page.waitForSelector('img', { timeout: 1000 }); - await page.evaluate(`${code}var snapshot = rrweb.snapshot(document, {inlineImages: true, inlineStylesheet: false}); - `); + await page.evaluate(`${code}var snapshot = rrweb.snapshot(document, { + dataURLOptions: { type: "image/webp", quality: 0.8 }, + 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,')); + assert(snapshot.includes('data:image/webp;base64,')); }); }); diff --git a/packages/rrweb-snapshot/typings/snapshot.d.ts b/packages/rrweb-snapshot/typings/snapshot.d.ts index af06efc2..b970f751 100644 --- a/packages/rrweb-snapshot/typings/snapshot.d.ts +++ b/packages/rrweb-snapshot/typings/snapshot.d.ts @@ -1,4 +1,4 @@ -import { serializedNodeWithId, INode, idNodeMap, MaskInputOptions, SlimDOMOptions, MaskTextFn, MaskInputFn, KeepIframeSrcFn } from './types'; +import { serializedNodeWithId, INode, idNodeMap, MaskInputOptions, SlimDOMOptions, DataURLOptions, MaskTextFn, MaskInputFn, KeepIframeSrcFn } from './types'; export declare const IGNORED_NODE = -2; export declare function absoluteToStylesheet(cssText: string | null, href: string): string; export declare function absoluteToDoc(doc: Document, attributeValue: string): string; @@ -18,6 +18,7 @@ export declare function serializeNodeWithId(n: Node | INode, options: { maskTextFn: MaskTextFn | undefined; maskInputFn: MaskInputFn | undefined; slimDOMOptions: SlimDOMOptions; + dataURLOptions?: DataURLOptions; keepIframeSrcFn?: KeepIframeSrcFn; inlineImages?: boolean; recordCanvas?: boolean; @@ -36,6 +37,7 @@ declare function snapshot(n: Document, options?: { maskTextFn?: MaskTextFn; maskInputFn?: MaskTextFn; slimDOM?: boolean | SlimDOMOptions; + dataURLOptions?: DataURLOptions; inlineImages?: boolean; recordCanvas?: boolean; preserveWhiteSpace?: boolean; diff --git a/packages/rrweb-snapshot/typings/types.d.ts b/packages/rrweb-snapshot/typings/types.d.ts index 262d3f9a..a8ccc0d3 100644 --- a/packages/rrweb-snapshot/typings/types.d.ts +++ b/packages/rrweb-snapshot/typings/types.d.ts @@ -91,6 +91,10 @@ export declare type SlimDOMOptions = Partial<{ headMetaAuthorship: boolean; headMetaVerification: boolean; }>; +export declare type DataURLOptions = Partial<{ + type: string; + quality: number; +}>; export declare type MaskTextFn = (text: string) => string; export declare type MaskInputFn = (text: string) => string; export declare type KeepIframeSrcFn = (src: string) => boolean;