Skip to content

Commit 0c710db

Browse files
committed
Ensure we can detect that an anchor was clicked upon, even if it's dimensions are incorrectly reported by getBoundingClientRect, as the latter doesn't take into account pseudo elements (::before/::after) which can be absolutely positioned and be larger than the parent anchor - see Shopify default products
1 parent edf43d1 commit 0c710db

File tree

2 files changed

+36
-21
lines changed

2 files changed

+36
-21
lines changed

packages/rrweb/src/record/observer.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
legacy_isTouchEvent,
1818
StyleSheetMirror,
1919
nowTimestamp,
20+
closestElementOfNode,
2021
} from '../utils';
2122
import { patch } from '@rrweb/utils';
2223
import type { observerParam, MutationBufferParam } from '../types';
@@ -401,9 +402,11 @@ function initMouseInteractionObserver({
401402

402403
let target;
403404
let htargetBound = null;
405+
let significantEl: HTMLElement | null = null;
404406

405407
if (!clientX) {
406408
target = getEventTarget(event) as Node;
409+
significantEl = closestElementOfNode(target);
407410
} else {
408411
// copy of getEventTarget taking into account clientX/clientY
409412
// (this seems redundant now)
@@ -426,8 +429,13 @@ function initMouseInteractionObserver({
426429
const node = maybeNode as Node;
427430
if (node.nodeType === Node.DOCUMENT_NODE) {
428431
target = node;
432+
significantEl = null;
429433
break;
430434
}
435+
if (!significantEl) {
436+
// first parent in path, even if it isn't within bounds
437+
significantEl = closestElementOfNode(node);
438+
}
431439
htargetBound = (node as HTMLElement).getBoundingClientRect();
432440
if (
433441
htargetBound.left < clientX &&
@@ -441,6 +449,7 @@ function initMouseInteractionObserver({
441449
}
442450
} catch {
443451
target = event.target as Node;
452+
significantEl = closestElementOfNode(target);
444453
}
445454
}
446455

@@ -466,43 +475,48 @@ function initMouseInteractionObserver({
466475
let src: string | null = null;
467476
let targetText: string | null = null;
468477

469-
let sig_target =
470-
event.target.closest &&
471-
event.target.closest(
478+
if (significantEl) {
479+
significantEl = significantEl.closest(
472480
'a[href],area[href],button,input[type="submit"],input[type="button"]',
473481
);
474-
if (!sig_target) {
475-
sig_target = htarget;
482+
}
483+
if (!significantEl) {
484+
significantEl = htarget;
485+
} else if (htarget !== significantEl && htarget.contains(significantEl)) {
486+
emissionEvent = {
487+
...emissionEvent,
488+
sigTargetInternal: true,
489+
};
476490
}
477491

478-
if (!sig_target.tagName) {
492+
if (!significantEl.tagName) {
479493
// could be the #document element
480-
} else if (sig_target.tagName.toLowerCase() === 'a') {
481-
const a_target = sig_target as HTMLAnchorElement;
494+
} else if (significantEl.tagName.toLowerCase() === 'a') {
495+
const a_target = significantEl as HTMLAnchorElement;
482496
if (a_target.href) {
483497
href = a_target.href; // this is worse for matching as resolves to a full absolute URL
484498
hrefAttr = a_target.getAttribute('href');
485499
}
486500
targetText = a_target.innerText.substring(0, 40);
487501
} else if (
488-
sig_target.tagName.toLowerCase() === 'area' &&
489-
(sig_target as HTMLAreaElement).href
502+
significantEl.tagName.toLowerCase() === 'area' &&
503+
(significantEl as HTMLAreaElement).href
490504
) {
491-
const area_target = sig_target as HTMLAreaElement;
505+
const area_target = significantEl as HTMLAreaElement;
492506
href = area_target.href; // this is worse for matching as resolves to a full absolute URL
493507
hrefAttr = area_target.getAttribute('href');
494-
} else if (sig_target.tagName.toLowerCase() === 'button') {
495-
const button_target = sig_target as HTMLButtonElement;
508+
} else if (significantEl.tagName.toLowerCase() === 'button') {
509+
const button_target = significantEl as HTMLButtonElement;
496510
targetText = button_target.innerText.substring(0, 40);
497511
} else if (
498-
sig_target.tagName.toLowerCase() === 'input' &&
499-
((sig_target as HTMLInputElement).type === 'submit' ||
500-
(sig_target as HTMLInputElement).type === 'button')
512+
significantEl.tagName.toLowerCase() === 'input' &&
513+
((significantEl as HTMLInputElement).type === 'submit' ||
514+
(significantEl as HTMLInputElement).type === 'button')
501515
) {
502-
const button_target = sig_target as HTMLInputElement;
516+
const button_target = significantEl as HTMLInputElement;
503517
targetText = button_target.value.substring(0, 40);
504-
} else if (sig_target.tagName.toLowerCase() === 'img') {
505-
const image_target = sig_target as HTMLImageElement;
518+
} else if (significantEl.tagName.toLowerCase() === 'img') {
519+
const image_target = significantEl as HTMLImageElement;
506520
src = image_target.src;
507521
}
508522
emissionEvent = {
@@ -520,10 +534,10 @@ function initMouseInteractionObserver({
520534
};
521535
}
522536

523-
if (htarget !== sig_target) {
537+
if (htarget !== significantEl) {
524538
emissionEvent = {
525539
...emissionEvent,
526-
sigTargetTagName: sig_target.tagName,
540+
sigTargetTagName: significantEl.tagName,
527541
};
528542
}
529543

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ export type clickParam = mouseInteractionParam &
534534
targetTagName?: string;
535535
targetClasses?: string[];
536536
sigTargetTagName?: string;
537+
sigTargetInternal?: boolean;
537538
altTargetSelector?: string;
538539
byIdTargetSelector?: string;
539540
structuralTargetSelector?: string;

0 commit comments

Comments
 (0)