diff --git a/static/app/utils/replays/hooks/useExtractDomNodes.tsx b/static/app/utils/replays/hooks/useExtractDomNodes.tsx index 6a676364c119f4..961c174f5bd0aa 100644 --- a/static/app/utils/replays/hooks/useExtractDomNodes.tsx +++ b/static/app/utils/replays/hooks/useExtractDomNodes.tsx @@ -10,7 +10,10 @@ export default function useExtractDomNodes({ }): UseQueryResult> { return useQuery({ queryKey: ['getDomNodes', replay], - queryFn: () => replay?.getExtractDomNodes(), + // Note: we filter out `style` mutations due to perf issues. + // We can do this as long as we only need the HTML and not need to + // visualize the rendered elements + queryFn: () => replay?.getExtractDomNodes({withoutStyles: true}), enabled: Boolean(replay), gcTime: Infinity, }); diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx index 835ba85cb40079..3d88d9fc7630d7 100644 --- a/static/app/utils/replays/replayReader.tsx +++ b/static/app/utils/replays/replayReader.tsx @@ -432,22 +432,26 @@ export default class ReplayReader { return this.processingErrors().length; }; - getExtractDomNodes = memoize(async () => { - if (this._fetching) { - return null; - } - const {onVisitFrame, shouldVisitFrame} = extractDomNodes; - - const results = await replayerStepper({ - frames: this.getDOMFrames(), - rrwebEvents: this.getRRWebFrames(), - startTimestampMs: this.getReplay().started_at.getTime() ?? 0, - onVisitFrame, - shouldVisitFrame, - }); + getExtractDomNodes = memoize( + async ({withoutStyles}: {withoutStyles?: boolean} = {}) => { + if (this._fetching) { + return null; + } + const {onVisitFrame, shouldVisitFrame} = extractDomNodes; + + const results = await replayerStepper({ + frames: this.getDOMFrames(), + rrwebEvents: withoutStyles + ? this.getRRWebFramesWithoutStyles() + : this.getRRWebFrames(), + startTimestampMs: this.getReplay().started_at.getTime() ?? 0, + onVisitFrame, + shouldVisitFrame, + }); - return results; - }); + return results; + } + ); getClipWindow = () => this._clipWindow; @@ -534,6 +538,35 @@ export default class ReplayReader { return eventsWithSnapshots; }); + /** + * Filter out style mutations as they can cause perf problems especially when + * used in replayStepper + */ + getRRWebFramesWithoutStyles = memoize(() => { + return this.getRRWebFrames().map(e => { + if ( + e.type === EventType.IncrementalSnapshot && + 'source' in e.data && + e.data.source === IncrementalSource.Mutation + ) { + return { + ...e, + data: { + ...e.data, + adds: e.data.adds.filter( + add => + !( + (add.node.type === 3 && add.node.isStyle) || + (add.node.type === 2 && add.node.tagName === 'style') + ) + ), + }, + }; + } + return e; + }); + }); + getRRwebTouchEvents = memoize(() => this.getRRWebFramesWithSnapshots().filter( e => isTouchEndFrame(e) || isTouchStartFrame(e)