@@ -103,6 +103,36 @@ function removeDuplicateClicks(frames: BreadcrumbFrame[]) {
103
103
return uniqueClickFrames . concat ( otherFrames ) . concat ( slowClickFrames ) ;
104
104
}
105
105
106
+ // If a `navigation` crumb and `navigation.*` span happen within this timeframe,
107
+ // we'll consider them duplicates.
108
+ const DUPLICATE_NAV_THRESHOLD_MS = 2 ;
109
+
110
+ /**
111
+ * Return a list of BreadcrumbFrames, where any navigation crumb is removed if
112
+ * there is a matching navigation.* span to replace it.
113
+ *
114
+ * SpanFrame is preferred because they render with more specific titles.
115
+ */
116
+ function removeDuplicateNavCrumbs (
117
+ crumbFrames : BreadcrumbFrame [ ] ,
118
+ spanFrames : SpanFrame [ ]
119
+ ) {
120
+ const navCrumbs = crumbFrames . filter ( crumb => crumb . category === 'navigation' ) ;
121
+ const otherBreadcrumbFrames = crumbFrames . filter (
122
+ crumb => crumb . category !== 'navigation'
123
+ ) ;
124
+
125
+ const navSpans = spanFrames . filter ( span => span . op . startsWith ( 'navigation.' ) ) ;
126
+
127
+ const uniqueNavCrumbs = navCrumbs . filter (
128
+ crumb =>
129
+ ! navSpans . some (
130
+ span => Math . abs ( crumb . offsetMs - span . offsetMs ) <= DUPLICATE_NAV_THRESHOLD_MS
131
+ )
132
+ ) ;
133
+ return otherBreadcrumbFrames . concat ( uniqueNavCrumbs ) ;
134
+ }
135
+
106
136
export default class ReplayReader {
107
137
static factory ( { attachments, errors, replayRecord, clipWindow} : ReplayReaderParams ) {
108
138
if ( ! attachments || ! replayRecord || ! errors ) {
@@ -446,20 +476,22 @@ export default class ReplayReader {
446
476
)
447
477
) ;
448
478
449
- getPerfFrames = memoize ( ( ) =>
450
- [
451
- ...removeDuplicateClicks (
452
- this . _sortedBreadcrumbFrames . filter (
453
- frame =>
454
- [ 'navigation' , 'ui.click' ] . includes ( frame . category ) ||
455
- ( frame . category === 'ui.slowClickDetected' &&
456
- ( isDeadClick ( frame as SlowClickFrame ) ||
457
- isDeadRageClick ( frame as SlowClickFrame ) ) )
458
- )
459
- ) ,
460
- ...this . _sortedSpanFrames . filter ( frame => frame . op . startsWith ( 'navigation.' ) ) ,
461
- ] . sort ( sortFrames )
462
- ) ;
479
+ getPerfFrames = memoize ( ( ) => {
480
+ const crumbs = removeDuplicateClicks (
481
+ this . _sortedBreadcrumbFrames . filter (
482
+ frame =>
483
+ [ 'navigation' , 'ui.click' ] . includes ( frame . category ) ||
484
+ ( frame . category === 'ui.slowClickDetected' &&
485
+ ( isDeadClick ( frame as SlowClickFrame ) ||
486
+ isDeadRageClick ( frame as SlowClickFrame ) ) )
487
+ )
488
+ ) ;
489
+ const spans = this . _sortedSpanFrames . filter ( frame =>
490
+ frame . op . startsWith ( 'navigation.' )
491
+ ) ;
492
+ const uniqueCrumbs = removeDuplicateNavCrumbs ( crumbs , spans ) ;
493
+ return [ ...uniqueCrumbs , ...spans ] . sort ( sortFrames ) ;
494
+ } ) ;
463
495
464
496
getLPCFrames = memoize ( ( ) => this . _sortedSpanFrames . filter ( isLCPFrame ) ) ;
465
497
0 commit comments