Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/rum/src/domain/record/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export { takeFullSnapshot, takeNodeSnapshot } from './internalApi'
export { record } from './record'
export type { SerializationMetric, SerializationStats } from './serialization'
export type { ChangeDecoder, SerializationMetric, SerializationStats } from './serialization'
export {
aggregateSerializationStats,
createChangeDecoder,
createSerializationStats,
isFullSnapshotChangeRecordsEnabled,
isIncrementalSnapshotChangeRecordsEnabled,
Expand Down
9 changes: 7 additions & 2 deletions packages/rum/src/domain/record/serialization/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export type { ChangeConverter, MutationLog, NodeIdRemapper } from './conversions'
export { createChangeConverter, createCopyingNodeIdRemapper, createIdentityNodeIdRemapper } from './conversions'
export type { ChangeConverter, ChangeDecoder, MutationLog, NodeIdRemapper } from './conversions'
export {
createChangeConverter,
createChangeDecoder,
createCopyingNodeIdRemapper,
createIdentityNodeIdRemapper,
} from './conversions'
export { isFullSnapshotChangeRecordsEnabled, isIncrementalSnapshotChangeRecordsEnabled } from './experimentalFeatures'
export { createChildInsertionCursor, createRootInsertionCursor } from './insertionCursor'
export { getElementInputValue } from './serializationUtils'
Expand Down
27 changes: 27 additions & 0 deletions packages/rum/test/record/changes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { BrowserChangeRecord, BrowserFullSnapshotChangeRecord, BrowserRecord } from '../../src/types'
import { RecordType, SnapshotFormat } from '../../src/types'
import { createChangeDecoder } from '../../src/domain/record'

export function decodeChangeRecords(
records: Array<BrowserChangeRecord | BrowserFullSnapshotChangeRecord>
): Array<BrowserChangeRecord | BrowserFullSnapshotChangeRecord> {
const changeDecoder = createChangeDecoder()
return records.map((record) => changeDecoder.decode(record))
}

export function decodeFullSnapshotChangeRecord(
record: BrowserFullSnapshotChangeRecord
): BrowserFullSnapshotChangeRecord {
const changeDecoder = createChangeDecoder()
return changeDecoder.decode(record) as BrowserFullSnapshotChangeRecord
}

export function findChangeRecords(
records: BrowserRecord[]
): Array<BrowserChangeRecord | BrowserFullSnapshotChangeRecord> {
return records.filter(
(record) =>
record.type === RecordType.Change ||
(record.type === RecordType.FullSnapshot && record.format === SnapshotFormat.Change)
)
}
135 changes: 135 additions & 0 deletions packages/rum/test/record/elements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import type {
AddElementNodeChange,
AddNodeChange,
BrowserFullSnapshotChangeRecord,
BrowserFullSnapshotRecord,
BrowserFullSnapshotV1Record,
ScrollPositionChange,
SerializedNodeWithId,
} from '../../src/types'
import { ChangeType, NodeType, SnapshotFormat } from '../../src/types'
import { decodeFullSnapshotChangeRecord } from './changes'

/**
* Given a full snapshot, locates elements with HTML id attributes and returns a map from
* each id attribute value to the corresponding element's node id.
*/
export function getElementIdsFromFullSnapshot(record: BrowserFullSnapshotRecord): Map<string, number> {
if (record.format === SnapshotFormat.Change) {
return getElementIdsFromFullSnapshotChange(record)
}
return getElementIdsFromFullSnapshotV1(record)
}

function getElementIdsFromFullSnapshotChange(rawRecord: BrowserFullSnapshotChangeRecord): Map<string, number> {
const elementIds = new Map<string, number>()

let nextId = 0
for (const change of decodeFullSnapshotChangeRecord(rawRecord).data) {
if (change[0] !== ChangeType.AddNode) {
continue
}

for (let i = 1; i < change.length; i++) {
const id = nextId++
const addedNode = change[i] as AddNodeChange
const nodeName = addedNode[1]

switch (nodeName) {
case '#cdata-section':
case '#doctype':
case '#document':
case '#document-fragment':
case '#shadow-root':
case '#text':
continue

default: {
const [, , ...attributeAssignments] = addedNode as AddElementNodeChange
for (const [name, value] of attributeAssignments) {
if (name === 'id') {
elementIds.set(String(value), id)
}
}
}
}
}
}

return elementIds
}

function getElementIdsFromFullSnapshotV1(record: BrowserFullSnapshotV1Record): Map<string, number> {
const elementIds = new Map<string, number>()

const collectIds = (node: SerializedNodeWithId) => {
if (node.type === NodeType.Element && node.attributes.id) {
elementIds.set(String(node.attributes.id), node.id)
}

if ('childNodes' in node) {
for (const child of node.childNodes) {
collectIds(child)
}
}
}

collectIds(record.data.node)
return elementIds
}

export interface ScrollPosition {
left: number
top: number
}

/**
* Given a full snapshot, locates elements with non-zero scroll positions and returns a
* map from each node id to the corresponding element's scroll position.
*/
export function getScrollPositionsFromFullSnapshot(record: BrowserFullSnapshotRecord): Map<number, ScrollPosition> {
if (record.format === SnapshotFormat.Change) {
return getScrollPositionsFromFullSnapshotChange(record)
}
return getScrollPositionsFromFullSnapshotV1(record)
}

function getScrollPositionsFromFullSnapshotChange(
record: BrowserFullSnapshotChangeRecord
): Map<number, ScrollPosition> {
const scrollPositions = new Map<number, ScrollPosition>()

for (const change of record.data) {
if (change[0] !== ChangeType.ScrollPosition) {
continue
}

for (let i = 1; i < change.length; i++) {
const [nodeId, left, top] = change[i] as ScrollPositionChange
scrollPositions.set(nodeId, { left, top })
}
}

return scrollPositions
}

function getScrollPositionsFromFullSnapshotV1(record: BrowserFullSnapshotV1Record): Map<number, ScrollPosition> {
const scrollPositions = new Map<number, ScrollPosition>()

const collectScrollPositions = (node: SerializedNodeWithId) => {
if (node.type === NodeType.Element && (node.attributes.rr_scrollLeft || node.attributes.rr_scrollTop)) {
const left = node.attributes.rr_scrollLeft ?? 0
const top = node.attributes.rr_scrollTop ?? 0
scrollPositions.set(node.id, { left: Number(left), top: Number(top) })
}

if ('childNodes' in node) {
for (const child of node.childNodes) {
collectScrollPositions(child)
}
}
}

collectScrollPositions(record.data.node)
return scrollPositions
}
2 changes: 2 additions & 0 deletions packages/rum/test/record/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './changes'
export * from './elements'
export * from './recordsPerFullSnapshot'
export * from './mutationPayloadValidator'
export * from './nodes'
Expand Down
Loading
Loading