diff --git a/packages/rum/src/domain/record/mutationObserver.ts b/packages/rum/src/domain/record/mutationObserver.ts index d3ff3acda3..cb81b5e699 100644 --- a/packages/rum/src/domain/record/mutationObserver.ts +++ b/packages/rum/src/domain/record/mutationObserver.ts @@ -211,6 +211,7 @@ function processChildListMutations( document, serializedNodeIds, parentNodePrivacyLevel, + serializationContext: 'mutation', }) if (!serializedNode) { continue diff --git a/packages/rum/src/domain/record/serialize.spec.ts b/packages/rum/src/domain/record/serialize.spec.ts index 541987d0e3..f0f5953cf5 100644 --- a/packages/rum/src/domain/record/serialize.spec.ts +++ b/packages/rum/src/domain/record/serialize.spec.ts @@ -17,7 +17,7 @@ import { import type { ElementNode, SerializedNodeWithId, TextNode } from '../../types' import { NodeType } from '../../types' import { hasSerializedNode } from './serializationUtils' -import type { SerializeOptions } from './serialize' +import type { SerializeOptions, SerializationContext } from './serialize' import { serializeDocument, serializeNodeWithId, @@ -30,6 +30,7 @@ import { MAX_ATTRIBUTE_VALUE_CHAR_LENGTH } from './privacy' const DEFAULT_OPTIONS: SerializeOptions = { document, parentNodePrivacyLevel: NodePrivacyLevel.ALLOW, + serializationContext: 'full-snapshot', } describe('serializeNodeWithId', () => { @@ -113,22 +114,41 @@ describe('serializeNodeWithId', () => { style: 'width: 10px;', }) }) - - it('serializes scroll position', () => { - const element = document.createElement('div') - Object.assign(element.style, { width: '100px', height: '100px', overflow: 'scroll' }) - const inner = document.createElement('div') - Object.assign(inner.style, { width: '200px', height: '200px' }) - element.appendChild(inner) - sandbox.appendChild(element) - element.scrollBy(10, 20) - - expect((serializeNodeWithId(element, DEFAULT_OPTIONS)! as ElementNode).attributes).toEqual( - jasmine.objectContaining({ + ;[ + { + description: 'serializes scroll position during full snapshot', + serializationContext: 'full-snapshot' as SerializationContext, + shouldSerializeScroll: true, + }, + { + description: 'does not serialize scroll position during mutation', + serializationContext: 'mutation' as SerializationContext, + shouldSerializeScroll: false, + }, + ].forEach(({ description, serializationContext, shouldSerializeScroll }) => { + it(description, () => { + const element = document.createElement('div') + Object.assign(element.style, { width: '100px', height: '100px', overflow: 'scroll' }) + const inner = document.createElement('div') + Object.assign(inner.style, { width: '200px', height: '200px' }) + element.appendChild(inner) + sandbox.appendChild(element) + element.scrollBy(10, 20) + + const serializedAttributes = ( + serializeNodeWithId(element, { ...DEFAULT_OPTIONS, serializationContext })! as ElementNode + ).attributes + const attributesWithScrollPositions = jasmine.objectContaining({ rr_scrollTop: 20, rr_scrollLeft: 10, }) - ) + + if (shouldSerializeScroll) { + expect(serializedAttributes).toEqual(attributesWithScrollPositions) + } else { + expect(serializedAttributes).not.toEqual(attributesWithScrollPositions) + } + }) }) it('ignores white space in ', () => { @@ -480,9 +500,10 @@ describe('serializeDocumentNode handles', function testAllowDomTree() { }) it('a masked DOM Document itself is still serialized ', () => { - const serializeOptionsMask = { + const serializeOptionsMask: SerializeOptions = { document, parentNodePrivacyLevel: NodePrivacyLevel.MASK, + serializationContext: 'full-snapshot', } expect(serializeDocumentNode(document, serializeOptionsMask)).toEqual({ type: NodeType.Document, diff --git a/packages/rum/src/domain/record/serialize.ts b/packages/rum/src/domain/record/serialize.ts index 1bc6d80ce8..aecfb2130d 100644 --- a/packages/rum/src/domain/record/serialize.ts +++ b/packages/rum/src/domain/record/serialize.ts @@ -34,11 +34,14 @@ type ParentNodePrivacyLevel = | typeof NodePrivacyLevel.MASK | typeof NodePrivacyLevel.MASK_USER_INPUT +export type SerializationContext = 'full-snapshot' | 'mutation' + export interface SerializeOptions { document: Document serializedNodeIds?: Set ignoreWhiteSpace?: boolean parentNodePrivacyLevel: ParentNodePrivacyLevel + serializationContext: SerializationContext } export function serializeDocument( @@ -49,6 +52,7 @@ export function serializeDocument( return serializeNodeWithId(document, { document, parentNodePrivacyLevel: defaultPrivacyLevel, + serializationContext: 'full-snapshot', })! } @@ -145,7 +149,7 @@ export function serializeElementNode(element: Element, options: SerializeOptions return } - const attributes = getAttributesForPrivacyLevel(element, nodePrivacyLevel) + const attributes = getAttributesForPrivacyLevel(element, nodePrivacyLevel, options.serializationContext) let childNodes: SerializedNodeWithId[] = [] if (element.childNodes.length) { @@ -303,7 +307,8 @@ function isSVGElement(el: Element): boolean { function getAttributesForPrivacyLevel( element: Element, - nodePrivacyLevel: NodePrivacyLevel + nodePrivacyLevel: NodePrivacyLevel, + serializationContext: SerializationContext ): Record { if (nodePrivacyLevel === NodePrivacyLevel.HIDDEN) { return {} @@ -393,13 +398,15 @@ function getAttributesForPrivacyLevel( } /** - * Serialize the scroll state for each element + * Serialize the scroll state for each element only for full snapshot */ - if (element.scrollLeft) { - safeAttrs.rr_scrollLeft = Math.round(element.scrollLeft) - } - if (element.scrollTop) { - safeAttrs.rr_scrollTop = Math.round(element.scrollTop) + if (serializationContext === 'full-snapshot') { + if (element.scrollLeft) { + safeAttrs.rr_scrollLeft = Math.round(element.scrollLeft) + } + if (element.scrollTop) { + safeAttrs.rr_scrollTop = Math.round(element.scrollTop) + } } return safeAttrs diff --git a/packages/rum/test/htmlAst.ts b/packages/rum/test/htmlAst.ts index d3ca55a65d..5bc7bdf941 100644 --- a/packages/rum/test/htmlAst.ts +++ b/packages/rum/test/htmlAst.ts @@ -32,6 +32,7 @@ export const generateLeanSerializedDoc = (htmlContent: string, privacyTag: strin serializeNodeWithId(newDoc, { document: newDoc, parentNodePrivacyLevel: NodePrivacyLevel.ALLOW, + serializationContext: 'full-snapshot', })! as unknown as Record ) as unknown as SerializedNodeWithId return serializedDoc