From 9c68c300df3ce466e43e02de5e828f9edfc18912 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 19 Apr 2024 12:50:06 +0100 Subject: [PATCH] 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 --- .changeset/event-single-wrap.md | 5 + packages/rrweb/src/record/iframe-manager.ts | 5 +- packages/rrweb/src/record/index.ts | 303 +++++++++----------- packages/rrweb/test/utils.ts | 4 +- packages/types/src/index.ts | 10 +- 5 files changed, 154 insertions(+), 173 deletions(-) create mode 100644 .changeset/event-single-wrap.md diff --git a/.changeset/event-single-wrap.md b/.changeset/event-single-wrap.md new file mode 100644 index 0000000000..c659c015ef --- /dev/null +++ b/.changeset/event-single-wrap.md @@ -0,0 +1,5 @@ +--- +'rrweb': patch +--- + +perf: Avoid an extra function call and object clone during event emission diff --git a/packages/rrweb/src/record/iframe-manager.ts b/packages/rrweb/src/record/iframe-manager.ts index d3c81d2a85..04f3d7fe7d 100644 --- a/packages/rrweb/src/record/iframe-manager.ts +++ b/packages/rrweb/src/record/iframe-manager.ts @@ -8,6 +8,7 @@ import CrossOriginIframeMirror from './cross-origin-iframe-mirror'; import { EventType, IncrementalSource } from '@sentry-internal/rrweb-types'; import type { eventWithTime, + eventWithoutTime, mutationCallBack, } from '@sentry-internal/rrweb-types'; import type { StylesheetManager } from './stylesheet-manager'; @@ -52,7 +53,7 @@ export class IframeManager implements IframeManagerInterface { 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; @@ -62,7 +63,7 @@ export class IframeManager implements IframeManagerInterface { 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; diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 910b4bf2c8..4a2fe0898c 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -19,7 +19,7 @@ import { import type { recordOptions } from '../types'; import { EventType, - event, + eventWithoutTime, eventWithTime, IncrementalSource, listenerHandler, @@ -54,17 +54,19 @@ import { } from './error-handler'; export type { CanvasManagerConstructorOptions } from './observers/canvas/canvas-manager'; -function wrapEvent(e: event): eventWithTime { - const eWithTime = e as eventWithTime; - eWithTime.timestamp = nowTimestamp(); - return eWithTime; -} +// function wrapEvent(e: event): eventWithTime { +// const eWithTime = e as eventWithTime; +// eWithTime.timestamp = nowTimestamp(); +// return eWithTime; +// } declare global { const __RRWEB_EXCLUDE_SHADOW_DOM__: boolean; const __RRWEB_EXCLUDE_IFRAME__: boolean; } +let wrappedEmit!: (e: eventWithoutTime, isCheckout?: boolean) => void; + // These are stored in module scope because we access them in other exported methods let _wrappedEmit: | undefined @@ -213,7 +215,9 @@ function record( } return e as unknown as T; }; - const 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 && @@ -266,47 +270,39 @@ function record( _wrappedEmit = wrappedEmit; 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, @@ -403,14 +399,14 @@ function record( const takeFullSnapshot = (isCheckout = false) => { wrappedEmit( - wrapEvent({ + { type: EventType.Meta, data: { href: window.location.href, width: getWindowWidth(), height: getWindowHeight(), }, - }), + }, isCheckout, ); @@ -465,13 +461,14 @@ function record( } 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 @@ -493,108 +490,88 @@ function record( onMutation, 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, @@ -634,15 +611,13 @@ function record( 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, + }, + }), })) || [], }, {}, @@ -670,12 +645,10 @@ function record( } else { handlers.push( on('DOMContentLoaded', () => { - wrappedEmit( - wrapEvent({ - type: EventType.DomContentLoaded, - data: {}, - }), - ); + wrappedEmit({ + type: EventType.DomContentLoaded, + data: {}, + }); if (recordAfter === 'DOMContentLoaded') init(); }), ); @@ -683,12 +656,10 @@ function record( on( 'load', () => { - wrappedEmit( - wrapEvent({ - type: EventType.Load, - data: {}, - }), - ); + wrappedEmit({ + type: EventType.Load, + data: {}, + }); if (recordAfter === 'load') init(); }, window, @@ -711,15 +682,13 @@ export function addCustomEvent(tag: string, payload: T) { if (!_wrappedEmit) { 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, + }, + }); } export function freezePage() { diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts index 7f406ad239..00a7f344d1 100644 --- a/packages/rrweb/test/utils.ts +++ b/packages/rrweb/test/utils.ts @@ -3,10 +3,10 @@ import { EventType, IncrementalSource, eventWithTime, + eventWithoutTime, MouseInteractions, Optional, mouseInteractionData, - event, pluginEvent, } from '@sentry-internal/rrweb-types'; import type { recordOptions } from '../src/types'; @@ -230,7 +230,7 @@ function stringifySnapshots( } } delete (s as Optional).timestamp; - return s as event; + return s as eventWithoutTime; }), null, 2, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b724559207..f81aa7688a 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -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; };