mixpanel-browser 2.70.0 → 2.71.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.
Files changed (38) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/CHANGELOG.md +11 -0
  3. package/build.sh +1 -0
  4. package/dist/mixpanel-core.cjs.d.ts +440 -0
  5. package/dist/mixpanel-core.cjs.js +849 -50
  6. package/dist/mixpanel-recorder.js +5 -3
  7. package/dist/mixpanel-recorder.min.js +1 -1
  8. package/dist/mixpanel-recorder.min.js.map +1 -1
  9. package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
  10. package/dist/mixpanel-with-async-recorder.cjs.js +849 -50
  11. package/dist/mixpanel-with-recorder.d.ts +440 -0
  12. package/dist/mixpanel-with-recorder.js +853 -52
  13. package/dist/mixpanel-with-recorder.min.d.ts +440 -0
  14. package/dist/mixpanel-with-recorder.min.js +1 -1
  15. package/dist/mixpanel.amd.d.ts +440 -0
  16. package/dist/mixpanel.amd.js +853 -52
  17. package/dist/mixpanel.cjs.d.ts +440 -0
  18. package/dist/mixpanel.cjs.js +853 -52
  19. package/dist/mixpanel.globals.js +849 -50
  20. package/dist/mixpanel.min.js +170 -153
  21. package/dist/mixpanel.module.d.ts +440 -0
  22. package/dist/mixpanel.module.js +853 -52
  23. package/dist/mixpanel.umd.d.ts +440 -0
  24. package/dist/mixpanel.umd.js +853 -52
  25. package/dist/rrweb-compiled.js +4 -2
  26. package/package.json +2 -19
  27. package/rollup.config.mjs +28 -4
  28. package/src/autocapture/deadclick.js +254 -0
  29. package/src/autocapture/index.js +239 -41
  30. package/src/autocapture/shadow-dom-observer.js +100 -0
  31. package/src/autocapture/utils.js +230 -3
  32. package/src/config.js +1 -1
  33. package/src/flags/index.js +32 -12
  34. package/src/index.d.ts +16 -3
  35. package/src/loaders/loader-module-core.d.ts +1 -0
  36. package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
  37. package/src/loaders/loader-module.d.ts +1 -0
  38. package/src/utils.js +15 -0
@@ -1,11 +1,12 @@
1
1
  import { _, document, safewrap, safewrapClass } from '../utils';
2
2
  import { window } from '../window';
3
3
  import {
4
- getPropsForDOMEvent, logger, minDOMApisSupported,
5
- EV_CHANGE, EV_CLICK, EV_HASHCHANGE, EV_MP_LOCATION_CHANGE, EV_POPSTATE,
6
- EV_SCROLLEND, EV_SUBMIT
4
+ getPolyfillScrollEndFunction, getPropsForDOMEvent, logger, minDOMApisSupported,
5
+ EV_CHANGE, EV_CLICK, EV_HASHCHANGE, EV_MP_LOCATION_CHANGE, EV_LOAD,
6
+ EV_POPSTATE, EV_SCROLL, EV_SCROLLEND, EV_SUBMIT, EV_VISIBILITYCHANGE
7
7
  } from './utils';
8
8
  import { RageClickTracker } from './rageclick';
9
+ import { DeadClickTracker, DEFAULT_DEAD_CLICK_TIMEOUT_MS } from './deadclick';
9
10
 
10
11
  var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
11
12
  var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
@@ -26,10 +27,12 @@ var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
26
27
  var CONFIG_SCROLL_CAPTURE_ALL = 'scroll_capture_all';
27
28
  var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
28
29
  var CONFIG_TRACK_CLICK = 'click';
30
+ var CONFIG_TRACK_DEAD_CLICK = 'dead_click';
29
31
  var CONFIG_TRACK_INPUT = 'input';
30
32
  var CONFIG_TRACK_PAGEVIEW = 'pageview';
31
33
  var CONFIG_TRACK_RAGE_CLICK = 'rage_click';
32
34
  var CONFIG_TRACK_SCROLL = 'scroll';
35
+ var CONFIG_TRACK_PAGE_LEAVE = 'page_leave';
33
36
  var CONFIG_TRACK_SUBMIT = 'submit';
34
37
 
35
38
  var CONFIG_DEFAULTS = {};
@@ -44,10 +47,12 @@ CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
44
47
  CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
45
48
  CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
46
49
  CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
50
+ CONFIG_DEFAULTS[CONFIG_TRACK_DEAD_CLICK] = true;
47
51
  CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
48
52
  CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
49
53
  CONFIG_DEFAULTS[CONFIG_TRACK_RAGE_CLICK] = true;
50
54
  CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
55
+ CONFIG_DEFAULTS[CONFIG_TRACK_PAGE_LEAVE] = false;
51
56
  CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
52
57
 
53
58
  var DEFAULT_PROPS = {
@@ -55,10 +60,12 @@ var DEFAULT_PROPS = {
55
60
  };
56
61
 
57
62
  var MP_EV_CLICK = '$mp_click';
63
+ var MP_EV_DEAD_CLICK = '$mp_dead_click';
58
64
  var MP_EV_INPUT = '$mp_input_change';
59
65
  var MP_EV_RAGE_CLICK = '$mp_rage_click';
60
66
  var MP_EV_SCROLL = '$mp_scroll';
61
67
  var MP_EV_SUBMIT = '$mp_submit';
68
+ var MP_EV_PAGE_LEAVE = '$mp_page_leave';
62
69
 
63
70
  /**
64
71
  * Autocapture: manages automatic event tracking
@@ -66,6 +73,9 @@ var MP_EV_SUBMIT = '$mp_submit';
66
73
  */
67
74
  var Autocapture = function(mp) {
68
75
  this.mp = mp;
76
+ this.maxScrollViewDepth = 0;
77
+ this.hasTrackedScrollSession = false;
78
+ this.previousScrollHeight = 0;
69
79
  };
70
80
 
71
81
  Autocapture.prototype.init = function() {
@@ -73,13 +83,15 @@ Autocapture.prototype.init = function() {
73
83
  logger.critical('Autocapture unavailable: missing required DOM APIs');
74
84
  return;
75
85
  }
76
-
86
+ this.initPageListeners();
77
87
  this.initPageviewTracking();
78
88
  this.initClickTracking();
89
+ this.initDeadClickTracking();
79
90
  this.initInputTracking();
80
91
  this.initScrollTracking();
81
92
  this.initSubmitTracking();
82
93
  this.initRageClickTracking();
94
+ this.initPageLeaveTracking();
83
95
  };
84
96
 
85
97
  Autocapture.prototype.getFullConfig = function() {
@@ -160,7 +172,8 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
160
172
 
161
173
  var isCapturedForHeatMap = this.mp.is_recording_heatmap_data() && (
162
174
  (mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK)) ||
163
- (mpEventName === MP_EV_RAGE_CLICK && !this._getRageClickConfig())
175
+ (mpEventName === MP_EV_RAGE_CLICK && !this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK)) ||
176
+ (mpEventName === MP_EV_DEAD_CLICK && !this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK))
164
177
  );
165
178
 
166
179
  var props = getPropsForDOMEvent(ev, {
@@ -179,11 +192,45 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
179
192
  }
180
193
  };
181
194
 
182
- Autocapture.prototype._getRageClickConfig = function() {
183
- var config = this.getConfig(CONFIG_TRACK_RAGE_CLICK);
195
+ Autocapture.prototype.initPageListeners = function() {
196
+ window.removeEventListener(EV_POPSTATE, this.listenerPopstate);
197
+ window.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
198
+
199
+ if (!this.pageviewTrackingConfig() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
200
+ // These are all the configs that use these listeners
201
+ return;
202
+ }
203
+
204
+ this.listenerPopstate = function() {
205
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
206
+ };
207
+ this.listenerHashchange = function() {
208
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
209
+ };
210
+
211
+ window.addEventListener(EV_POPSTATE, this.listenerPopstate);
212
+ window.addEventListener(EV_HASHCHANGE, this.listenerHashchange);
213
+ var nativePushState = window.history.pushState;
214
+ if (typeof nativePushState === 'function') {
215
+ window.history.pushState = function(state, unused, url) {
216
+ nativePushState.call(window.history, state, unused, url);
217
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
218
+ };
219
+ }
220
+ var nativeReplaceState = window.history.replaceState;
221
+ if (typeof nativeReplaceState === 'function') {
222
+ window.history.replaceState = function(state, unused, url) {
223
+ nativeReplaceState.call(window.history, state, unused, url);
224
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
225
+ };
226
+ }
227
+ };
228
+
229
+ Autocapture.prototype._getClickTrackingConfig = function(configKey) {
230
+ var config = this.getConfig(configKey);
184
231
 
185
232
  if (!config) {
186
- return null; // rage click tracking disabled
233
+ return null; // click tracking disabled
187
234
  }
188
235
 
189
236
  if (config === true) {
@@ -197,6 +244,70 @@ Autocapture.prototype._getRageClickConfig = function() {
197
244
  return {}; // fallback to defaults for any other truthy value
198
245
  };
199
246
 
247
+ Autocapture.prototype._trackPageLeave = function(ev, currentUrl, currentScrollHeight) {
248
+ if (this.hasTrackedScrollSession) {
249
+ // User has navigated away already ending their impression.
250
+ return;
251
+ }
252
+
253
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.is_recording_heatmap_data()) {
254
+ return;
255
+ }
256
+
257
+ this.hasTrackedScrollSession = true;
258
+ var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
259
+ var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
260
+ var foldLinePercentage = Math.round((viewportHeight / currentScrollHeight) * 100);
261
+ if (currentScrollHeight <= viewportHeight) {
262
+ // If the content fits within the viewport, consider it fully scrolled
263
+ scrollPercentage = 100;
264
+ foldLinePercentage = 100;
265
+ }
266
+
267
+ var props = _.extend({
268
+ '$max_scroll_view_depth': this.maxScrollViewDepth,
269
+ '$max_scroll_percentage': scrollPercentage,
270
+ '$fold_line_percentage': foldLinePercentage,
271
+ '$scroll_height': currentScrollHeight,
272
+ '$event_type': ev.type,
273
+ '$current_url': currentUrl || _.info.currentUrl(),
274
+ '$viewportHeight': viewportHeight, // This is the fold line
275
+ '$viewportWidth': Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
276
+ '$captured_for_heatmap': this.mp.is_recording_heatmap_data()
277
+ }, DEFAULT_PROPS);
278
+
279
+ // Send with beacon transport to ensure event is sent before unload
280
+ this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
281
+ };
282
+
283
+ Autocapture.prototype._initScrollDepthTracking = function() {
284
+ window.removeEventListener(EV_SCROLL, this.listenerScrollDepth);
285
+ window.removeEventListener(EV_SCROLLEND, this.listenerScrollDepth);
286
+
287
+ if (!this.mp.get_config('record_heatmap_data')) {
288
+ return;
289
+ }
290
+
291
+ logger.log('Initializing scroll depth tracking');
292
+
293
+ this.maxScrollViewDepth = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
294
+
295
+ var updateScrollDepth = function() {
296
+ if (this.currentUrlBlocked()) {
297
+ return;
298
+ }
299
+ var scrollViewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) + window.scrollY;
300
+ if (scrollViewHeight > this.maxScrollViewDepth) {
301
+ this.maxScrollViewDepth = scrollViewHeight;
302
+ }
303
+ this.previousScrollHeight = document.body.scrollHeight;
304
+ }.bind(this);
305
+
306
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(updateScrollDepth);
307
+ this.listenerScrollDepth = scrollEndPolyfill.listener;
308
+ window.addEventListener(scrollEndPolyfill.eventType, this.listenerScrollDepth);
309
+ };
310
+
200
311
  Autocapture.prototype.initClickTracking = function() {
201
312
  window.removeEventListener(EV_CLICK, this.listenerClick);
202
313
 
@@ -205,12 +316,49 @@ Autocapture.prototype.initClickTracking = function() {
205
316
  }
206
317
  logger.log('Initializing click tracking');
207
318
 
208
- this.listenerClick = window.addEventListener(EV_CLICK, function(ev) {
319
+ this.listenerClick = function(ev) {
209
320
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
210
321
  return;
211
322
  }
212
323
  this.trackDomEvent(ev, MP_EV_CLICK);
213
- }.bind(this));
324
+ }.bind(this);
325
+ window.addEventListener(EV_CLICK, this.listenerClick);
326
+ };
327
+
328
+ Autocapture.prototype.initDeadClickTracking = function() {
329
+ var deadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
330
+
331
+ if (!deadClickConfig && !this.mp.get_config('record_heatmap_data')) {
332
+ this.stopDeadClickTracking();
333
+ return;
334
+ }
335
+
336
+ logger.log('Initializing dead click tracking');
337
+ if (!this._deadClickTracker) {
338
+ this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
339
+ this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
340
+ }.bind(this));
341
+ this._deadClickTracker.startTracking();
342
+ }
343
+
344
+ if (!this.listenerDeadClick) {
345
+ this.listenerDeadClick = function(ev) {
346
+ var currentDeadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
347
+ if (!currentDeadClickConfig && !this.mp.is_recording_heatmap_data()) {
348
+ return;
349
+ }
350
+ if (this.currentUrlBlocked()) {
351
+ return;
352
+ }
353
+ // Normalize config to ensure timeout_ms is always set
354
+ var normalizedConfig = currentDeadClickConfig || {};
355
+ if (!normalizedConfig['timeout_ms']) {
356
+ normalizedConfig['timeout_ms'] = DEFAULT_DEAD_CLICK_TIMEOUT_MS;
357
+ }
358
+ this._deadClickTracker.trackClick(ev, normalizedConfig);
359
+ }.bind(this);
360
+ window.addEventListener(EV_CLICK, this.listenerDeadClick);
361
+ }
214
362
  };
215
363
 
216
364
  Autocapture.prototype.initInputTracking = function() {
@@ -221,17 +369,16 @@ Autocapture.prototype.initInputTracking = function() {
221
369
  }
222
370
  logger.log('Initializing input tracking');
223
371
 
224
- this.listenerChange = window.addEventListener(EV_CHANGE, function(ev) {
372
+ this.listenerChange = function(ev) {
225
373
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
226
374
  return;
227
375
  }
228
376
  this.trackDomEvent(ev, MP_EV_INPUT);
229
- }.bind(this));
377
+ }.bind(this);
378
+ window.addEventListener(EV_CHANGE, this.listenerChange);
230
379
  };
231
380
 
232
381
  Autocapture.prototype.initPageviewTracking = function() {
233
- window.removeEventListener(EV_POPSTATE, this.listenerPopstate);
234
- window.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
235
382
  window.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
236
383
 
237
384
  if (!this.pageviewTrackingConfig()) {
@@ -248,27 +395,7 @@ Autocapture.prototype.initPageviewTracking = function() {
248
395
  previousTrackedUrl = _.info.currentUrl();
249
396
  }
250
397
 
251
- this.listenerPopstate = window.addEventListener(EV_POPSTATE, function() {
252
- window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
253
- });
254
- this.listenerHashchange = window.addEventListener(EV_HASHCHANGE, function() {
255
- window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
256
- });
257
- var nativePushState = window.history.pushState;
258
- if (typeof nativePushState === 'function') {
259
- window.history.pushState = function(state, unused, url) {
260
- nativePushState.call(window.history, state, unused, url);
261
- window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
262
- };
263
- }
264
- var nativeReplaceState = window.history.replaceState;
265
- if (typeof nativeReplaceState === 'function') {
266
- window.history.replaceState = function(state, unused, url) {
267
- nativeReplaceState.call(window.history, state, unused, url);
268
- window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
269
- };
270
- }
271
- this.listenerLocationchange = window.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
398
+ this.listenerLocationchange = safewrap(function() {
272
399
  if (this.currentUrlBlocked()) {
273
400
  return;
274
401
  }
@@ -295,13 +422,14 @@ Autocapture.prototype.initPageviewTracking = function() {
295
422
  logger.log('Path change: re-initializing scroll depth checkpoints');
296
423
  }
297
424
  }
298
- }.bind(this)));
425
+ }.bind(this));
426
+ window.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
299
427
  };
300
428
 
301
429
  Autocapture.prototype.initRageClickTracking = function() {
302
430
  window.removeEventListener(EV_CLICK, this.listenerRageClick);
303
431
 
304
- var rageClickConfig = this._getRageClickConfig();
432
+ var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
305
433
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
306
434
  return;
307
435
  }
@@ -312,7 +440,7 @@ Autocapture.prototype.initRageClickTracking = function() {
312
440
  }
313
441
 
314
442
  this.listenerRageClick = function(ev) {
315
- var currentRageClickConfig = this._getRageClickConfig();
443
+ var currentRageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
316
444
  if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
317
445
  return;
318
446
  }
@@ -330,6 +458,8 @@ Autocapture.prototype.initRageClickTracking = function() {
330
458
 
331
459
  Autocapture.prototype.initScrollTracking = function() {
332
460
  window.removeEventListener(EV_SCROLLEND, this.listenerScroll);
461
+ window.removeEventListener(EV_SCROLL, this.listenerScroll);
462
+
333
463
 
334
464
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
335
465
  return;
@@ -337,7 +467,7 @@ Autocapture.prototype.initScrollTracking = function() {
337
467
  logger.log('Initializing scroll tracking');
338
468
  this.lastScrollCheckpoint = 0;
339
469
 
340
- this.listenerScroll = window.addEventListener(EV_SCROLLEND, safewrap(function() {
470
+ var scrollTrackFunction = function() {
341
471
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
342
472
  return;
343
473
  }
@@ -376,7 +506,11 @@ Autocapture.prototype.initScrollTracking = function() {
376
506
  if (shouldTrack) {
377
507
  this.mp.track(MP_EV_SCROLL, props);
378
508
  }
379
- }.bind(this)));
509
+ }.bind(this);
510
+
511
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(scrollTrackFunction);
512
+ this.listenerScroll = scrollEndPolyfill.listener;
513
+ window.addEventListener(scrollEndPolyfill.eventType, this.listenerScroll);
380
514
  };
381
515
 
382
516
  Autocapture.prototype.initSubmitTracking = function() {
@@ -387,12 +521,76 @@ Autocapture.prototype.initSubmitTracking = function() {
387
521
  }
388
522
  logger.log('Initializing submit tracking');
389
523
 
390
- this.listenerSubmit = window.addEventListener(EV_SUBMIT, function(ev) {
524
+ this.listenerSubmit = function(ev) {
391
525
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
392
526
  return;
393
527
  }
394
528
  this.trackDomEvent(ev, MP_EV_SUBMIT);
529
+ }.bind(this);
530
+ window.addEventListener(EV_SUBMIT, this.listenerSubmit);
531
+ };
532
+
533
+ Autocapture.prototype.initPageLeaveTracking = function() {
534
+ // Capture page_leave both when the user navigates away from the page (visibilitychange) as well
535
+ // as when they navigate to a different page within the SPA (popstate/pushstate/hashchange).
536
+ document.removeEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
537
+ window.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
538
+ window.removeEventListener(EV_LOAD, this.listenerPageLoad);
539
+
540
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
541
+ return;
542
+ }
543
+
544
+ logger.log('Initializing page visibility tracking.');
545
+ this._initScrollDepthTracking();
546
+ var previousTrackedUrl = _.info.currentUrl();
547
+
548
+ // Initialize previousScrollHeight on `load` which handles async loading
549
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
550
+ this.listenerPageLoad = function() {
551
+ this.previousScrollHeight = document.body.scrollHeight;
552
+ }.bind(this);
553
+ window.addEventListener(EV_LOAD, this.listenerPageLoad);
554
+
555
+ // Track page navigation events similar to how initPageviewTracking does it
556
+ this.listenerPageLeaveLocationchange = safewrap(function(ev) {
557
+ if (this.currentUrlBlocked()) {
558
+ return;
559
+ }
560
+
561
+ var currentUrl = _.info.currentUrl();
562
+ // Track all URL changes including query string or fragment changes as separate scroll sessions
563
+ var shouldTrack = currentUrl !== previousTrackedUrl;
564
+
565
+ if (shouldTrack) {
566
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
567
+ previousTrackedUrl = currentUrl;
568
+ // Fragment navigation should call scroll(end) and trigger listener, don't add window.scrollY here.
569
+ this.maxScrollViewDepth = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
570
+ this.previousScrollHeight = document.body.scrollHeight;
571
+ this.hasTrackedScrollSession = false;
572
+ }
395
573
  }.bind(this));
574
+ window.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
575
+
576
+ this.listenerPageLeaveVisibilitychange = function(ev) {
577
+ if (document.hidden) {
578
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
579
+ }
580
+ }.bind(this);
581
+ document.addEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
582
+ };
583
+
584
+ Autocapture.prototype.stopDeadClickTracking = function() {
585
+ if (this.listenerDeadClick) {
586
+ window.removeEventListener(EV_CLICK, this.listenerDeadClick);
587
+ this.listenerDeadClick = null;
588
+ }
589
+
590
+ if (this._deadClickTracker) {
591
+ this._deadClickTracker.stopTracking();
592
+ this._deadClickTracker = null;
593
+ }
396
594
  };
397
595
 
398
596
  // TODO integrate error_reporter from mixpanel instance
@@ -0,0 +1,100 @@
1
+ import { logger, weakSetSupported } from './utils';
2
+
3
+ function ShadowDOMObserver(changeCallback, observerConfig) {
4
+ this.changeCallback = changeCallback || function() {};
5
+ this.observerConfig = observerConfig;
6
+
7
+ this.observedShadowRoots = null;
8
+ this.shadowObservers = [];
9
+ }
10
+
11
+ ShadowDOMObserver.prototype.getEventTarget = function(event) {
12
+ if (!this.observedShadowRoots) {
13
+ return;
14
+ }
15
+ var path = this.getComposedPath(event);
16
+ if (path && path.length) {
17
+ return path[0];
18
+ }
19
+
20
+ return event['target'] || event['srcElement'];
21
+ };
22
+
23
+
24
+ ShadowDOMObserver.prototype.getComposedPath = function(event) {
25
+ if ('composedPath' in event) {
26
+ return event['composedPath']();
27
+ }
28
+
29
+ return [];
30
+ };
31
+ ShadowDOMObserver.prototype.observeFromEvent = function(event) {
32
+ if (!this.observedShadowRoots) {
33
+ return;
34
+ }
35
+
36
+ var path = this.getComposedPath(event);
37
+
38
+ // Check each element in path for shadow roots
39
+ for (var i = 0; i < path.length; i++) {
40
+ var element = path[i];
41
+
42
+ if (element && element.shadowRoot) {
43
+ this.observeShadowRoot(element.shadowRoot);
44
+ }
45
+ }
46
+ };
47
+
48
+
49
+ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
50
+ if (!this.observedShadowRoots || this.observedShadowRoots.has(shadowRoot)) {
51
+ return;
52
+ }
53
+
54
+ var self = this;
55
+
56
+ try {
57
+ this.observedShadowRoots.add(shadowRoot);
58
+
59
+ var observer = new window.MutationObserver(function() {
60
+ self.changeCallback();
61
+ });
62
+
63
+ observer.observe(shadowRoot, this.observerConfig);
64
+ this.shadowObservers.push(observer);
65
+ } catch (e) {
66
+ logger.critical('Error while observing shadow root', e);
67
+ }
68
+ };
69
+
70
+
71
+ ShadowDOMObserver.prototype.start = function() {
72
+ if (this.observedShadowRoots) {
73
+ return;
74
+ }
75
+
76
+ if (!weakSetSupported()) {
77
+ logger.critical('Shadow DOM observation unavailable: WeakSet not supported');
78
+ return;
79
+ }
80
+
81
+ this.observedShadowRoots = new WeakSet();
82
+ };
83
+
84
+ ShadowDOMObserver.prototype.stop = function() {
85
+ if (!this.observedShadowRoots) {
86
+ return;
87
+ }
88
+
89
+ for (var i = 0; i < this.shadowObservers.length; i++) {
90
+ try {
91
+ this.shadowObservers[i].disconnect();
92
+ } catch (e) {
93
+ logger.critical('Error while disconnecting shadow DOM observer', e);
94
+ }
95
+ }
96
+ this.shadowObservers = [];
97
+ this.observedShadowRoots = null;
98
+ };
99
+
100
+ export { ShadowDOMObserver };