mixpanel-browser 2.69.1 → 2.71.0

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 (37) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/build.sh +1 -0
  3. package/dist/mixpanel-core.cjs.d.ts +440 -0
  4. package/dist/mixpanel-core.cjs.js +857 -56
  5. package/dist/mixpanel-recorder.js +5 -3
  6. package/dist/mixpanel-recorder.min.js +1 -1
  7. package/dist/mixpanel-recorder.min.js.map +1 -1
  8. package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
  9. package/dist/mixpanel-with-async-recorder.cjs.js +857 -56
  10. package/dist/mixpanel-with-recorder.d.ts +440 -0
  11. package/dist/mixpanel-with-recorder.js +861 -58
  12. package/dist/mixpanel-with-recorder.min.d.ts +440 -0
  13. package/dist/mixpanel-with-recorder.min.js +1 -1
  14. package/dist/mixpanel.amd.d.ts +440 -0
  15. package/dist/mixpanel.amd.js +861 -58
  16. package/dist/mixpanel.cjs.d.ts +440 -0
  17. package/dist/mixpanel.cjs.js +861 -58
  18. package/dist/mixpanel.globals.js +857 -56
  19. package/dist/mixpanel.min.js +170 -152
  20. package/dist/mixpanel.module.d.ts +440 -0
  21. package/dist/mixpanel.module.js +861 -58
  22. package/dist/mixpanel.umd.d.ts +440 -0
  23. package/dist/mixpanel.umd.js +861 -58
  24. package/dist/rrweb-compiled.js +4 -2
  25. package/package.json +2 -19
  26. package/rollup.config.mjs +28 -4
  27. package/src/autocapture/deadclick.js +254 -0
  28. package/src/autocapture/index.js +237 -41
  29. package/src/autocapture/shadow-dom-observer.js +100 -0
  30. package/src/autocapture/utils.js +230 -3
  31. package/src/config.js +1 -1
  32. package/src/flags/index.js +43 -18
  33. package/src/index.d.ts +16 -3
  34. package/src/loaders/loader-module-core.d.ts +1 -0
  35. package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
  36. package/src/loaders/loader-module.d.ts +1 -0
  37. 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,68 @@ 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
+ this.hasTrackedScrollSession = true;
253
+ var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
254
+ var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
255
+ var foldLinePercentage = Math.round((viewportHeight / currentScrollHeight) * 100);
256
+ if (currentScrollHeight <= viewportHeight) {
257
+ // If the content fits within the viewport, consider it fully scrolled
258
+ scrollPercentage = 100;
259
+ foldLinePercentage = 100;
260
+ }
261
+
262
+ var props = _.extend({
263
+ '$max_scroll_view_depth': this.maxScrollViewDepth,
264
+ '$max_scroll_percentage': scrollPercentage,
265
+ '$fold_line_percentage': foldLinePercentage,
266
+ '$scroll_height': currentScrollHeight,
267
+ '$event_type': ev.type,
268
+ '$current_url': currentUrl || _.info.currentUrl(),
269
+ '$viewportHeight': viewportHeight, // This is the fold line
270
+ '$viewportWidth': Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
271
+ }, DEFAULT_PROPS);
272
+
273
+ if (this.mp.is_recording_heatmap_data() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE)) {
274
+ props['$captured_for_heatmap'] = true;
275
+ }
276
+
277
+ // Send with beacon transport to ensure event is sent before unload
278
+ this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
279
+ };
280
+
281
+ Autocapture.prototype._initScrollDepthTracking = function() {
282
+ window.removeEventListener(EV_SCROLL, this.listenerScrollDepth);
283
+ window.removeEventListener(EV_SCROLLEND, this.listenerScrollDepth);
284
+
285
+ if (!this.mp.get_config('record_heatmap_data')) {
286
+ return;
287
+ }
288
+
289
+ logger.log('Initializing scroll depth tracking');
290
+
291
+ this.maxScrollViewDepth = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
292
+
293
+ var updateScrollDepth = function() {
294
+ if (this.currentUrlBlocked()) {
295
+ return;
296
+ }
297
+ var scrollViewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) + window.scrollY;
298
+ if (scrollViewHeight > this.maxScrollViewDepth) {
299
+ this.maxScrollViewDepth = scrollViewHeight;
300
+ }
301
+ this.previousScrollHeight = document.body.scrollHeight;
302
+ }.bind(this);
303
+
304
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(updateScrollDepth);
305
+ this.listenerScrollDepth = scrollEndPolyfill.listener;
306
+ window.addEventListener(scrollEndPolyfill.eventType, this.listenerScrollDepth);
307
+ };
308
+
200
309
  Autocapture.prototype.initClickTracking = function() {
201
310
  window.removeEventListener(EV_CLICK, this.listenerClick);
202
311
 
@@ -205,12 +314,49 @@ Autocapture.prototype.initClickTracking = function() {
205
314
  }
206
315
  logger.log('Initializing click tracking');
207
316
 
208
- this.listenerClick = window.addEventListener(EV_CLICK, function(ev) {
317
+ this.listenerClick = function(ev) {
209
318
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
210
319
  return;
211
320
  }
212
321
  this.trackDomEvent(ev, MP_EV_CLICK);
213
- }.bind(this));
322
+ }.bind(this);
323
+ window.addEventListener(EV_CLICK, this.listenerClick);
324
+ };
325
+
326
+ Autocapture.prototype.initDeadClickTracking = function() {
327
+ var deadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
328
+
329
+ if (!deadClickConfig && !this.mp.get_config('record_heatmap_data')) {
330
+ this.stopDeadClickTracking();
331
+ return;
332
+ }
333
+
334
+ logger.log('Initializing dead click tracking');
335
+ if (!this._deadClickTracker) {
336
+ this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
337
+ this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
338
+ }.bind(this));
339
+ this._deadClickTracker.startTracking();
340
+ }
341
+
342
+ if (!this.listenerDeadClick) {
343
+ this.listenerDeadClick = function(ev) {
344
+ var currentDeadClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_DEAD_CLICK);
345
+ if (!currentDeadClickConfig && !this.mp.is_recording_heatmap_data()) {
346
+ return;
347
+ }
348
+ if (this.currentUrlBlocked()) {
349
+ return;
350
+ }
351
+ // Normalize config to ensure timeout_ms is always set
352
+ var normalizedConfig = currentDeadClickConfig || {};
353
+ if (!normalizedConfig['timeout_ms']) {
354
+ normalizedConfig['timeout_ms'] = DEFAULT_DEAD_CLICK_TIMEOUT_MS;
355
+ }
356
+ this._deadClickTracker.trackClick(ev, normalizedConfig);
357
+ }.bind(this);
358
+ window.addEventListener(EV_CLICK, this.listenerDeadClick);
359
+ }
214
360
  };
215
361
 
216
362
  Autocapture.prototype.initInputTracking = function() {
@@ -221,17 +367,16 @@ Autocapture.prototype.initInputTracking = function() {
221
367
  }
222
368
  logger.log('Initializing input tracking');
223
369
 
224
- this.listenerChange = window.addEventListener(EV_CHANGE, function(ev) {
370
+ this.listenerChange = function(ev) {
225
371
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
226
372
  return;
227
373
  }
228
374
  this.trackDomEvent(ev, MP_EV_INPUT);
229
- }.bind(this));
375
+ }.bind(this);
376
+ window.addEventListener(EV_CHANGE, this.listenerChange);
230
377
  };
231
378
 
232
379
  Autocapture.prototype.initPageviewTracking = function() {
233
- window.removeEventListener(EV_POPSTATE, this.listenerPopstate);
234
- window.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
235
380
  window.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
236
381
 
237
382
  if (!this.pageviewTrackingConfig()) {
@@ -248,27 +393,7 @@ Autocapture.prototype.initPageviewTracking = function() {
248
393
  previousTrackedUrl = _.info.currentUrl();
249
394
  }
250
395
 
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() {
396
+ this.listenerLocationchange = safewrap(function() {
272
397
  if (this.currentUrlBlocked()) {
273
398
  return;
274
399
  }
@@ -295,13 +420,14 @@ Autocapture.prototype.initPageviewTracking = function() {
295
420
  logger.log('Path change: re-initializing scroll depth checkpoints');
296
421
  }
297
422
  }
298
- }.bind(this)));
423
+ }.bind(this));
424
+ window.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
299
425
  };
300
426
 
301
427
  Autocapture.prototype.initRageClickTracking = function() {
302
428
  window.removeEventListener(EV_CLICK, this.listenerRageClick);
303
429
 
304
- var rageClickConfig = this._getRageClickConfig();
430
+ var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
305
431
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
306
432
  return;
307
433
  }
@@ -312,7 +438,7 @@ Autocapture.prototype.initRageClickTracking = function() {
312
438
  }
313
439
 
314
440
  this.listenerRageClick = function(ev) {
315
- var currentRageClickConfig = this._getRageClickConfig();
441
+ var currentRageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
316
442
  if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
317
443
  return;
318
444
  }
@@ -330,6 +456,8 @@ Autocapture.prototype.initRageClickTracking = function() {
330
456
 
331
457
  Autocapture.prototype.initScrollTracking = function() {
332
458
  window.removeEventListener(EV_SCROLLEND, this.listenerScroll);
459
+ window.removeEventListener(EV_SCROLL, this.listenerScroll);
460
+
333
461
 
334
462
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
335
463
  return;
@@ -337,7 +465,7 @@ Autocapture.prototype.initScrollTracking = function() {
337
465
  logger.log('Initializing scroll tracking');
338
466
  this.lastScrollCheckpoint = 0;
339
467
 
340
- this.listenerScroll = window.addEventListener(EV_SCROLLEND, safewrap(function() {
468
+ var scrollTrackFunction = function() {
341
469
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
342
470
  return;
343
471
  }
@@ -376,7 +504,11 @@ Autocapture.prototype.initScrollTracking = function() {
376
504
  if (shouldTrack) {
377
505
  this.mp.track(MP_EV_SCROLL, props);
378
506
  }
379
- }.bind(this)));
507
+ }.bind(this);
508
+
509
+ var scrollEndPolyfill = getPolyfillScrollEndFunction(scrollTrackFunction);
510
+ this.listenerScroll = scrollEndPolyfill.listener;
511
+ window.addEventListener(scrollEndPolyfill.eventType, this.listenerScroll);
380
512
  };
381
513
 
382
514
  Autocapture.prototype.initSubmitTracking = function() {
@@ -387,12 +519,76 @@ Autocapture.prototype.initSubmitTracking = function() {
387
519
  }
388
520
  logger.log('Initializing submit tracking');
389
521
 
390
- this.listenerSubmit = window.addEventListener(EV_SUBMIT, function(ev) {
522
+ this.listenerSubmit = function(ev) {
391
523
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
392
524
  return;
393
525
  }
394
526
  this.trackDomEvent(ev, MP_EV_SUBMIT);
527
+ }.bind(this);
528
+ window.addEventListener(EV_SUBMIT, this.listenerSubmit);
529
+ };
530
+
531
+ Autocapture.prototype.initPageLeaveTracking = function() {
532
+ // Capture page_leave both when the user navigates away from the page (visibilitychange) as well
533
+ // as when they navigate to a different page within the SPA (popstate/pushstate/hashchange).
534
+ document.removeEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
535
+ window.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
536
+ window.removeEventListener(EV_LOAD, this.listenerPageLoad);
537
+
538
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.get_config('record_heatmap_data')) {
539
+ return;
540
+ }
541
+
542
+ logger.log('Initializing page visibility tracking.');
543
+ this._initScrollDepthTracking();
544
+ var previousTrackedUrl = _.info.currentUrl();
545
+
546
+ // Initialize previousScrollHeight on `load` which handles async loading
547
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
548
+ this.listenerPageLoad = function() {
549
+ this.previousScrollHeight = document.body.scrollHeight;
550
+ }.bind(this);
551
+ window.addEventListener(EV_LOAD, this.listenerPageLoad);
552
+
553
+ // Track page navigation events similar to how initPageviewTracking does it
554
+ this.listenerPageLeaveLocationchange = safewrap(function(ev) {
555
+ if (this.currentUrlBlocked()) {
556
+ return;
557
+ }
558
+
559
+ var currentUrl = _.info.currentUrl();
560
+ // Track all URL changes including query string or fragment changes as separate scroll sessions
561
+ var shouldTrack = currentUrl !== previousTrackedUrl;
562
+
563
+ if (shouldTrack) {
564
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
565
+ previousTrackedUrl = currentUrl;
566
+ // Fragment navigation should call scroll(end) and trigger listener, don't add window.scrollY here.
567
+ this.maxScrollViewDepth = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
568
+ this.previousScrollHeight = document.body.scrollHeight;
569
+ this.hasTrackedScrollSession = false;
570
+ }
395
571
  }.bind(this));
572
+ window.addEventListener(EV_MP_LOCATION_CHANGE, this.listenerPageLeaveLocationchange);
573
+
574
+ this.listenerPageLeaveVisibilitychange = function(ev) {
575
+ if (document.hidden) {
576
+ this._trackPageLeave(ev, previousTrackedUrl, this.previousScrollHeight);
577
+ }
578
+ }.bind(this);
579
+ document.addEventListener(EV_VISIBILITYCHANGE, this.listenerPageLeaveVisibilitychange);
580
+ };
581
+
582
+ Autocapture.prototype.stopDeadClickTracking = function() {
583
+ if (this.listenerDeadClick) {
584
+ window.removeEventListener(EV_CLICK, this.listenerDeadClick);
585
+ this.listenerDeadClick = null;
586
+ }
587
+
588
+ if (this._deadClickTracker) {
589
+ this._deadClickTracker.stopTracking();
590
+ this._deadClickTracker = null;
591
+ }
396
592
  };
397
593
 
398
594
  // 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 };