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
2 changes: 0 additions & 2 deletions packages/core/src/tools/experimentalFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ export enum ExperimentalFeature {
FEATURE_OPERATION_VITAL = 'feature_operation_vital',
START_STOP_ACTION = 'start_stop_action',
START_STOP_RESOURCE = 'start_stop_resource',
USE_CHANGE_RECORDS = 'use_change_records',
USE_INCREMENTAL_CHANGE_RECORDS = 'use_incremental_change_records',
TOO_MANY_REQUESTS_INVESTIGATION = 'too_many_requests_investigation',
TRACK_RESOURCE_HEADERS = 'track_resource_headers',
}
Expand Down
41 changes: 2 additions & 39 deletions packages/rum/src/boot/datadogRecorder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import type { TimeStamp, HttpRequest, HttpRequestEvent, Telemetry } from '@datadog/browser-core'
import {
PageExitReason,
DefaultPrivacyLevel,
noop,
DeflateEncoderStreamId,
Observable,
ExperimentalFeature,
addExperimentalFeatures,
} from '@datadog/browser-core'
import { PageExitReason, DefaultPrivacyLevel, noop, DeflateEncoderStreamId, Observable } from '@datadog/browser-core'
import type { ViewCreatedEvent } from '@datadog/browser-rum-core'
import { LifeCycle, LifeCycleEventType, startViewHistory } from '@datadog/browser-rum-core'
import type { SessionManagerMock } from '@datadog/browser-core/test'
Expand Down Expand Up @@ -111,35 +103,6 @@ describe('startRecording', () => {
})
})

it('sends recorded segments with valid context when Change records are enabled', async () => {
addExperimentalFeatures([ExperimentalFeature.USE_CHANGE_RECORDS])
setupStartRecording()
flushSegment(lifeCycle)

const requests = await readSentRequests(1)
expect(requests[0].segment).toEqual(jasmine.any(Object))
expect(requests[0].event).toEqual({
application: {
id: 'appId',
},
creation_reason: 'init',
end: jasmine.stringMatching(/^\d{13}$/),
has_full_snapshot: true,
records_count: recordsPerFullSnapshot(),
session: {
id: MOCK_SESSION_ID,
},
start: jasmine.any(Number),
raw_segment_size: jasmine.any(Number),
compressed_segment_size: jasmine.any(Number),
view: {
id: 'view-id',
},
index_in_view: 0,
source: 'browser',
})
})

it('flushes the segment when its compressed data reaches the segment bytes limit', async () => {
setupStartRecording()
const inputCount = 150
Expand Down Expand Up @@ -196,7 +159,7 @@ describe('startRecording', () => {

const requests = await readSentRequests(2)
const firstSegment = requests[0].segment
expect(firstSegment.records[firstSegment.records.length - 2].type).toBe(RecordType.IncrementalSnapshot)
expect(firstSegment.records[firstSegment.records.length - 2].type).toBe(RecordType.Change)
expect(firstSegment.records[firstSegment.records.length - 1].type).toBe(RecordType.ViewEnd)

const secondSegment = requests[1].segment
Expand Down
2 changes: 0 additions & 2 deletions packages/rum/src/domain/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ export {
aggregateSerializationStats,
createChangeDecoder,
createSerializationStats,
isFullSnapshotChangeRecordsEnabled,
isIncrementalSnapshotChangeRecordsEnabled,
serializeNode,
} from './serialization'
export { createElementsScrollPositions } from './elementsScrollPositions'
Expand Down
94 changes: 32 additions & 62 deletions packages/rum/src/domain/record/internalApi.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { NodeType, RecordType, SnapshotFormat } from '../../types'
import type { BrowserChangeRecord, BrowserFullSnapshotChangeRecord, BrowserRecord } from '../../types'
import { ChangeType, RecordType, SnapshotFormat } from '../../types'
import { appendElement } from '../../../../rum-core/test'
import { takeFullSnapshot, takeNodeSnapshot } from './internalApi'
import { createChangeDecoder } from './serialization'

describe('takeFullSnapshot', () => {
it('should produce Meta, Focus, and FullSnapshot records', () => {
Expand All @@ -23,14 +25,8 @@ describe('takeFullSnapshot', () => {
timestamp: jasmine.any(Number),
},
{
data: {
node: jasmine.any(Object),
initialOffset: {
left: jasmine.any(Number),
top: jasmine.any(Number),
},
},
format: SnapshotFormat.V1,
data: jasmine.any(Object),
format: SnapshotFormat.Change,
type: RecordType.FullSnapshot,
timestamp: jasmine.any(Number),
},
Expand All @@ -56,68 +52,42 @@ describe('takeFullSnapshot', () => {
})

describe('takeNodeSnapshot', () => {
function decodeSnapshot(
record: BrowserRecord | undefined
): BrowserChangeRecord | BrowserFullSnapshotChangeRecord | undefined {
if (!record) {
return undefined
}
if (record.type !== RecordType.FullSnapshot) {
throw new Error(`Unexpected record type ${record.type}`)
}
if (record.format !== SnapshotFormat.Change) {
throw new Error(`Unexpected record format ${record.format}`)
}

const decoder = createChangeDecoder()
return decoder.decode(record)
}

it('should serialize nodes', () => {
const node = appendElement('<div>Hello <b>world</b></div>', document.body)
expect(takeNodeSnapshot(node)).toEqual({
type: NodeType.Element,
id: 0,
tagName: 'div',
isSVG: undefined,
attributes: {},
childNodes: [
{
type: NodeType.Text,
id: 1,
textContent: 'Hello ',
},
{
type: NodeType.Element,
id: 2,
tagName: 'b',
isSVG: undefined,
attributes: {},
childNodes: [
{
type: NodeType.Text,
id: 3,
textContent: 'world',
},
],
},
],
expect(decodeSnapshot(takeNodeSnapshot(node))).toEqual({
type: RecordType.FullSnapshot,
format: SnapshotFormat.Change,
data: [[ChangeType.AddNode, [null, 'DIV'], [1, '#text', 'Hello '], [0, 'B'], [1, '#text', 'world']]],
timestamp: jasmine.any(Number),
})
})

it('should serialize shadow hosts', () => {
const node = appendElement('<div>Hello</div>', document.body)
const shadowRoot = node.attachShadow({ mode: 'open' })
shadowRoot.appendChild(document.createTextNode('world'))
expect(takeNodeSnapshot(node)).toEqual({
type: NodeType.Element,
id: 0,
tagName: 'div',
isSVG: undefined,
attributes: {},
childNodes: [
{
type: NodeType.Text,
id: 1,
textContent: 'Hello',
},
{
type: NodeType.DocumentFragment,
id: 2,
isShadowRoot: true,
adoptedStyleSheets: undefined,
childNodes: [
{
type: NodeType.Text,
id: 3,
textContent: 'world',
},
],
},
],
expect(decodeSnapshot(takeNodeSnapshot(node))).toEqual({
type: RecordType.FullSnapshot,
format: SnapshotFormat.Change,
data: [[ChangeType.AddNode, [null, 'DIV'], [1, '#text', 'Hello'], [0, '#shadow-root'], [1, '#text', 'world']]],
timestamp: jasmine.any(Number),
})
})
})
30 changes: 20 additions & 10 deletions packages/rum/src/domain/record/internalApi.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { noop, timeStampNow } from '@datadog/browser-core'
import type { RumConfiguration } from '@datadog/browser-rum-core'
import { getNodePrivacyLevel, NodePrivacyLevel } from '@datadog/browser-rum-core'
import type { BrowserRecord, SerializedNodeWithId } from '../../types'
import type { BrowserRecord } from '../../types'
import { takeFullSnapshot as doTakeFullSnapshot } from './startFullSnapshots'
import type { ShadowRootsController } from './shadowRootsController'
import type { RecordingScope } from './recordingScope'
import { createRecordingScope } from './recordingScope'
import { createElementsScrollPositions } from './elementsScrollPositions'
import type { EmitRecordCallback } from './record.types'
import type { SerializationTransaction } from './serialization'
import { SerializationKind, serializeInTransaction, serializeNode } from './serialization'
import type { ChangeSerializationTransaction } from './serialization'
import {
createRootInsertionCursor,
SerializationKind,
serializeChangesInTransaction,
serializeNodeAsChange,
} from './serialization'

/**
* Take a full snapshot of the document, generating the same records that the browser SDK
Expand Down Expand Up @@ -47,24 +52,29 @@ export function takeFullSnapshot({
export function takeNodeSnapshot(
node: Node,
{ configuration }: { configuration?: Partial<RumConfiguration> } = {}
): SerializedNodeWithId | null {
let serializedNode: SerializedNodeWithId | null = null
): BrowserRecord | undefined {
let nodeSnapshotRecord: BrowserRecord | undefined
const emitRecord = (record: BrowserRecord) => {
nodeSnapshotRecord = record
}

serializeInTransaction(
serializeChangesInTransaction(
SerializationKind.INITIAL_FULL_SNAPSHOT,
noop,
emitRecord,
noop,
createTemporaryRecordingScope(configuration),
(transaction: SerializationTransaction): void => {
timeStampNow(),
(transaction: ChangeSerializationTransaction): void => {
const privacyLevel = getNodePrivacyLevel(node, transaction.scope.configuration.defaultPrivacyLevel)
if (privacyLevel === NodePrivacyLevel.HIDDEN || privacyLevel === NodePrivacyLevel.IGNORE) {
return
}
serializedNode = serializeNode(node, privacyLevel, transaction)
const cursor = createRootInsertionCursor(transaction.scope.nodeIds)
serializeNodeAsChange(cursor, node, privacyLevel, transaction)
}
)

return serializedNode
return nodeSnapshotRecord
}

function createTemporaryRecordingScope(configuration?: Partial<RumConfiguration>): RecordingScope {
Expand Down
Loading
Loading