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