perf: Avoid an extra function call and object clone during event emission (#1441)

performance: remove a nested function call and an object clone during event emission

 - rename `event` to `eventWithoutTime`, but maintain backwards compatibility
 - `eventWithTime` (with time) could be renamed to `event` in a future version

This is an extension of PR #1339 authored by: mydea <mydea@users.noreply.github.com>
This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 28f32140f0
commit 049274dd37
5 changed files with 151 additions and 177 deletions

View File

@@ -0,0 +1,5 @@
---
'rrweb': patch
---
perf: Avoid an extra function call and object clone during event emission

View File

@@ -3,7 +3,11 @@ import { genId, NodeType } from 'rrweb-snapshot';
import type { CrossOriginIframeMessageEvent } from '../types';
import CrossOriginIframeMirror from './cross-origin-iframe-mirror';
import { EventType, IncrementalSource } from '@rrweb/types';
import type { eventWithTime, mutationCallBack } from '@rrweb/types';
import type {
eventWithTime,
eventWithoutTime,
mutationCallBack,
} from '@rrweb/types';
import type { StylesheetManager } from './stylesheet-manager';
export class IframeManager {
@@ -16,7 +20,7 @@ export class IframeManager {
new WeakMap();
private mirror: Mirror;
private mutationCb: mutationCallBack;
private wrappedEmit: (e: eventWithTime, isCheckout?: boolean) => void;
private wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void;
private loadListener?: (iframeEl: HTMLIFrameElement) => unknown;
private stylesheetManager: StylesheetManager;
private recordCrossOriginIframes: boolean;
@@ -26,7 +30,7 @@ export class IframeManager {
mutationCb: mutationCallBack;
stylesheetManager: StylesheetManager;
recordCrossOriginIframes: boolean;
wrappedEmit: (e: eventWithTime, isCheckout?: boolean) => void;
wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void;
}) {
this.mutationCb = options.mutationCb;
this.wrappedEmit = options.wrappedEmit;

View File

@@ -19,7 +19,7 @@ import {
import type { recordOptions } from '../types';
import {
EventType,
event,
eventWithoutTime,
eventWithTime,
IncrementalSource,
listenerHandler,
@@ -40,14 +40,7 @@ import {
unregisterErrorHandler,
} from './error-handler';
function wrapEvent(e: event): eventWithTime {
return {
...e,
timestamp: nowTimestamp(),
};
}
let wrappedEmit!: (e: eventWithTime, isCheckout?: boolean) => void;
let wrappedEmit!: (e: eventWithoutTime, isCheckout?: boolean) => void;
let takeFullSnapshot!: (isCheckout?: boolean) => void;
let canvasManager!: CanvasManager;
@@ -187,7 +180,9 @@ function record<T = eventWithTime>(
}
return e as unknown as T;
};
wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => {
wrappedEmit = (r: eventWithoutTime, isCheckout?: boolean) => {
const e = r as eventWithTime;
e.timestamp = nowTimestamp();
if (
mutationBuffers[0]?.isFrozen() &&
e.type !== EventType.FullSnapshot &&
@@ -238,47 +233,39 @@ function record<T = eventWithTime>(
};
const wrappedMutationEmit = (m: mutationCallbackParam) => {
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
...m,
},
}),
);
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
...m,
},
});
};
const wrappedScrollEmit: scrollCallback = (p) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Scroll,
...p,
},
}),
);
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Scroll,
...p,
},
});
const wrappedCanvasMutationEmit = (p: canvasMutationParam) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p,
},
}),
);
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p,
},
});
const wrappedAdoptedStyleSheetEmit = (a: adoptedStyleSheetParam) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.AdoptedStyleSheet,
...a,
},
}),
);
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.AdoptedStyleSheet,
...a,
},
});
const stylesheetManager = new StylesheetManager({
mutationCb: wrappedMutationEmit,
@@ -350,14 +337,14 @@ function record<T = eventWithTime>(
return;
}
wrappedEmit(
wrapEvent({
{
type: EventType.Meta,
data: {
href: window.location.href,
width: getWindowWidth(),
height: getWindowHeight(),
},
}),
},
isCheckout,
);
@@ -406,13 +393,13 @@ function record<T = eventWithTime>(
}
wrappedEmit(
wrapEvent({
{
type: EventType.FullSnapshot,
data: {
node,
initialOffset: getWindowScroll(window),
},
}),
},
isCheckout,
);
mutationBuffers.forEach((buf) => buf.unlock()); // generate & emit any mutations that happened during snapshotting, as can now apply against the newly built mirror
@@ -433,108 +420,88 @@ function record<T = eventWithTime>(
{
mutationCb: wrappedMutationEmit,
mousemoveCb: (positions, source) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source,
positions,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source,
positions,
},
}),
mouseInteractionCb: (d) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MouseInteraction,
...d,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MouseInteraction,
...d,
},
}),
scrollCb: wrappedScrollEmit,
viewportResizeCb: (d) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.ViewportResize,
...d,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.ViewportResize,
...d,
},
}),
inputCb: (v) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Input,
...v,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Input,
...v,
},
}),
mediaInteractionCb: (p) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MediaInteraction,
...p,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MediaInteraction,
...p,
},
}),
styleSheetRuleCb: (r) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleSheetRule,
...r,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleSheetRule,
...r,
},
}),
styleDeclarationCb: (r) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleDeclaration,
...r,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleDeclaration,
...r,
},
}),
canvasMutationCb: wrappedCanvasMutationEmit,
fontCb: (p) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Font,
...p,
},
}),
),
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Font,
...p,
},
}),
selectionCb: (p) => {
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Selection,
...p,
},
}),
);
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Selection,
...p,
},
});
},
customElementCb: (c) => {
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CustomElement,
...c,
},
}),
);
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CustomElement,
...c,
},
});
},
blockClass,
ignoreClass,
@@ -570,15 +537,13 @@ function record<T = eventWithTime>(
observer: p.observer!,
options: p.options,
callback: (payload: object) =>
wrappedEmit(
wrapEvent({
type: EventType.Plugin,
data: {
plugin: p.name,
payload,
},
}),
),
wrappedEmit({
type: EventType.Plugin,
data: {
plugin: p.name,
payload,
},
}),
})) || [],
},
hooks,
@@ -607,12 +572,10 @@ function record<T = eventWithTime>(
} else {
handlers.push(
on('DOMContentLoaded', () => {
wrappedEmit(
wrapEvent({
type: EventType.DomContentLoaded,
data: {},
}),
);
wrappedEmit({
type: EventType.DomContentLoaded,
data: {},
});
if (recordAfter === 'DOMContentLoaded') init();
}),
);
@@ -620,12 +583,10 @@ function record<T = eventWithTime>(
on(
'load',
() => {
wrappedEmit(
wrapEvent({
type: EventType.Load,
data: {},
}),
);
wrappedEmit({
type: EventType.Load,
data: {},
});
if (recordAfter === 'load') init();
},
window,
@@ -648,15 +609,13 @@ record.addCustomEvent = <T>(tag: string, payload: T) => {
if (!recording) {
throw new Error('please add custom event after start recording');
}
wrappedEmit(
wrapEvent({
type: EventType.Custom,
data: {
tag,
payload,
},
}),
);
wrappedEmit({
type: EventType.Custom,
data: {
tag,
payload,
},
});
};
record.freezePage = () => {

View File

@@ -3,10 +3,10 @@ import {
EventType,
IncrementalSource,
eventWithTime,
eventWithoutTime,
MouseInteractions,
Optional,
mouseInteractionData,
event,
pluginEvent,
} from '@rrweb/types';
import type { recordOptions } from '../src/types';
@@ -228,7 +228,7 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
}
}
delete (s as Optional<eventWithTime, 'timestamp'>).timestamp;
return s as event;
return s as eventWithoutTime;
}),
null,
2,

View File

@@ -163,7 +163,7 @@ export type incrementalData =
| adoptedStyleSheetData
| customElementData;
export type event =
export type eventWithoutTime =
| domContentLoadedEvent
| loadedEvent
| fullSnapshotEvent
@@ -172,7 +172,13 @@ export type event =
| customEvent
| pluginEvent;
export type eventWithTime = event & {
/**
* @deprecated intended for internal use
* a synonym for eventWithoutTime
*/
export type event = eventWithoutTime;
export type eventWithTime = eventWithoutTime & {
timestamp: number;
delay?: number;
};