From beba7ab8dac259c3efa5087f790f2c8f88285d85 Mon Sep 17 00:00:00 2001 From: eoghan Date: Wed, 27 May 2020 16:25:58 +0000 Subject: [PATCH] Enable external pausing of mutation buffer emissions - no automatic pausing based on e.g. pageVisibility yet, assuming such a thing is desirable https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API - user code has to call new API method `freezePage` e.g. when page is hidden or after a timeout - automatically unpauses when the next user initiated event occurs (am assuming everything that isn't a mutation event counts as 'user initiated' either way think this is the correct thing to do until I see a counterexample of an event that shouldn't cause the mutations to be unbufferred) --- src/record/index.ts | 16 +++++++++++++++- src/record/mutation.ts | 8 ++++++-- src/record/observer.ts | 6 ++++-- typings/record/index.d.ts | 1 + typings/record/mutation.d.ts | 7 ++++--- typings/record/observer.d.ts | 4 +++- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/record/index.ts b/src/record/index.ts index d04ca288e3..0ef16347f5 100644 --- a/src/record/index.ts +++ b/src/record/index.ts @@ -1,5 +1,5 @@ import { snapshot } from 'rrweb-snapshot'; -import initObservers from './observer'; +import { initObservers, mutationBuffer } from './observer'; import { mirror, on, @@ -50,6 +50,16 @@ function record( let lastFullSnapshotEvent: eventWithTime; let incrementalSnapshotCount = 0; wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => { + + if (mutationBuffer.paused && + !(e.type == EventType.IncrementalSnapshot + && e.data.source == IncrementalSource.Mutation)) { + // we've got a user initiated event so first we need to apply + // all DOM changes that have been buffering during paused state + mutationBuffer.emit(); + mutationBuffer.paused = false; + } + emit(((packFn ? packFn(e) : e) as unknown) as T, isCheckout); if (e.type === EventType.FullSnapshot) { lastFullSnapshotEvent = e; @@ -271,4 +281,8 @@ record.addCustomEvent = (tag: string, payload: T) => { ); }; +record.freezePage = () => { + mutationBuffer.paused = true; +}; + export default record; diff --git a/src/record/mutation.ts b/src/record/mutation.ts index aaea355e87..1529bc1dcb 100644 --- a/src/record/mutation.ts +++ b/src/record/mutation.ts @@ -20,6 +20,8 @@ function isINode(n: Node | INode): n is INode { */ export default class MutationBuffer { + public paused: boolean = false; + private texts: textCursor[] = []; private attributes: attributeCursor[] = []; private removes: removedNodeMutation[] = []; @@ -53,7 +55,7 @@ export default class MutationBuffer { private inlineStylesheet: boolean; private maskAllInputs: boolean; - constructor( + public init( cb: mutationCallBack, blockClass: blockClass, inlineStylesheet: boolean, @@ -127,7 +129,9 @@ export default class MutationBuffer { pushAdd(addQueue.shift()!); } - this.emit(); + if (!this.paused) { + this.emit(); + } } private processMutation = (m: mutationRecord) => { diff --git a/src/record/observer.ts b/src/record/observer.ts index 9d65ebdfdc..2894c633e1 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -32,6 +32,8 @@ import { } from '../types'; import MutationBuffer from './mutation'; +export const mutationBuffer = new MutationBuffer(); + function initMutationObserver( cb: mutationCallBack, blockClass: blockClass, @@ -39,7 +41,7 @@ function initMutationObserver( maskAllInputs: boolean, ): MutationObserver { // see mutation.ts for details - const mutationBuffer = new MutationBuffer( + mutationBuffer.init( cb, blockClass, inlineStylesheet, @@ -405,7 +407,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) { }; } -export default function initObservers( +export function initObservers( o: observerParam, hooks: hooksParam = {}, ): listenerHandler { diff --git a/typings/record/index.d.ts b/typings/record/index.d.ts index 73e1f2c373..9c5e20436f 100644 --- a/typings/record/index.d.ts +++ b/typings/record/index.d.ts @@ -2,5 +2,6 @@ import { eventWithTime, recordOptions, listenerHandler } from '../types'; declare function record(options?: recordOptions): listenerHandler | undefined; declare namespace record { var addCustomEvent: (tag: string, payload: T) => void; + var freezePage: () => void; } export default record; diff --git a/typings/record/mutation.d.ts b/typings/record/mutation.d.ts index 23d0b80f70..2588208e31 100644 --- a/typings/record/mutation.d.ts +++ b/typings/record/mutation.d.ts @@ -1,5 +1,6 @@ import { mutationRecord, blockClass, mutationCallBack } from '../types'; export default class MutationBuffer { + paused: boolean; private texts; private attributes; private removes; @@ -12,9 +13,9 @@ export default class MutationBuffer { private blockClass; private inlineStylesheet; private maskAllInputs; - constructor(cb: mutationCallBack, blockClass: blockClass, inlineStylesheet: boolean, maskAllInputs: boolean); - processMutations(mutations: mutationRecord[]): void; + init(cb: mutationCallBack, blockClass: blockClass, inlineStylesheet: boolean, maskAllInputs: boolean): void; + processMutations: (mutations: mutationRecord[]) => void; private processMutation; private genAdds; - emit(): void; + emit: () => void; } diff --git a/typings/record/observer.d.ts b/typings/record/observer.d.ts index 4c67f3196e..25858a01a7 100644 --- a/typings/record/observer.d.ts +++ b/typings/record/observer.d.ts @@ -1,2 +1,4 @@ import { observerParam, listenerHandler, hooksParam } from '../types'; -export default function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler; +import MutationBuffer from './mutation'; +export declare const mutationBuffer: MutationBuffer; +export declare function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler;