Skip to content

Commit 592bf6e

Browse files
authored
feat(replay): Filter out style mutations when extracting DOM nodes (#83016)
We have an org that has a small handful of replays where the replayStepper causes massive perf issues to the extent that it freezes the browser. I narrowed it down to the `diff()` code inside of `rrdom` and a recent upstream PR (getsentry/rrweb#233) seems to have exacerbated the problem. I have not been able to figure out the root cause for the perf issues, but it seems to be related to CSS and the mutations that add `style` elements. We will want to try to identify what exactly in these replays are causing the perf issues. In the meantime we can filter out these mutations. Since we are only interested in generating and extracting the HTML for certain breadcrumb events, the styles should have no affect on the data we are interested in using. Closes #82221
1 parent e080c66 commit 592bf6e

File tree

2 files changed

+52
-16
lines changed

2 files changed

+52
-16
lines changed

static/app/utils/replays/hooks/useExtractDomNodes.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export default function useExtractDomNodes({
1010
}): UseQueryResult<Map<ReplayFrame, Extraction>> {
1111
return useQuery({
1212
queryKey: ['getDomNodes', replay],
13-
queryFn: () => replay?.getExtractDomNodes(),
13+
// Note: we filter out `style` mutations due to perf issues.
14+
// We can do this as long as we only need the HTML and not need to
15+
// visualize the rendered elements
16+
queryFn: () => replay?.getExtractDomNodes({withoutStyles: true}),
1417
enabled: Boolean(replay),
1518
gcTime: Infinity,
1619
});

static/app/utils/replays/replayReader.tsx

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -432,22 +432,26 @@ export default class ReplayReader {
432432
return this.processingErrors().length;
433433
};
434434

435-
getExtractDomNodes = memoize(async () => {
436-
if (this._fetching) {
437-
return null;
438-
}
439-
const {onVisitFrame, shouldVisitFrame} = extractDomNodes;
440-
441-
const results = await replayerStepper({
442-
frames: this.getDOMFrames(),
443-
rrwebEvents: this.getRRWebFrames(),
444-
startTimestampMs: this.getReplay().started_at.getTime() ?? 0,
445-
onVisitFrame,
446-
shouldVisitFrame,
447-
});
435+
getExtractDomNodes = memoize(
436+
async ({withoutStyles}: {withoutStyles?: boolean} = {}) => {
437+
if (this._fetching) {
438+
return null;
439+
}
440+
const {onVisitFrame, shouldVisitFrame} = extractDomNodes;
441+
442+
const results = await replayerStepper({
443+
frames: this.getDOMFrames(),
444+
rrwebEvents: withoutStyles
445+
? this.getRRWebFramesWithoutStyles()
446+
: this.getRRWebFrames(),
447+
startTimestampMs: this.getReplay().started_at.getTime() ?? 0,
448+
onVisitFrame,
449+
shouldVisitFrame,
450+
});
448451

449-
return results;
450-
});
452+
return results;
453+
}
454+
);
451455

452456
getClipWindow = () => this._clipWindow;
453457

@@ -534,6 +538,35 @@ export default class ReplayReader {
534538
return eventsWithSnapshots;
535539
});
536540

541+
/**
542+
* Filter out style mutations as they can cause perf problems especially when
543+
* used in replayStepper
544+
*/
545+
getRRWebFramesWithoutStyles = memoize(() => {
546+
return this.getRRWebFrames().map(e => {
547+
if (
548+
e.type === EventType.IncrementalSnapshot &&
549+
'source' in e.data &&
550+
e.data.source === IncrementalSource.Mutation
551+
) {
552+
return {
553+
...e,
554+
data: {
555+
...e.data,
556+
adds: e.data.adds.filter(
557+
add =>
558+
!(
559+
(add.node.type === 3 && add.node.isStyle) ||
560+
(add.node.type === 2 && add.node.tagName === 'style')
561+
)
562+
),
563+
},
564+
};
565+
}
566+
return e;
567+
});
568+
});
569+
537570
getRRwebTouchEvents = memoize(() =>
538571
this.getRRWebFramesWithSnapshots().filter(
539572
e => isTouchEndFrame(e) || isTouchStartFrame(e)

0 commit comments

Comments
 (0)