Save images offline, in the snapshot (#770)
* Implemented image restore from rr_dataURL * Implement saving images in the snapshot * Fixed image saving, added a test * Rename data-src to data-rrweb-src * Updated the guide * Rename recordImages to inlineImages and try catch
This commit is contained in:
committed by
GitHub
parent
dd2cdedcd6
commit
151debad4a
@@ -226,18 +226,24 @@ function buildNode(
|
||||
ctx.drawImage(image, 0, 0, image.width, image.height);
|
||||
}
|
||||
};
|
||||
} else if (tagName === 'img' && name === 'rr_dataURL') {
|
||||
const image = (node as HTMLImageElement);
|
||||
if (!image.currentSrc.startsWith('data:')) {
|
||||
// backup original img src
|
||||
image.setAttribute('data-rrweb-src', image.currentSrc);
|
||||
image.src = value;
|
||||
}
|
||||
image.removeAttribute(name);
|
||||
}
|
||||
|
||||
if (name === 'rr_width') {
|
||||
(node as HTMLElement).style.width = value;
|
||||
}
|
||||
if (name === 'rr_height') {
|
||||
} else if (name === 'rr_height') {
|
||||
(node as HTMLElement).style.height = value;
|
||||
}
|
||||
if (name === 'rr_mediaCurrentTime') {
|
||||
} else if (name === 'rr_mediaCurrentTime') {
|
||||
(node as HTMLMediaElement).currentTime = n.attributes
|
||||
.rr_mediaCurrentTime as number;
|
||||
}
|
||||
if (name === 'rr_mediaState') {
|
||||
} else if (name === 'rr_mediaState') {
|
||||
switch (value) {
|
||||
case 'played':
|
||||
(node as HTMLMediaElement)
|
||||
|
||||
@@ -75,6 +75,20 @@ function extractOrigin(url: string): string {
|
||||
return origin;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -369,6 +383,7 @@ function serializeNode(
|
||||
maskInputOptions: MaskInputOptions;
|
||||
maskTextFn: MaskTextFn | undefined;
|
||||
maskInputFn: MaskInputFn | undefined;
|
||||
inlineImages: boolean;
|
||||
recordCanvas: boolean;
|
||||
keepIframeSrcFn: KeepIframeSrcFn;
|
||||
},
|
||||
@@ -383,6 +398,7 @@ function serializeNode(
|
||||
maskInputOptions = {},
|
||||
maskTextFn,
|
||||
maskInputFn,
|
||||
inlineImages,
|
||||
recordCanvas,
|
||||
keepIframeSrcFn,
|
||||
} = options;
|
||||
@@ -498,6 +514,19 @@ function serializeNode(
|
||||
if (tagName === 'canvas' && recordCanvas) {
|
||||
attributes.rr_dataURL = (n as HTMLCanvasElement).toDataURL();
|
||||
}
|
||||
// save image offline
|
||||
if (tagName === 'img' && inlineImages && canvasService && canvasCtx) {
|
||||
const image = (n as HTMLImageElement);
|
||||
image.crossOrigin = 'anonymous';
|
||||
try {
|
||||
canvasService.width = image.naturalWidth;
|
||||
canvasService.height = image.naturalHeight;
|
||||
canvasCtx.drawImage(image, 0, 0);
|
||||
attributes.rr_dataURL = canvasService.toDataURL();
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
// media elements
|
||||
if (tagName === 'audio' || tagName === 'video') {
|
||||
attributes.rr_mediaState = (n as HTMLMediaElement).paused
|
||||
@@ -711,6 +740,7 @@ export function serializeNodeWithId(
|
||||
maskInputFn: MaskInputFn | undefined;
|
||||
slimDOMOptions: SlimDOMOptions;
|
||||
keepIframeSrcFn?: KeepIframeSrcFn;
|
||||
inlineImages?: boolean;
|
||||
recordCanvas?: boolean;
|
||||
preserveWhiteSpace?: boolean;
|
||||
onSerialize?: (n: INode) => unknown;
|
||||
@@ -731,6 +761,7 @@ export function serializeNodeWithId(
|
||||
maskTextFn,
|
||||
maskInputFn,
|
||||
slimDOMOptions,
|
||||
inlineImages = false,
|
||||
recordCanvas = false,
|
||||
onSerialize,
|
||||
onIframeLoad,
|
||||
@@ -748,6 +779,7 @@ export function serializeNodeWithId(
|
||||
maskInputOptions,
|
||||
maskTextFn,
|
||||
maskInputFn,
|
||||
inlineImages,
|
||||
recordCanvas,
|
||||
keepIframeSrcFn,
|
||||
});
|
||||
@@ -800,6 +832,9 @@ export function serializeNodeWithId(
|
||||
) {
|
||||
preserveWhiteSpace = false;
|
||||
}
|
||||
if (inlineImages) {
|
||||
initCanvasService(doc);
|
||||
}
|
||||
const bypassOptions = {
|
||||
doc,
|
||||
map,
|
||||
@@ -813,6 +848,7 @@ export function serializeNodeWithId(
|
||||
maskTextFn,
|
||||
maskInputFn,
|
||||
slimDOMOptions,
|
||||
inlineImages,
|
||||
recordCanvas,
|
||||
preserveWhiteSpace,
|
||||
onSerialize,
|
||||
@@ -865,6 +901,7 @@ export function serializeNodeWithId(
|
||||
maskTextFn,
|
||||
maskInputFn,
|
||||
slimDOMOptions,
|
||||
inlineImages,
|
||||
recordCanvas,
|
||||
preserveWhiteSpace,
|
||||
onSerialize,
|
||||
@@ -897,6 +934,7 @@ function snapshot(
|
||||
maskTextFn?: MaskTextFn;
|
||||
maskInputFn?: MaskTextFn;
|
||||
slimDOM?: boolean | SlimDOMOptions;
|
||||
inlineImages?: boolean;
|
||||
recordCanvas?: boolean;
|
||||
preserveWhiteSpace?: boolean;
|
||||
onSerialize?: (n: INode) => unknown;
|
||||
@@ -911,6 +949,7 @@ function snapshot(
|
||||
maskTextClass = 'rr-mask',
|
||||
maskTextSelector = null,
|
||||
inlineStylesheet = true,
|
||||
inlineImages = false,
|
||||
recordCanvas = false,
|
||||
maskAllInputs = false,
|
||||
maskTextFn,
|
||||
@@ -980,6 +1019,7 @@ function snapshot(
|
||||
maskTextFn,
|
||||
maskInputFn,
|
||||
slimDOMOptions,
|
||||
inlineImages,
|
||||
recordCanvas,
|
||||
preserveWhiteSpace,
|
||||
onSerialize,
|
||||
|
||||
Reference in New Issue
Block a user