Skip to content

Commit 5ad616d

Browse files
committed
ref: improve detection of multi clicks
1 parent dbd590c commit 5ad616d

File tree

2 files changed

+29
-23
lines changed

2 files changed

+29
-23
lines changed

packages/replay/src/coreHandlers/handleClick.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import { getClickTargetNode } from './util/domUtils';
77

88
type ClickBreadcrumb = Breadcrumb & {
99
timestamp: number;
10-
data: { nodeId: number };
1110
};
1211

1312
interface Click {
1413
timestamp: number;
15-
nodeId: number;
1614
mutationAfter?: number;
1715
scrollAfter?: number;
1816
clickBreadcrumb: ClickBreadcrumb;
@@ -121,7 +119,7 @@ export class ClickDetector implements ReplayClickDetector {
121119
return;
122120
}
123121

124-
const click = this._getClick(breadcrumb);
122+
const click = this._getClick(node);
125123

126124
if (click) {
127125
// this means a click on the same element was captured in the last 1s, so we consider this a multi click
@@ -130,9 +128,9 @@ export class ClickDetector implements ReplayClickDetector {
130128

131129
const newClick: Click = {
132130
timestamp: breadcrumb.timestamp,
133-
nodeId: breadcrumb.data.nodeId,
134131
clickBreadcrumb: breadcrumb,
135-
clickCount: 1,
132+
// Set this to 0 so we know it originates from the click breadcrumb
133+
clickCount: 0,
136134
node,
137135
};
138136
this._clicks.push(newClick);
@@ -145,27 +143,22 @@ export class ClickDetector implements ReplayClickDetector {
145143

146144
/** Count multiple clicks on elements. */
147145
private _handleMultiClick(node: HTMLElement): void {
148-
const now = nowInSeconds();
149-
const click = this._clicks.find(click => click.node === node && now - click.timestamp < this._multiClickTimeout);
146+
const click = this._getClick(node);
150147

151148
if (!click) {
152149
return;
153150
}
154151

155-
// ignore VERY close timestamps - otherwise we record the initial timestamp twice!
156-
if (click && Math.abs(click.timestamp - nowInSeconds()) > 0.01) {
157-
click.clickCount++;
158-
}
152+
click.clickCount++;
159153
}
160154

161155
/** Try to get an existing click on the given element. */
162-
private _getClick(breadcrumb: ClickBreadcrumb): Click | undefined {
163-
const { nodeId } = breadcrumb.data;
156+
private _getClick(node: HTMLElement): Click | undefined {
164157
const now = nowInSeconds();
165158

166159
// Find any click on the same element in the last second
167160
// If one exists, we consider this click as a double/triple/etc click
168-
return this._clicks.find(click => click.nodeId === nodeId && now - click.timestamp < this._multiClickTimeout);
161+
return this._clicks.find(click => click.node === node && now - click.timestamp < this._multiClickTimeout);
169162
}
170163

171164
/** Check the clicks that happened. */
@@ -182,6 +175,13 @@ export class ClickDetector implements ReplayClickDetector {
182175
click.scrollAfter = click.timestamp <= this._lastScroll ? this._lastScroll - click.timestamp : undefined;
183176
}
184177

178+
// If an action happens within the multi click threshold, we can skip waiting and handle the click right away
179+
const actionTime = click.scrollAfter || click.mutationAfter || 0;
180+
if (actionTime && actionTime <= this._multiClickTimeout) {
181+
timedOutClicks.push(click);
182+
return;
183+
}
184+
185185
if (click.timestamp + this._timeout <= now) {
186186
timedOutClicks.push(click);
187187
}
@@ -230,7 +230,9 @@ export class ClickDetector implements ReplayClickDetector {
230230
route: replay.getCurrentRoute(),
231231
timeAfterClickMs,
232232
endReason,
233-
clickCount,
233+
// If clickCount === 0, it means multiClick was not correctly captured here
234+
// - we still want to send 1 in this case
235+
clickCount: clickCount || 1,
234236
},
235237
};
236238

packages/replay/test/unit/coreHandlers/handleClick.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,12 @@ describe('Unit | coreHandlers | handleClick', () => {
216216
nodeId: 3,
217217
},
218218
};
219-
const node = document.createElement('button');
220-
detector.handleClick(breadcrumb1, node);
221-
detector.handleClick(breadcrumb2, node);
222-
detector.handleClick(breadcrumb3, node);
219+
const node1 = document.createElement('button');
220+
const node2 = document.createElement('button');
221+
const node3 = document.createElement('button');
222+
detector.handleClick(breadcrumb1, node1);
223+
detector.handleClick(breadcrumb2, node2);
224+
detector.handleClick(breadcrumb3, node3);
223225

224226
expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0);
225227

@@ -268,10 +270,12 @@ describe('Unit | coreHandlers | handleClick', () => {
268270
nodeId: 3,
269271
},
270272
};
271-
const node = document.createElement('div');
272-
detector.handleClick(breadcrumb1, node);
273-
detector.handleClick(breadcrumb2, node);
274-
detector.handleClick(breadcrumb3, node);
273+
const node1 = document.createElement('div');
274+
const node2 = document.createElement('div');
275+
const node3 = document.createElement('div');
276+
detector.handleClick(breadcrumb1, node1);
277+
detector.handleClick(breadcrumb2, node2);
278+
detector.handleClick(breadcrumb3, node3);
275279

276280
expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0);
277281

0 commit comments

Comments
 (0)