Skip to content
Merged
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
4 changes: 2 additions & 2 deletions packages/rum-recorder/src/domain/rrweb/mutation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ describe('MutationObserverWrapper', () => {
expect(mutationCallbackSpy).not.toHaveBeenCalled()
})

it('emits buffered mutation records on freeze', () => {
it('emits buffered mutation records on flush', () => {
addNodeToMap(sandbox, {})

MockMutationObserver.storeRecords([createMutationRecord()])
expect(mutationCallbackSpy).toHaveBeenCalledTimes(0)
mutationController.freeze()
mutationController.flush()
expect(mutationCallbackSpy).toHaveBeenCalledTimes(1)
})

Expand Down
35 changes: 8 additions & 27 deletions packages/rum-recorder/src/domain/rrweb/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,33 +102,17 @@ function isINode(n: Node | INode): n is INode {
}

/**
* Controls how mutations are processed, allowing to temporarily freeze the mutations process.
* Controls how mutations are processed, allowing to flush pending mutations.
*/
export class MutationController {
private frozen = false
private unfreezeListener?: () => void
private freezeListener?: () => void
private flushListener?: () => void

public freeze() {
this.freezeListener?.()
this.frozen = true
public flush() {
this.flushListener?.()
}

public unfreeze() {
this.unfreezeListener?.()
this.frozen = false
}

public isFrozen() {
return this.frozen
}

public onFreeze(listener: () => void) {
this.freezeListener = listener
}

public onUnfreeze(listener: () => void) {
this.unfreezeListener = listener
public onFlush(listener: () => void) {
this.flushListener = listener
}
}

Expand Down Expand Up @@ -175,8 +159,7 @@ export class MutationObserverWrapper {
childList: true,
subtree: true,
})
this.controller.onFreeze(() => this.processMutations(this.observer.takeRecords()))
this.controller.onUnfreeze(this.emit)
this.controller.onFlush(() => this.processMutations(this.observer.takeRecords()))
}

public stop() {
Expand All @@ -185,9 +168,7 @@ export class MutationObserverWrapper {

private processMutations = (mutations: MutationRecord[]) => {
mutations.forEach(this.processMutation)
if (!this.controller.isFrozen()) {
this.emit()
}
this.emit()
}

private emit = () => {
Expand Down
48 changes: 14 additions & 34 deletions packages/rum-recorder/src/domain/rrweb/record.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { runOnReadyState } from '@datadog/browser-core'
import { snapshot } from '../rrweb-snapshot'
import { RawRecord, RecordType } from '../../types'
import { RecordType } from '../../types'
import { initObservers } from './observer'
import { IncrementalSource, ListenerHandler, RecordAPI, RecordOptions } from './types'
import { getWindowHeight, getWindowWidth, mirror } from './utils'
import { MutationController } from './mutation'

let wrappedEmit!: (record: RawRecord, isCheckout?: boolean) => void

export function record(options: RecordOptions = {}): RecordAPI {
const { emit } = options
// runtime checks for user options
Expand All @@ -17,25 +15,10 @@ export function record(options: RecordOptions = {}): RecordAPI {

const mutationController = new MutationController()

wrappedEmit = (record, isCheckout) => {
if (
mutationController.isFrozen() &&
record.type !== RecordType.FullSnapshot &&
!(record.type === RecordType.IncrementalSnapshot && record.data.source === IncrementalSource.Mutation)
) {
// we've got a user initiated record so first we need to apply
// all DOM changes that have been buffering during paused state
mutationController.unfreeze()
}

emit(record, isCheckout)
}

const takeFullSnapshot = (isCheckout = false) => {
const wasFrozen = mutationController.isFrozen()
mutationController.freeze() // don't allow any mirror modifications during snapshotting
mutationController.flush() // process any pending mutation before taking a full snapshot

wrappedEmit(
emit(
{
data: {
height: getWindowHeight(),
Expand All @@ -47,7 +30,7 @@ export function record(options: RecordOptions = {}): RecordAPI {
isCheckout
)

wrappedEmit(
emit(
{
data: {
has_focus: document.hasFocus(),
Expand All @@ -64,7 +47,7 @@ export function record(options: RecordOptions = {}): RecordAPI {
}

mirror.map = idNodeMap
wrappedEmit({
emit({
data: {
node,
initialOffset: {
Expand All @@ -86,9 +69,6 @@ export function record(options: RecordOptions = {}): RecordAPI {
},
type: RecordType.FullSnapshot,
})
if (!wasFrozen) {
mutationController.unfreeze()
}
}

const handlers: ListenerHandler[] = []
Expand All @@ -99,71 +79,71 @@ export function record(options: RecordOptions = {}): RecordAPI {
initObservers({
mutationController,
inputCb: (v) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.Input,
...v,
},
type: RecordType.IncrementalSnapshot,
}),
mediaInteractionCb: (p) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.MediaInteraction,
...p,
},
type: RecordType.IncrementalSnapshot,
}),
mouseInteractionCb: (d) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.MouseInteraction,
...d,
},
type: RecordType.IncrementalSnapshot,
}),
mousemoveCb: (positions, source) =>
wrappedEmit({
emit({
data: {
positions,
source,
},
type: RecordType.IncrementalSnapshot,
}),
mutationCb: (m) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.Mutation,
...m,
},
type: RecordType.IncrementalSnapshot,
}),
scrollCb: (p) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.Scroll,
...p,
},
type: RecordType.IncrementalSnapshot,
}),
styleSheetRuleCb: (r) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.StyleSheetRule,
...r,
},
type: RecordType.IncrementalSnapshot,
}),
viewportResizeCb: (d) =>
wrappedEmit({
emit({
data: {
source: IncrementalSource.ViewportResize,
...d,
},
type: RecordType.IncrementalSnapshot,
}),
focusCb: (data) =>
wrappedEmit({
emit({
type: RecordType.Focus,
data,
}),
Expand Down