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.
- package/CHANGELOG.md +11 -0
- package/build.sh +1 -0
- package/dist/mixpanel-core.cjs.d.ts +440 -0
- package/dist/mixpanel-core.cjs.js +857 -56
- package/dist/mixpanel-recorder.js +5 -3
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +440 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +857 -56
- package/dist/mixpanel-with-recorder.d.ts +440 -0
- package/dist/mixpanel-with-recorder.js +861 -58
- package/dist/mixpanel-with-recorder.min.d.ts +440 -0
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +440 -0
- package/dist/mixpanel.amd.js +861 -58
- package/dist/mixpanel.cjs.d.ts +440 -0
- package/dist/mixpanel.cjs.js +861 -58
- package/dist/mixpanel.globals.js +857 -56
- package/dist/mixpanel.min.js +170 -152
- package/dist/mixpanel.module.d.ts +440 -0
- package/dist/mixpanel.module.js +861 -58
- package/dist/mixpanel.umd.d.ts +440 -0
- package/dist/mixpanel.umd.js +861 -58
- package/dist/rrweb-compiled.js +4 -2
- package/package.json +2 -19
- package/rollup.config.mjs +28 -4
- package/src/autocapture/deadclick.js +254 -0
- package/src/autocapture/index.js +237 -41
- package/src/autocapture/shadow-dom-observer.js +100 -0
- package/src/autocapture/utils.js +230 -3
- package/src/config.js +1 -1
- package/src/flags/index.js +43 -18
- package/src/index.d.ts +16 -3
- package/src/loaders/loader-module-core.d.ts +1 -0
- package/src/loaders/loader-module-with-async-recorder.d.ts +1 -0
- package/src/loaders/loader-module.d.ts +1 -0
- package/src/utils.js +15 -0
package/src/autocapture/index.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
183
|
-
|
|
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; //
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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 };
|