@sentry/replay 7.52.0 → 7.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.js CHANGED
@@ -3372,6 +3372,141 @@ function addBreadcrumbEvent(replay, breadcrumb) {
3372
3372
  });
3373
3373
  }
3374
3374
 
3375
+ /**
3376
+ * Detect a slow click on a button/a tag,
3377
+ * and potentially create a corresponding breadcrumb.
3378
+ */
3379
+ function detectSlowClick(
3380
+ replay,
3381
+ config,
3382
+ clickBreadcrumb,
3383
+ node,
3384
+ ) {
3385
+ if (ignoreElement(node, config)) {
3386
+ return;
3387
+ }
3388
+
3389
+ /*
3390
+ We consider a slow click a click on a button/a, which does not trigger one of:
3391
+ - DOM mutation
3392
+ - Scroll (within 100ms)
3393
+ Within the given threshold time.
3394
+ After time timeout time, we stop listening and mark it as a slow click anyhow.
3395
+ */
3396
+
3397
+ let cleanup = () => {
3398
+ // replaced further down
3399
+ };
3400
+
3401
+ // After timeout time, def. consider this a slow click, and stop watching for mutations
3402
+ const timeout = setTimeout(() => {
3403
+ handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
3404
+ cleanup();
3405
+ }, config.timeout);
3406
+
3407
+ const mutationHandler = () => {
3408
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
3409
+ cleanup();
3410
+ };
3411
+
3412
+ const scrollHandler = () => {
3413
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
3414
+ cleanup();
3415
+ };
3416
+
3417
+ const obs = new MutationObserver(mutationHandler);
3418
+
3419
+ obs.observe(WINDOW.document.documentElement, {
3420
+ attributes: true,
3421
+ characterData: true,
3422
+ childList: true,
3423
+ subtree: true,
3424
+ });
3425
+
3426
+ WINDOW.addEventListener('scroll', scrollHandler);
3427
+
3428
+ // Stop listening to scroll timeouts early
3429
+ const scrollTimeout = setTimeout(() => {
3430
+ WINDOW.removeEventListener('scroll', scrollHandler);
3431
+ }, config.scrollTimeout);
3432
+
3433
+ cleanup = () => {
3434
+ clearTimeout(timeout);
3435
+ clearTimeout(scrollTimeout);
3436
+ obs.disconnect();
3437
+ WINDOW.removeEventListener('scroll', scrollHandler);
3438
+ };
3439
+ }
3440
+
3441
+ function maybeHandleSlowClick(
3442
+ replay,
3443
+ clickBreadcrumb,
3444
+ threshold,
3445
+ timeout,
3446
+ endReason,
3447
+ ) {
3448
+ const now = Date.now();
3449
+ const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
3450
+
3451
+ if (timeAfterClickMs > threshold) {
3452
+ handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
3453
+ return true;
3454
+ }
3455
+
3456
+ return false;
3457
+ }
3458
+
3459
+ function handleSlowClick(
3460
+ replay,
3461
+ clickBreadcrumb,
3462
+ timeAfterClickMs,
3463
+ endReason,
3464
+ ) {
3465
+ const breadcrumb = {
3466
+ message: clickBreadcrumb.message,
3467
+ timestamp: clickBreadcrumb.timestamp,
3468
+ category: 'ui.slowClickDetected',
3469
+ data: {
3470
+ ...clickBreadcrumb.data,
3471
+ url: WINDOW.location.href,
3472
+ // TODO FN: add parametrized route, when possible
3473
+ timeAfterClickMs,
3474
+ endReason,
3475
+ },
3476
+ };
3477
+
3478
+ addBreadcrumbEvent(replay, breadcrumb);
3479
+ }
3480
+
3481
+ const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
3482
+
3483
+ function ignoreElement(node, config) {
3484
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
3485
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
3486
+ return true;
3487
+ }
3488
+
3489
+ if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
3490
+ return true;
3491
+ }
3492
+
3493
+ // If <a> tag, detect special variants that may not lead to an action
3494
+ // If target !== _self, we may open the link somewhere else, which would lead to no action
3495
+ // Also, when downloading a file, we may not leave the page, but still not trigger an action
3496
+ if (
3497
+ node.tagName === 'A' &&
3498
+ (node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
3499
+ ) {
3500
+ return true;
3501
+ }
3502
+
3503
+ if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
3504
+ return true;
3505
+ }
3506
+
3507
+ return false;
3508
+ }
3509
+
3375
3510
  // Note that these are the serialized attributes and not attributes directly on
3376
3511
  // the DOM Node. Attributes we are interested in:
3377
3512
  const ATTRIBUTES_TO_RECORD = new Set([
@@ -3406,9 +3541,21 @@ function getAttributesToRecord(attributes) {
3406
3541
  return obj;
3407
3542
  }
3408
3543
 
3409
- const handleDomListener =
3410
- (replay) =>
3411
- (handlerData) => {
3544
+ const handleDomListener = (
3545
+ replay,
3546
+ ) => {
3547
+ const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
3548
+
3549
+ const slowClickConfig = slowClickExperiment
3550
+ ? {
3551
+ threshold: slowClickExperiment.threshold,
3552
+ timeout: slowClickExperiment.timeout,
3553
+ scrollTimeout: slowClickExperiment.scrollTimeout,
3554
+ ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
3555
+ }
3556
+ : undefined;
3557
+
3558
+ return (handlerData) => {
3412
3559
  if (!replay.isEnabled()) {
3413
3560
  return;
3414
3561
  }
@@ -3419,8 +3566,21 @@ const handleDomListener =
3419
3566
  return;
3420
3567
  }
3421
3568
 
3569
+ const isClick = handlerData.name === 'click';
3570
+ const event = isClick && (handlerData.event );
3571
+ // Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
3572
+ if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
3573
+ detectSlowClick(
3574
+ replay,
3575
+ slowClickConfig,
3576
+ result ,
3577
+ getClickTargetNode(handlerData.event) ,
3578
+ );
3579
+ }
3580
+
3422
3581
  addBreadcrumbEvent(replay, result);
3423
3582
  };
3583
+ };
3424
3584
 
3425
3585
  /** Get the base DOM breadcrumb. */
3426
3586
  function getBaseDomBreadcrumb(target, message) {