From 2c06b39d6c5bdd6d2e470f77a3ac1677815f393e Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Thu, 11 Jan 2024 17:54:13 +0100 Subject: [PATCH 1/2] Replay: generate censored image with similar dimension than the original --- .../record/serialization/htmlAst.specHelper.ts | 2 +- .../record/serialization/serializationUtils.ts | 4 ++++ .../record/serialization/serializeAttribute.ts | 18 +++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts b/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts index 3817d8d783..8288c395b8 100644 --- a/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts +++ b/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts @@ -249,7 +249,7 @@ export const AST_MASK = { type: 2, tagName: 'img', attributes: { - src: 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==', + src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style='background-color:silver'%3E%3C/svg%3E", }, childNodes: [], }, diff --git a/packages/rum/src/domain/record/serialization/serializationUtils.ts b/packages/rum/src/domain/record/serialization/serializationUtils.ts index 5a8bf45679..218aa564b2 100644 --- a/packages/rum/src/domain/record/serialization/serializationUtils.ts +++ b/packages/rum/src/domain/record/serialization/serializationUtils.ts @@ -119,3 +119,7 @@ export function getValidTagName(tagName: string): string { return processedTagName } + +export function censoredImageForSize(width: number, height: number) { + return `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}' style='background-color:silver'%3E%3C/svg%3E` +} diff --git a/packages/rum/src/domain/record/serialization/serializeAttribute.ts b/packages/rum/src/domain/record/serialization/serializeAttribute.ts index 6fb0450731..4b29ca6e0d 100644 --- a/packages/rum/src/domain/record/serialization/serializeAttribute.ts +++ b/packages/rum/src/domain/record/serialization/serializeAttribute.ts @@ -3,6 +3,7 @@ import { STABLE_ATTRIBUTES } from '@datadog/browser-rum-core' import type { RumConfiguration } from '@datadog/browser-rum-core' import { NodePrivacyLevel, PRIVACY_ATTR_NAME, CENSORED_STRING_MARK, CENSORED_IMG_MARK } from '../../../constants' import { MAX_ATTRIBUTE_VALUE_CHAR_LENGTH } from '../privacy' +import { censoredImageForSize } from './serializationUtils' export function serializeAttribute( element: Element, @@ -30,12 +31,23 @@ export function serializeAttribute( case 'placeholder': return CENSORED_STRING_MARK } + // mask image URLs - if (tagName === 'IMG' || tagName === 'SOURCE') { - if (attributeName === 'src' || attributeName === 'srcset') { - return CENSORED_IMG_MARK + if (tagName === 'IMG' && (attributeName === 'src' || attributeName === 'srcset')) { + // generate image with similar dimension than the original to have the same rendering behaviour + const image = element as HTMLImageElement + if (image.naturalWidth > 0) { + return censoredImageForSize(image.naturalWidth, image.naturalHeight) } + const { width, height } = element.getBoundingClientRect() + return censoredImageForSize(width, height) + } + + // mask source URLs + if (tagName === 'SOURCE' && (attributeName === 'src' || attributeName === 'srcset')) { + return CENSORED_IMG_MARK } + // mask URLs if (tagName === 'A' && attributeName === 'href') { return CENSORED_STRING_MARK From eac1e95d8218626395d1b61097977cdde5eca5d2 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Mon, 15 Jan 2024 17:51:21 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=91=8C=20add=20extra=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serialization/htmlAst.specHelper.ts | 2 +- .../serialization/serializeAttribute.spec.ts | 42 +++++++++++++++++++ .../serialization/serializeAttribute.ts | 6 ++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts b/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts index 8288c395b8..3817d8d783 100644 --- a/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts +++ b/packages/rum/src/domain/record/serialization/htmlAst.specHelper.ts @@ -249,7 +249,7 @@ export const AST_MASK = { type: 2, tagName: 'img', attributes: { - src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style='background-color:silver'%3E%3C/svg%3E", + src: 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==', }, childNodes: [], }, diff --git a/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts b/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts index e3f10747cf..d1cd214f05 100644 --- a/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts +++ b/packages/rum/src/domain/record/serialization/serializeAttribute.spec.ts @@ -96,4 +96,46 @@ describe('serializeAttribute', () => { expect(serializeAttribute(node, NodePrivacyLevel.MASK, STABLE_ATTRIBUTES[0], DEFAULT_CONFIGURATION)).toBe('foo') }) }) + + describe('image masking', () => { + let imageStub: Partial & { width: number; height: number; naturalWidth: number; naturalHeight: number } + + beforeEach(() => { + imageStub = { + width: 0, + height: 0, + naturalWidth: 0, + naturalHeight: 0, + tagName: 'IMG', + getAttribute() { + return 'http://foo.bar/image.png' + }, + getBoundingClientRect() { + return { width: this.width, height: this.height } as DOMRect + }, + } + }) + + it('should use an image with same natural dimension than the original one', () => { + imageStub.naturalWidth = 2000 + imageStub.naturalHeight = 1000 + expect(serializeAttribute(imageStub as Element, NodePrivacyLevel.MASK, 'src', DEFAULT_CONFIGURATION)).toBe( + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2000' height='1000' style='background-color:silver'%3E%3C/svg%3E" + ) + }) + + it('should use an image with same rendering dimension than the original one', () => { + imageStub.width = 200 + imageStub.height = 100 + expect(serializeAttribute(imageStub as Element, NodePrivacyLevel.MASK, 'src', DEFAULT_CONFIGURATION)).toBe( + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='100' style='background-color:silver'%3E%3C/svg%3E" + ) + }) + + it("should use the censored image when original image size can't be computed", () => { + expect(serializeAttribute(imageStub as Element, NodePrivacyLevel.MASK, 'src', DEFAULT_CONFIGURATION)).toBe( + 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==' + ) + }) + }) }) diff --git a/packages/rum/src/domain/record/serialization/serializeAttribute.ts b/packages/rum/src/domain/record/serialization/serializeAttribute.ts index 4b29ca6e0d..f04d74262d 100644 --- a/packages/rum/src/domain/record/serialization/serializeAttribute.ts +++ b/packages/rum/src/domain/record/serialization/serializeAttribute.ts @@ -40,7 +40,11 @@ export function serializeAttribute( return censoredImageForSize(image.naturalWidth, image.naturalHeight) } const { width, height } = element.getBoundingClientRect() - return censoredImageForSize(width, height) + if (width > 0 || height > 0) { + return censoredImageForSize(width, height) + } + // if we can't get the image size, fallback to the censored image + return CENSORED_IMG_MARK } // mask source URLs