govuk_publishing_components 28.6.0 → 28.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,12 +7,9 @@
7
7
  * See docs/real-user-metrics.md for more information.
8
8
  */
9
9
 
10
-
11
10
  /* ! Remember to keep these settings at the end of this file when updating LUX:
12
11
  *
13
12
  * * `LUX.customerid = 47044334` to let LUX know who this is
14
- * * `LUX.beaconMode = "simple"` to fire the beacon as an image, which is now
15
- * allowed by the content security policy.
16
13
  * * `LUX.debug = false` turns debugging on and off. Left set to false - and
17
14
  * kept in the file so it's easier to remember that this can be turned on.
18
15
  *
@@ -22,33 +19,143 @@
22
19
  * * `LUX.samplerate = 1` to set sample rate to 1% of users.
23
20
  */
24
21
 
25
- var LUX_t_start = Date.now(),
26
- LUX = window.LUX || {};
27
- LUX = (function () {
22
+ (function () {
23
+ "use strict";
24
+
25
+ function now() {
26
+ return Date.now ? Date.now() : +new Date();
27
+ }
28
+
29
+ var LUX_t_start = now();
30
+
31
+ function fromObject(obj) {
32
+ var autoMode = getProperty(obj, "auto", true);
33
+ return {
34
+ auto: autoMode,
35
+ beaconUrl: getProperty(obj, "beaconUrl", "https://lux.speedcurve.com/lux/"),
36
+ customerid: getProperty(obj, "customerid", undefined),
37
+ debug: getProperty(obj, "debug", false),
38
+ errorBeaconUrl: getProperty(obj, "errorBeaconUrl", "https://lux.speedcurve.com/error/"),
39
+ jspagelabel: getProperty(obj, "jspagelabel", undefined),
40
+ label: getProperty(obj, "label", undefined),
41
+ maxErrors: getProperty(obj, "maxErrors", 5),
42
+ maxMeasureTime: getProperty(obj, "maxMeasureTime", 60000),
43
+ measureUntil: getProperty(obj, "measureUntil", "onload"),
44
+ samplerate: getProperty(obj, "samplerate", 100),
45
+ sendBeaconOnPageHidden: getProperty(obj, "sendBeaconOnPageHidden", autoMode),
46
+ trackErrors: getProperty(obj, "trackErrors", true),
47
+ };
48
+ }
49
+ function getProperty(obj, key, defaultValue) {
50
+ if (typeof obj[key] !== "undefined") {
51
+ return obj[key];
52
+ }
53
+ return defaultValue;
54
+ }
55
+
56
+ var LogEvent = {
57
+ // Internal events
58
+ EvaluationStart: 1,
59
+ EvaluationEnd: 2,
60
+ InitCalled: 3,
61
+ MarkCalled: 4,
62
+ MeasureCalled: 5,
63
+ AddDataCalled: 6,
64
+ SendCalled: 7,
65
+ ForceSampleCalled: 8,
66
+ // Data collection events
67
+ SessionIsSampled: 21,
68
+ SessionIsNotSampled: 22,
69
+ MainBeaconSent: 23,
70
+ UserTimingBeaconSent: 24,
71
+ InteractionBeaconSent: 25,
72
+ CustomDataBeaconSent: 26,
73
+ // Errors
74
+ PerformanceObserverError: 51,
75
+ InputEventPermissionError: 52,
76
+ InnerHtmlAccessError: 53,
77
+ EventTargetAccessError: 54,
78
+ CookieReadError: 55,
79
+ CookieSetError: 56,
80
+ PageLabelEvaluationError: 57,
81
+ // Browser support messages
82
+ NavTimingNotSupported: 71,
83
+ PaintTimingNotSupported: 72,
84
+ };
85
+ var Logger = (function () {
86
+ function Logger() {
87
+ this.events = [];
88
+ }
89
+ Logger.prototype.logEvent = function (event, args) {
90
+ if (args === void 0) {
91
+ args = [];
92
+ }
93
+ this.events.push([new Date(), event, args]);
94
+ };
95
+ Logger.prototype.getEvents = function () {
96
+ return this.events;
97
+ };
98
+ return Logger;
99
+ })();
100
+
101
+ var Flags = {
102
+ InitCalled: 1 << 0,
103
+ NavTimingNotSupported: 1 << 1,
104
+ UserTimingNotSupported: 1 << 2,
105
+ VisibilityStateNotVisible: 1 << 3,
106
+ BeaconSentFromUnloadHandler: 1 << 4,
107
+ BeaconSentAfterTimeout: 1 << 5,
108
+ PageLabelFromDocumentTitle: 1 << 6,
109
+ PageLabelFromLabelProp: 1 << 7,
110
+ PageLabelFromGlobalVariable: 1 << 8,
111
+ };
112
+ function addFlag(flags, flag) {
113
+ return flags | flag;
114
+ }
115
+
116
+ var LUX = window.LUX || {};
117
+ LUX = (function () {
28
118
  // -------------------------------------------------------------------------
29
119
  // Settings
30
120
  // -------------------------------------------------------------------------
31
121
  // Set the sample rate to 1% to avoid all events being sent.
32
- LUX.samplerate = 1
122
+ LUX.samplerate = 1;
33
123
  // -------------------------------------------------------------------------
34
124
  /// End
35
125
  // -------------------------------------------------------------------------
36
126
 
37
- var e = [];
38
- pe("lux.js evaluation start.");
39
- var t = "216",
40
- n = 0;
41
- function r(e) {
42
- n++,
127
+ var SCRIPT_VERSION = "300";
128
+ var logger = new Logger();
129
+ var userConfig = fromObject(LUX);
130
+ logger.logEvent(LogEvent.EvaluationStart, [SCRIPT_VERSION]);
131
+ // Log JS errors.
132
+ var nErrors = 0;
133
+ function errorHandler(e) {
134
+ if (!userConfig.trackErrors) {
135
+ return;
136
+ }
137
+ nErrors++;
138
+ if (
43
139
  e &&
44
- void 0 !== e.filename &&
45
- void 0 !== e.message &&
46
- (-1 !== e.filename.indexOf("/lux.js?") ||
47
- -1 !== e.message.indexOf("LUX") ||
48
- (n <= 5 && W())) &&
49
- (new Image().src =
50
- "https://lux.speedcurve.com/error/?v=216&id=" +
51
- J() +
140
+ "undefined" !== typeof e.filename &&
141
+ "undefined" !== typeof e.message
142
+ ) {
143
+ // it is a valid error object
144
+ if (
145
+ -1 !== e.filename.indexOf("/lux.js?") ||
146
+ -1 !== e.message.indexOf("LUX") || // Always send LUX errors.
147
+ (nErrors <= userConfig.maxErrors &&
148
+ "function" === typeof _sample &&
149
+ _sample())
150
+ ) {
151
+ // Sample & limit other errors.
152
+ // Send the error beacon.
153
+ new Image().src =
154
+ userConfig.errorBeaconUrl +
155
+ "?v=" +
156
+ SCRIPT_VERSION +
157
+ "&id=" +
158
+ getCustomerId() +
52
159
  "&fn=" +
53
160
  encodeURIComponent(e.filename) +
54
161
  "&ln=" +
@@ -58,966 +165,1744 @@ LUX = (function () {
58
165
  "&msg=" +
59
166
  encodeURIComponent(e.message) +
60
167
  "&l=" +
61
- encodeURIComponent(ge()) +
62
- (V() ? "&ct=" + V() : ""));
63
- }
64
- window.addEventListener("error", r);
65
- var i = ("object" == typeof window.LUX_al ? window.LUX_al : []).slice();
66
- if ("function" == typeof PerformanceObserver) {
67
- var a = new PerformanceObserver(function (e) {
68
- e.getEntries().forEach(function (e) {
69
- ("longtask" === e.entryType && -1 !== i.indexOf(e)) || i.push(e);
168
+ encodeURIComponent(_getPageLabel()) +
169
+ (connectionType() ? "&ct=" + connectionType() : "") +
170
+ "&HN=" +
171
+ encodeURIComponent(document.location.hostname) +
172
+ "&PN=" +
173
+ encodeURIComponent(document.location.pathname);
174
+ }
175
+ }
176
+ }
177
+ window.addEventListener("error", errorHandler);
178
+ // Initialize performance observer
179
+ // Note: This code was later added to the LUX snippet. In the snippet we ONLY collect
180
+ // Long Task entries because that is the only entry type that can not be buffered.
181
+ // We _copy_ any Long Tasks collected by the snippet and ignore it after that.
182
+ var gaSnippetLongTasks =
183
+ typeof window.LUX_al === "object" ? window.LUX_al : [];
184
+ var gaPerfEntries = gaSnippetLongTasks.slice(); // array of Long Tasks (prefer the array from the snippet)
185
+ if (typeof PerformanceObserver === "function") {
186
+ var perfObserver = new PerformanceObserver(function (list) {
187
+ // Keep an array of perf objects to process later.
188
+ list.getEntries().forEach(function (entry) {
189
+ // Only record long tasks that weren't already recorded by the PerformanceObserver in the snippet
190
+ if (
191
+ entry.entryType !== "longtask" ||
192
+ gaPerfEntries.indexOf(entry) === -1
193
+ ) {
194
+ gaPerfEntries.push(entry);
195
+ }
70
196
  });
71
197
  });
72
198
  try {
73
- "function" == typeof PerformanceLongTaskTiming &&
74
- a.observe({ type: "longtask", buffered: !0 }),
75
- "function" == typeof LargestContentfulPaint &&
76
- a.observe({ type: "largest-contentful-paint", buffered: !0 }),
77
- "function" == typeof PerformanceElementTiming &&
78
- a.observe({ type: "element", buffered: !0 }),
79
- "function" == typeof PerformancePaintTiming && a.observe({ type: "paint", buffered: !0 }),
80
- "function" == typeof LayoutShift && a.observe({ type: "layout-shift", buffered: !0 });
199
+ if (typeof PerformanceLongTaskTiming === "function") {
200
+ perfObserver.observe({ type: "longtask", buffered: true });
201
+ }
202
+ if (typeof LargestContentfulPaint === "function") {
203
+ perfObserver.observe({
204
+ type: "largest-contentful-paint",
205
+ buffered: true,
206
+ });
207
+ }
208
+ if (typeof PerformanceElementTiming === "function") {
209
+ perfObserver.observe({ type: "element", buffered: true });
210
+ }
211
+ if (typeof PerformancePaintTiming === "function") {
212
+ perfObserver.observe({ type: "paint", buffered: true });
213
+ }
214
+ if (typeof LayoutShift === "function") {
215
+ perfObserver.observe({ type: "layout-shift", buffered: true });
216
+ }
81
217
  } catch (e) {
82
- pe("Long Tasks error.");
83
- }
84
- } else pe("Long Tasks not supported.");
85
- var o,
86
- s = 0,
87
- u = void 0 !== LUX.gaMarks ? LUX.gaMarks : [],
88
- c = void 0 !== LUX.gaMeasures ? LUX.gaMeasures : [],
89
- d = {},
90
- f = {},
91
- m = 0,
92
- l = 0,
93
- v = 0,
94
- g = 0,
95
- p = 1,
96
- h = "LUX_start",
97
- y = "LUX_end",
98
- E = me(),
99
- T = le(E),
100
- L = window.performance,
101
- b = 2e3,
102
- w = void 0 !== LUX.beaconMode ? LUX.beaconMode : "autoupdate",
103
- U = void 0 !== LUX.beaconUrl ? LUX.beaconUrl : "https://lux.speedcurve.com/lux/",
104
- X = void 0 !== LUX.samplerate ? LUX.samplerate : 100;
105
- pe(
106
- "Sample rate = " +
107
- X +
108
- "%. " +
109
- (W()
110
- ? "This session IS being sampled."
111
- : "This session is NOT being sampled. The data will NOT show up in your LUX dashboards. Call LUX.forceSample() and try again.")
112
- );
113
- var S,
114
- k = void 0 === LUX.auto || LUX.auto,
115
- M = LUX.ns ? LUX.ns : Date.now ? Date.now() : +new Date(),
116
- B = 0;
117
- L && L.timing && L.timing.navigationStart
118
- ? ((M = L.timing.navigationStart), (B = LUX.ns ? LUX.ns - M : 0))
119
- : (pe("Nav Timing is not supported."), (s |= 2));
120
- var N = ["click", "mousedown", "keydown", "touchstart", "pointerdown"],
121
- x = { passive: !0, capture: !0 };
122
- function C(e) {
123
- S ||
124
- ((S = Math.round(e)),
125
- N.forEach(function (e) {
126
- removeEventListener(e, I, x);
127
- }));
128
- }
129
- function I(e) {
130
- var t = !1;
218
+ logger.logEvent(LogEvent.PerformanceObserverError, [e]);
219
+ }
220
+ }
221
+ // Bitmask of flags for this session & page
222
+ var gFlags = 0;
223
+ // array of marks where each element is a hash
224
+ var gaMarks = typeof LUX.gaMarks !== "undefined" ? LUX.gaMarks : [];
225
+ // array of measures where each element is a hash
226
+ var gaMeasures =
227
+ typeof LUX.gaMeasures !== "undefined" ? LUX.gaMeasures : [];
228
+ var ghIx = {}; // hash for Interaction Metrics (scroll, click, keyboard)
229
+ var ghData = {}; // hash for data that is specific to the customer (eg, userid, conversion info)
230
+ var gbLuxSent = 0; // have we sent the LUX data? (avoid sending twice in unload)
231
+ var gbNavSent = 0; // have we sent the Nav Timing beacon yet? (avoid sending twice for SPA)
232
+ var gbIxSent = 0; // have we sent the IX data? (avoid sending twice for SPA)
233
+ var gbFirstPV = 1; // this is the first page view (vs. a SPA "soft nav")
234
+ var gStartMark = "LUX_start"; // the name of the mark that corresponds to "navigationStart" for SPA
235
+ var gEndMark = "LUX_end"; // the name of the mark that corresponds to "loadEventStart" for SPA
236
+ var gSessionTimeout = 30 * 60; // number of seconds after which we consider a session to have "timed out" (used for calculating bouncerate)
237
+ var gSyncId = createSyncId(); // if we send multiple beacons, use this to sync them (eg, LUX & IX) (also called "luxid")
238
+ var gUid = refreshUniqueId(gSyncId); // cookie for this session ("Unique ID")
239
+ var gCustomerDataTimeout; // setTimeout timer for sending a Customer Data beacon after onload
240
+ var perf = window.performance;
241
+ var gMaxQuerystring = 8190; // split the beacon querystring if it gets longer than this
242
+ if (_sample()) {
243
+ logger.logEvent(LogEvent.SessionIsSampled, [userConfig.samplerate]);
244
+ } else {
245
+ logger.logEvent(LogEvent.SessionIsNotSampled, [userConfig.samplerate]);
246
+ }
247
+ // Get a timestamp as close to navigationStart as possible.
248
+ var _navigationStart = LUX.ns ? LUX.ns : now(); // create a _navigationStart
249
+ var gLuxSnippetStart = 0;
250
+ if (perf && perf.timing && perf.timing.navigationStart) {
251
+ _navigationStart = perf.timing.navigationStart;
252
+ // Record when the LUX snippet was evaluated relative to navigationStart.
253
+ gLuxSnippetStart = LUX.ns ? LUX.ns - _navigationStart : 0;
254
+ } else {
255
+ logger.logEvent(LogEvent.NavTimingNotSupported);
256
+ gFlags = addFlag(gFlags, Flags.NavTimingNotSupported);
257
+ }
258
+ ////////////////////// FID BEGIN
259
+ // FIRST INPUT DELAY (FID)
260
+ // The basic idea behind FID is to attach various input event listeners and measure the time
261
+ // between when the event happens and when the handler executes. That is FID.
262
+ var gFirstInputDelay; // this is FID
263
+ var gaEventTypes = [
264
+ "click",
265
+ "mousedown",
266
+ "keydown",
267
+ "touchstart",
268
+ "pointerdown",
269
+ ]; // NOTE: does NOT include scroll!
270
+ var ghListenerOptions = { passive: true, capture: true };
271
+ // Record the FIRST input delay.
272
+ function recordDelay(delay) {
273
+ if (!gFirstInputDelay) {
274
+ gFirstInputDelay = Math.round(delay); // milliseconds
275
+ // remove event listeners
276
+ gaEventTypes.forEach(function (eventType) {
277
+ removeEventListener(eventType, onInput, ghListenerOptions);
278
+ });
279
+ }
280
+ }
281
+ // Pointer events are special. Ignore scrolling by looking for pointercancel
282
+ // events because FID does not include scrolling nor pinch/zooming.
283
+ function onPointerDown(delay) {
284
+ function onPointerUp() {
285
+ recordDelay(delay);
286
+ removeListeners();
287
+ }
288
+ // Do NOT record FID - this is a scroll.
289
+ function onPointerCancel() {
290
+ removeListeners();
291
+ }
292
+ function removeListeners() {
293
+ window.removeEventListener("pointerup", onPointerUp, ghListenerOptions);
294
+ window.removeEventListener(
295
+ "pointercancel",
296
+ onPointerCancel,
297
+ ghListenerOptions
298
+ );
299
+ }
300
+ window.addEventListener("pointerup", onPointerUp, ghListenerOptions);
301
+ window.addEventListener(
302
+ "pointercancel",
303
+ onPointerCancel,
304
+ ghListenerOptions
305
+ );
306
+ }
307
+ // Record FID as the delta between when the event happened and when the
308
+ // listener was able to execute.
309
+ function onInput(evt) {
310
+ var bCancelable = false;
131
311
  try {
132
- t = e.cancelable;
312
+ // Seeing "Permission denied" errors, so do a simple try-catch.
313
+ bCancelable = evt.cancelable;
133
314
  } catch (e) {
134
- return void pe("Permission error accessing input event.");
135
- }
136
- if (t) {
137
- var n = D(!0),
138
- r = e.timeStamp;
139
- if ((r > 152e7 && (n = Number(new Date())), r > n)) return;
140
- var i = n - r;
141
- "pointerdown" == e.type
142
- ? (function (e, t) {
143
- function n() {
144
- C(e), i();
145
- }
146
- function r() {
147
- i();
148
- }
149
- function i() {
150
- window.removeEventListener("pointerup", n, x),
151
- window.removeEventListener("pointercancel", r, x);
152
- }
153
- window.addEventListener("pointerup", n, x),
154
- window.addEventListener("pointercancel", r, x);
155
- })(i)
156
- : C(i);
157
- }
158
- }
159
- function D(e) {
160
- var t = (Date.now ? Date.now() : +new Date()) - M,
161
- n = _(h);
162
- return n && !e ? t - n.startTime : L && L.now ? L.now() : t;
163
- }
164
- function j(e) {
165
- if ((pe("Enter LUX.mark(), name = " + e), L)) {
166
- if (L.mark) return L.mark(e);
167
- if (L.webkitMark) return L.webkitMark(e);
168
- }
169
- (s |= 4), u.push({ name: e, entryType: "mark", startTime: D(), duration: 0 });
170
- }
171
- function _(e) {
172
- return (function (e, t) {
173
- if (t)
174
- for (var n = t.length - 1; n >= 0; n--) {
175
- var r = t[n];
176
- if (e === r.name) return r;
177
- }
315
+ // bail - no need to return anything
316
+ logger.logEvent(LogEvent.InputEventPermissionError);
178
317
  return;
179
- })(e, P());
180
- }
181
- function P() {
182
- if (L) {
183
- if (L.getEntriesByType) return L.getEntriesByType("mark");
184
- if (L.webkitGetEntriesByType) return L.webkitGetEntriesByType("mark");
185
- }
186
- return u;
187
- }
188
- function O() {
189
- var e = {},
190
- t = _(h),
191
- n = P();
192
- n &&
193
- n.forEach(function (n) {
194
- var r = n.name,
195
- i = r !== h && t ? t.startTime : 0,
196
- a = Math.round(n.startTime - i);
197
- a < 0 || (void 0 === e[r] ? (e[r] = a) : (e[r] = Math.max(a, e[r])));
318
+ }
319
+ if (bCancelable) {
320
+ var now_1 = _now(true);
321
+ var eventTimeStamp = evt.timeStamp;
322
+ if (eventTimeStamp > 1520000000) {
323
+ // If the event timeStamp is an epoch time instead of a time relative to NavigationStart,
324
+ // then compare it to Date.now() instead of performance.now().
325
+ now_1 = Number(new Date());
326
+ }
327
+ if (eventTimeStamp > now_1) {
328
+ // If there is a race condition and eventTimeStamp happened after
329
+ // this code was executed, something is wrong. Bail.
330
+ return;
331
+ }
332
+ var delay = now_1 - eventTimeStamp;
333
+ if ("pointerdown" == evt.type) {
334
+ // special case
335
+ onPointerDown(delay);
336
+ } else {
337
+ recordDelay(delay);
338
+ }
339
+ }
340
+ }
341
+ // Attach event listener to input events.
342
+ gaEventTypes.forEach(function (eventType) {
343
+ window.addEventListener(eventType, onInput, ghListenerOptions);
344
+ });
345
+ ////////////////////// FID END
346
+ /**
347
+ * Returns the time elapsed (in ms) since navigationStart. For SPAs, returns
348
+ * the time elapsed since the last LUX.init call.
349
+ *
350
+ * When `absolute = true` the time is always relative to navigationStart, even
351
+ * in SPAs.
352
+ */
353
+ function _now(absolute) {
354
+ var currentTimestamp = Date.now ? Date.now() : +new Date();
355
+ var msSinceNavigationStart = currentTimestamp - _navigationStart;
356
+ var startMark = _getMark(gStartMark);
357
+ // For SPA page views, we use our internal mark as a reference point
358
+ if (startMark && !absolute) {
359
+ return msSinceNavigationStart - startMark.startTime;
360
+ }
361
+ // For "regular" page views, we can use performance.now() if it's available...
362
+ if (perf && perf.now) {
363
+ return perf.now();
364
+ }
365
+ // ... or we can use navigationStart as a reference point
366
+ return msSinceNavigationStart;
367
+ }
368
+ // set a mark
369
+ // NOTE: It's possible to set multiple marks with the same name.
370
+ function _mark(name) {
371
+ logger.logEvent(LogEvent.MarkCalled, [name]);
372
+ if (perf) {
373
+ if (perf.mark) {
374
+ return perf.mark(name);
375
+ } else if (perf.webkitMark) {
376
+ return perf.webkitMark(name);
377
+ }
378
+ }
379
+ gFlags = addFlag(gFlags, Flags.UserTimingNotSupported);
380
+ // Shim
381
+ var entry = {
382
+ name: name,
383
+ detail: null,
384
+ entryType: "mark",
385
+ startTime: _now(),
386
+ duration: 0,
387
+ };
388
+ gaMarks.push(entry);
389
+ return entry;
390
+ }
391
+ // compute a measurement (delta)
392
+ function _measure(name, startMarkName, endMarkName) {
393
+ logger.logEvent(LogEvent.MeasureCalled, [
394
+ name,
395
+ startMarkName,
396
+ endMarkName,
397
+ ]);
398
+ if ("undefined" === typeof startMarkName && _getMark(gStartMark)) {
399
+ // If a start mark is not specified, but the user has called _init() to set a new start,
400
+ // then use the new start base time (similar to navigationStart) as the start mark.
401
+ startMarkName = gStartMark;
402
+ }
403
+ if (perf) {
404
+ if (perf.measure) {
405
+ // IE 11 does not handle null and undefined correctly
406
+ if (startMarkName) {
407
+ if (endMarkName) {
408
+ return perf.measure(name, startMarkName, endMarkName);
409
+ } else {
410
+ return perf.measure(name, startMarkName);
411
+ }
412
+ } else {
413
+ return perf.measure(name);
414
+ }
415
+ } else if (perf.webkitMeasure) {
416
+ return perf.webkitMeasure(name, startMarkName, endMarkName);
417
+ }
418
+ }
419
+ // shim:
420
+ var startTime = 0,
421
+ endTime = _now();
422
+ if (startMarkName) {
423
+ var startMark = _getMark(startMarkName);
424
+ if (startMark) {
425
+ startTime = startMark.startTime;
426
+ } else if (perf && perf.timing && perf.timing[startMarkName]) {
427
+ // the mark name can also be a property from Navigation Timing
428
+ startTime = perf.timing[startMarkName] - perf.timing.navigationStart;
429
+ } else {
430
+ throw new DOMException(
431
+ "Failed to execute 'measure' on 'Performance': The mark '".concat(
432
+ startMarkName,
433
+ "' does not exist"
434
+ )
435
+ );
436
+ }
437
+ }
438
+ if (endMarkName) {
439
+ var endMark = _getMark(endMarkName);
440
+ if (endMark) {
441
+ endTime = endMark.startTime;
442
+ } else if (perf && perf.timing && perf.timing[endMarkName]) {
443
+ // the mark name can also be a property from Navigation Timing
444
+ endTime = perf.timing[endMarkName] - perf.timing.navigationStart;
445
+ } else {
446
+ throw new DOMException(
447
+ "Failed to execute 'measure' on 'Performance': The mark '".concat(
448
+ endMarkName,
449
+ "' does not exist"
450
+ )
451
+ );
452
+ }
453
+ }
454
+ // Shim
455
+ var entry = {
456
+ name: name,
457
+ detail: null,
458
+ entryType: "measure",
459
+ startTime: startTime,
460
+ duration: endTime - startTime,
461
+ };
462
+ gaMeasures.push(entry);
463
+ return entry;
464
+ }
465
+ // Return THE LAST mark that matches the name.
466
+ function _getMark(name) {
467
+ return _getM(name, _getMarks());
468
+ }
469
+ function _getM(name, aItems) {
470
+ if (aItems) {
471
+ for (var i = aItems.length - 1; i >= 0; i--) {
472
+ var m = aItems[i];
473
+ if (name === m.name) {
474
+ return m;
475
+ }
476
+ }
477
+ }
478
+ return undefined;
479
+ }
480
+ // Return an array of marks.
481
+ function _getMarks() {
482
+ if (perf) {
483
+ if (perf.getEntriesByType) {
484
+ return perf.getEntriesByType("mark");
485
+ } else if (perf.webkitGetEntriesByType) {
486
+ return perf.webkitGetEntriesByType("mark");
487
+ }
488
+ }
489
+ return gaMarks;
490
+ }
491
+ // Return an array of measures.
492
+ function _getMeasures() {
493
+ if (perf) {
494
+ if (perf.getEntriesByType) {
495
+ return perf.getEntriesByType("measure");
496
+ } else if (perf.webkitGetEntriesByType) {
497
+ return perf.webkitGetEntriesByType("measure");
498
+ }
499
+ }
500
+ return gaMeasures;
501
+ }
502
+ // Return a string of User Timing Metrics formatted for beacon querystring.
503
+ function userTimingValues() {
504
+ // The User Timing spec allows for there to be multiple marks with the same name,
505
+ // and multiple measures with the same name. But we can only send back one value
506
+ // for a name, so we always take the MAX value. We do this by first creating a
507
+ // hash that has the max value for each name.
508
+ var hUT = {};
509
+ var startMark = _getMark(gStartMark);
510
+ var endMark = _getMark(gEndMark);
511
+ // marks
512
+ var aMarks = _getMarks();
513
+ if (aMarks) {
514
+ aMarks.forEach(function (m) {
515
+ if (m === startMark || m === endMark) {
516
+ // Don't include the internal marks in the beacon
517
+ return;
518
+ }
519
+ var name = m.name;
520
+ // For user timing values taken in a SPA page load, we need to adjust them
521
+ // so that they're zeroed against the last LUX.init() call. We zero every
522
+ // UT value except for the internal LUX start mark.
523
+ var tZero =
524
+ name !== gStartMark && startMark ? startMark.startTime : 0;
525
+ var markTime = Math.round(m.startTime - tZero);
526
+ if (markTime < 0) {
527
+ // Exclude marks that were taken before the current SPA page view
528
+ return;
529
+ }
530
+ if (typeof hUT[name] === "undefined") {
531
+ hUT[name] = markTime;
532
+ } else {
533
+ hUT[name] = Math.max(markTime, hUT[name]);
534
+ }
198
535
  });
199
- var r = (function () {
200
- if (L) {
201
- if (L.getEntriesByType) return L.getEntriesByType("measure");
202
- if (L.webkitGetEntriesByType) return L.webkitGetEntriesByType("measure");
203
- }
204
- return c;
205
- })();
206
- r &&
207
- r.forEach(function (n) {
208
- if (!(t && n.startTime < t.startTime)) {
209
- var r = n.name,
210
- i = Math.round(n.duration);
211
- void 0 === e[r] ? (e[r] = i) : (e[r] = Math.max(i, e[r]));
536
+ }
537
+ // measures
538
+ var aMeasures = _getMeasures();
539
+ if (aMeasures) {
540
+ aMeasures.forEach(function (m) {
541
+ if (startMark && m.startTime < startMark.startTime) {
542
+ // Exclude measures that were taken before the current SPA page view
543
+ return;
544
+ }
545
+ var name = m.name;
546
+ var measureTime = Math.round(m.duration);
547
+ if (typeof hUT[name] === "undefined") {
548
+ hUT[name] = measureTime;
549
+ } else {
550
+ hUT[name] = Math.max(measureTime, hUT[name]);
212
551
  }
213
552
  });
214
- var i = [];
215
- return (
216
- Object.keys(e).forEach(function (t) {
217
- i.push(t + "|" + e[t]);
218
- }),
219
- i.join(",")
220
- );
553
+ }
554
+ // OK. hUT is now a hash (associative array) whose keys are the names of the
555
+ // marks & measures, and the value is the max value. Here we create a tuple
556
+ // for each name|value pair and then join them.
557
+ var aUT = [];
558
+ var aNames = Object.keys(hUT);
559
+ aNames.forEach(function (name) {
560
+ aUT.push(name + "|" + hUT[name]);
561
+ });
562
+ return aUT.join(",");
221
563
  }
222
- function H() {
223
- if ("function" != typeof PerformanceLongTaskTiming) return "";
224
- var e = "",
225
- t = {},
226
- n = {};
227
- if (i.length)
228
- for (
229
- var r = _(h),
230
- a = r ? r.startTime : 0,
231
- o = r ? _(y).startTime : L.timing.loadEventEnd - L.timing.navigationStart,
232
- s = 0;
233
- s < i.length;
234
- s++
235
- ) {
236
- var u = i[s];
237
- if ("longtask" === u.entryType) {
238
- var c = Math.round(u.duration);
239
- if (u.startTime < a) c -= a - u.startTime;
240
- else if (u.startTime >= o) continue;
241
- var d = u.attribution[0].name;
242
- t[d] || ((t[d] = 0), (n[d] = "")),
243
- (t[d] += c),
244
- (n[d] += "," + Math.round(u.startTime) + "|" + c);
564
+ // Return a string of Element Timing Metrics formatted for beacon querystring.
565
+ function elementTimingValues() {
566
+ var aET = [];
567
+ if (gaPerfEntries.length) {
568
+ for (var i = 0; i < gaPerfEntries.length; i++) {
569
+ var pe = gaPerfEntries[i];
570
+ if ("element" === pe.entryType && pe.identifier && pe.startTime) {
571
+ aET.push(pe.identifier + "|" + Math.round(pe.startTime));
572
+ }
573
+ }
574
+ }
575
+ return aET.join(",");
576
+ }
577
+ // Return a string of CPU times formatted for beacon querystring.
578
+ function cpuTimes() {
579
+ if ("function" !== typeof PerformanceLongTaskTiming) {
580
+ // Do not return any CPU metrics if Long Tasks API is not supported.
581
+ return "";
582
+ }
583
+ var sCPU = "";
584
+ var hCPU = {};
585
+ var hCPUDetails = {}; // TODO - Could remove this later after large totals go away.
586
+ // Add up totals for each "type" of long task
587
+ if (gaPerfEntries.length) {
588
+ // Long Task start times are relative to NavigationStart which is "0".
589
+ // But if it is a SPA then the relative start time is gStartMark.
590
+ var startMark = _getMark(gStartMark);
591
+ var tZero = startMark ? startMark.startTime : 0;
592
+ // Do not include Long Tasks that start after the page is done.
593
+ // For full page loads, "done" is loadEventEnd.
594
+ var tEnd = perf.timing.loadEventEnd - perf.timing.navigationStart;
595
+ if (startMark) {
596
+ // For SPA page loads (determined by the presence of a start mark), "done" is gEndMark.
597
+ var endMark = _getMark(gEndMark);
598
+ if (endMark) {
599
+ tEnd = endMark.startTime;
600
+ }
601
+ }
602
+ for (var i = 0; i < gaPerfEntries.length; i++) {
603
+ var p = gaPerfEntries[i];
604
+ if ("longtask" !== p.entryType) {
605
+ continue;
606
+ }
607
+ var dur = Math.round(p.duration);
608
+ if (p.startTime < tZero) {
609
+ // In a SPA it is possible that we were in the middle of a Long Task when
610
+ // LUX.init() was called. If so, only include the duration after tZero.
611
+ dur -= tZero - p.startTime;
612
+ } else if (p.startTime >= tEnd) {
613
+ // In a SPA it is possible that a Long Task started after loadEventEnd but before our
614
+ // callback from setTimeout(200) happened. Do not include anything that started after tEnd.
615
+ continue;
245
616
  }
617
+ var type = p.attribution[0].name; // TODO - is there ever more than 1 attribution???
618
+ if (!hCPU[type]) {
619
+ // initialize this category
620
+ hCPU[type] = 0;
621
+ hCPUDetails[type] = "";
622
+ }
623
+ hCPU[type] += dur;
624
+ // Send back the raw startTime and duration, as well as the adjusted duration.
625
+ hCPUDetails[type] += "," + Math.round(p.startTime) + "|" + dur;
246
626
  }
247
- var f = void 0 !== t.script ? "script" : "unknown";
248
- void 0 === t[f] && ((t[f] = 0), (n[f] = ""));
249
- var m = (function (e) {
250
- for (var t = 0, n = A(), r = 0 === n, i = [], a = e.split(","), o = 0; o < a.length; o++) {
251
- var s = a[o].split("|");
252
- if (2 === s.length) {
253
- var u = parseInt(s[0]),
254
- c = parseInt(s[1]);
255
- i.push(c), (t = c > t ? c : t), !r && u > n && (u - n > 5e3 ? (r = !0) : (n = u + c));
627
+ }
628
+ // TODO - Add more types if/when they become available.
629
+ var jsType = "undefined" !== typeof hCPU["script"] ? "script" : "unknown"; // spec changed from "script" to "unknown" Nov 2018
630
+ if ("undefined" === typeof hCPU[jsType]) {
631
+ // Initialize default values for pages that have *no Long Tasks*.
632
+ hCPU[jsType] = 0;
633
+ hCPUDetails[jsType] = "";
634
+ }
635
+ var hStats = cpuStats(hCPUDetails[jsType]);
636
+ var sStats =
637
+ ",n|" +
638
+ hStats["count"] +
639
+ ",d|" +
640
+ hStats["median"] +
641
+ ",x|" +
642
+ hStats["max"] +
643
+ (0 === hStats["fci"] ? "" : ",i|" + hStats["fci"]); // only add FCI if it is non-zero
644
+ sCPU += "s|" + hCPU[jsType] + sStats + hCPUDetails[jsType];
645
+ return sCPU;
646
+ }
647
+ // Return a hash of "stats" about the CPU details incl. count, max, and median.
648
+ function cpuStats(sDetails) {
649
+ // tuples of starttime|duration, eg: ,456|250,789|250,1012|250
650
+ var max = 0;
651
+ var fci = getFcp(); // FCI is beginning of 5 second window of no Long Tasks _after_ first contentful paint
652
+ // If FCP is 0 then that means FCP is not supported.
653
+ // If FCP is not supported then we can NOT calculate a valid FCI.
654
+ // Thus, leave FCI = 0 and exclude it from the beacon above.
655
+ var bFoundFci = 0 === fci ? true : false;
656
+ var aValues = [];
657
+ var aTuples = sDetails.split(",");
658
+ for (var i = 0; i < aTuples.length; i++) {
659
+ var aTuple = aTuples[i].split("|");
660
+ if (aTuple.length === 2) {
661
+ var start = parseInt(aTuple[0]);
662
+ var dur = parseInt(aTuple[1]);
663
+ aValues.push(dur);
664
+ max = dur > max ? dur : max;
665
+ // FCI
666
+ if (!bFoundFci && start > fci) {
667
+ // should always be true (assumes Long Tasks are in chrono order)
668
+ if (start - fci > 5000) {
669
+ // More than 5 seconds of inactivity!
670
+ // FCI is the previous value we set (eg, FCI or the _end_ of the previous Long Task)
671
+ bFoundFci = true;
672
+ } else {
673
+ // Less than 5 seconds of inactivity
674
+ fci = start + dur; // FCI is now the end of this Long Task
256
675
  }
257
676
  }
258
- var d = i.length,
259
- f = (function (e) {
260
- if (0 === e.length) return 0;
261
- var t = Math.floor(e.length / 2);
262
- return (
263
- e.sort(function (e, t) {
264
- return e - t;
265
- }),
266
- e.length % 2 ? e[t] : Math.round((e[t - 1] + e[t]) / 2)
267
- );
268
- })(i);
269
- return { count: d, median: f, max: t, fci: n };
270
- })(n[f]),
271
- l = ",n|" + m.count + ",d|" + m.median + ",x|" + m.max + (0 === m.fci ? "" : ",i|" + m.fci);
272
- return (e += "s|" + t[f] + l + n[f]);
273
- }
274
- function R() {
275
- var e = [];
276
- for (var t in d) e.push(t + "|" + d[t]);
277
- return e.join(",");
278
- }
279
- function W() {
280
- if (void 0 === T || void 0 === X) return !1;
281
- var e = ("" + T).substr(-2);
282
- return parseInt(e) < X;
283
- }
284
- function F() {
285
- var e = [];
286
- for (var t in f) {
287
- var n = "" + f[t];
288
- (t = t.replace(/,/g, "").replace(/\|/g, "")),
289
- (n = n.replace(/,/g, "").replace(/\|/g, "")),
290
- e.push(t + "|" + n);
291
- }
292
- return encodeURIComponent(e.join(","));
293
- }
294
- function G() {
295
- var e = Z();
296
- if (!e)
297
- return (function () {
298
- for (
299
- var e = document.getElementsByTagName("script"), t = 0, n = 0, r = e.length;
300
- n < r;
301
- n++
302
- ) {
303
- var i = e[n];
304
- !i.src || i.async || i.defer || t++;
677
+ }
678
+ }
679
+ var count = aValues.length;
680
+ var median = arrayMedian(aValues);
681
+ return { count: count, median: median, max: max, fci: fci };
682
+ }
683
+ function calculateDCLS() {
684
+ if ("function" !== typeof LayoutShift) {
685
+ return false;
686
+ }
687
+ var DCLS = 0;
688
+ for (var i = 0; i < gaPerfEntries.length; i++) {
689
+ var p = gaPerfEntries[i];
690
+ if ("layout-shift" !== p.entryType || p.hadRecentInput) {
691
+ continue;
692
+ }
693
+ DCLS += p.value;
694
+ }
695
+ // The DCL column in Redshift is REAL (FLOAT4) which stores a maximum
696
+ // of 6 significant digits.
697
+ return DCLS.toFixed(6);
698
+ }
699
+ // Return the median value from an array of integers.
700
+ function arrayMedian(aValues) {
701
+ if (0 === aValues.length) {
702
+ return 0;
703
+ }
704
+ var half = Math.floor(aValues.length / 2);
705
+ aValues.sort(function (a, b) {
706
+ return a - b;
707
+ });
708
+ if (aValues.length % 2) {
709
+ // Return the middle value.
710
+ return aValues[half];
711
+ } else {
712
+ // Return the average of the two middle values.
713
+ return Math.round((aValues[half - 1] + aValues[half]) / 2.0);
714
+ }
715
+ }
716
+ // Track how long it took lux.js to load via Resource Timing.
717
+ function selfLoading() {
718
+ var sLuxjs = "";
719
+ if (perf && perf.getEntriesByName) {
720
+ // Get the lux script URL (including querystring params).
721
+ var luxScript = getScriptElement("/js/lux.js");
722
+ if (luxScript) {
723
+ var aResources = perf.getEntriesByName(luxScript.src);
724
+ if (aResources && aResources.length) {
725
+ var r = aResources[0];
726
+ // DO NOT USE DURATION!!!!!
727
+ // See https://www.stevesouders.com/blog/2014/11/25/serious-confusion-with-resource-timing/
728
+ var dns = Math.round(r.domainLookupEnd - r.domainLookupStart);
729
+ var tcp = Math.round(r.connectEnd - r.connectStart); // includes ssl negotiation
730
+ var fb = Math.round(r.responseStart - r.requestStart); // first byte
731
+ var content = Math.round(r.responseEnd - r.responseStart);
732
+ var networkDuration = dns + tcp + fb + content;
733
+ var parseEval = LUX_t_end - LUX_t_start;
734
+ var transferSize = r.encodedBodySize ? r.encodedBodySize : 0;
735
+ // Instead of a delimiter use a 1-letter abbreviation as a separator.
736
+ sLuxjs =
737
+ "d" +
738
+ dns +
739
+ "t" +
740
+ tcp +
741
+ "f" +
742
+ fb +
743
+ "c" +
744
+ content +
745
+ "n" +
746
+ networkDuration +
747
+ "e" +
748
+ parseEval +
749
+ "r" +
750
+ userConfig.samplerate + // sample rate
751
+ (transferSize ? "x" + transferSize : "") +
752
+ (gLuxSnippetStart ? "l" + gLuxSnippetStart : "") +
753
+ "s" +
754
+ (LUX_t_start - _navigationStart) + // when lux.js started getting evaluated relative to navigationStart
755
+ "";
305
756
  }
306
- return t;
307
- })();
308
- for (var t = document.getElementsByTagName("script"), n = 0, r = 0, i = t.length; r < i; r++) {
309
- var a = t[r];
310
- !a.src || a.async || a.defer || 0 == (4 & a.compareDocumentPosition(e)) || n++;
757
+ }
311
758
  }
312
- return n;
759
+ return sLuxjs;
760
+ }
761
+ // _clearIx
762
+ function _clearIx() {
763
+ ghIx = {};
764
+ }
765
+ // Return a string of Interaction Metrics formatted for beacon querystring.
766
+ function ixValues() {
767
+ var aIx = [];
768
+ for (var key in ghIx) {
769
+ aIx.push(key + "|" + ghIx[key]);
770
+ }
771
+ return aIx.join(",");
772
+ }
773
+ // _addData()
774
+ function _addData(name, value) {
775
+ logger.logEvent(LogEvent.AddDataCalled, [name, value]);
776
+ var typeN = typeof name;
777
+ var typeV = typeof value;
778
+ if (
779
+ "string" === typeN &&
780
+ ("string" === typeV || "number" === typeV || "boolean" === typeV)
781
+ ) {
782
+ ghData[name] = value;
783
+ }
784
+ if (gbLuxSent) {
785
+ // This is special: We want to allow customers to call LUX.addData()
786
+ // _after_ window.onload. So we have to send a Customer Data beacon that
787
+ // includes the new customer data.
788
+ // Do setTimeout so that if there are multiple back-to-back addData calls
789
+ // we get them all in one beacon.
790
+ if (gCustomerDataTimeout) {
791
+ // Cancel the timer for any previous beacons so that if they have not
792
+ // yet been sent we can combine all the data in a new beacon.
793
+ clearTimeout(gCustomerDataTimeout);
794
+ }
795
+ gCustomerDataTimeout = window.setTimeout(_sendCustomerData, 100);
796
+ }
797
+ }
798
+ // _sample()
799
+ // Return true if beacons for this page should be sampled.
800
+ function _sample() {
801
+ if (
802
+ "undefined" === typeof gUid ||
803
+ "undefined" === typeof userConfig.samplerate
804
+ ) {
805
+ return false; // bail
806
+ }
807
+ var nThis = ("" + gUid).substr(-2); // number for THIS page - from 00 to 99
808
+ return parseInt(nThis) < userConfig.samplerate;
809
+ }
810
+ // Return a string of Customer Data formatted for beacon querystring.
811
+ function customerDataValues() {
812
+ var aData = [];
813
+ for (var key in ghData) {
814
+ var value = "" + ghData[key]; // convert to string (eg for ints and booleans)
815
+ // strip delimiters (instead of escaping)
816
+ key = key.replace(/,/g, "").replace(/\|/g, "");
817
+ value = value.replace(/,/g, "").replace(/\|/g, "");
818
+ aData.push(key + "|" + value);
819
+ }
820
+ return encodeURIComponent(aData.join(","));
821
+ }
822
+ // _init()
823
+ // Use this function in Single Page Apps to reset things.
824
+ // This function should ONLY be called within a SPA!
825
+ // Otherwise, you might clear marks & measures that were set by a shim.
826
+ function _init() {
827
+ // Some customers (incorrectly) call LUX.init on the very first page load of a SPA. This would
828
+ // cause some first-page-only data (like paint metrics) to be lost. To prevent this, we silently
829
+ // bail from this function when we detect an unnecessary LUX.init call.
830
+ var endMark = _getMark(gEndMark);
831
+ if (!endMark) {
832
+ return;
833
+ }
834
+ logger.logEvent(LogEvent.InitCalled);
835
+ // Clear all interactions from the previous "page".
836
+ _clearIx();
837
+ // Since we actively disable IX handlers, we re-add them each time.
838
+ _removeIxHandlers();
839
+ _addIxHandlers();
840
+ // Reset a bunch of flags.
841
+ gbNavSent = 0;
842
+ gbLuxSent = 0;
843
+ gbIxSent = 0;
844
+ gbFirstPV = 0;
845
+ gSyncId = createSyncId();
846
+ gUid = refreshUniqueId(gSyncId);
847
+ gaPerfEntries.splice(0); // clear out the array of performance entries (do NOT redefine gaPerfEntries!)
848
+ nErrors = 0;
849
+ // Clear flags then set the flag that init was called (ie, this is a SPA).
850
+ gFlags = 0;
851
+ gFlags = addFlag(gFlags, Flags.InitCalled);
852
+ // Mark the "navigationStart" for this SPA page.
853
+ _mark(gStartMark);
854
+ }
855
+ // Return the number of blocking (synchronous) external scripts in the page.
856
+ function blockingScripts() {
857
+ var lastViewportElem = lastViewportElement();
858
+ if (!lastViewportElem) {
859
+ // If we can not find the last DOM element in the viewport,
860
+ // use the old technique of just counting sync scripts.
861
+ return syncScripts();
862
+ }
863
+ // Find all the synchronous scripts that are ABOVE the last DOM element in the
864
+ // viewport. (If they are BELOW then they do not block rendering of initial viewport.)
865
+ var aElems = document.getElementsByTagName("script");
866
+ var num = 0;
867
+ for (var i = 0, len = aElems.length; i < len; i++) {
868
+ var e = aElems[i];
869
+ if (
870
+ e.src &&
871
+ !e.async &&
872
+ !e.defer &&
873
+ 0 !== (e.compareDocumentPosition(lastViewportElem) & 4)
874
+ ) {
875
+ // If the script has a SRC and async is false and it occurs BEFORE the last viewport element,
876
+ // then increment the counter.
877
+ num++;
878
+ }
879
+ }
880
+ return num;
881
+ }
882
+ // Return the number of blocking (synchronous) external scripts in the page.
883
+ function blockingStylesheets() {
884
+ var nBlocking = 0;
885
+ var aElems = document.getElementsByTagName("link");
886
+ for (var i = 0, len = aElems.length; i < len; i++) {
887
+ var e = aElems[i];
888
+ if (e.href && "stylesheet" === e.rel && 0 !== e.href.indexOf("data:")) {
889
+ if (
890
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
891
+ e.onloadcssdefined ||
892
+ "print" === e.media ||
893
+ "style" === e.as ||
894
+ ("function" === typeof e.onload && "all" === e.media)
895
+ );
896
+ else {
897
+ nBlocking++;
898
+ }
899
+ }
900
+ }
901
+ return nBlocking;
313
902
  }
314
- function q(e) {
315
- for (var t = document.getElementsByTagName(e), n = 0, r = 0, i = t.length; r < i; r++) {
316
- var a = t[r];
903
+ // Return the number of synchronous external scripts in the page.
904
+ function syncScripts() {
905
+ var aElems = document.getElementsByTagName("script");
906
+ var num = 0;
907
+ for (var i = 0, len = aElems.length; i < len; i++) {
908
+ var e = aElems[i];
909
+ if (e.src && !e.async && !e.defer) {
910
+ // If the script has a SRC and async is false, then increment the counter.
911
+ num++;
912
+ }
913
+ }
914
+ return num;
915
+ }
916
+ // Return the number of external scripts in the page.
917
+ function numScripts() {
918
+ var aElems = document.getElementsByTagName("script");
919
+ var num = 0;
920
+ for (var i = 0, len = aElems.length; i < len; i++) {
921
+ var e = aElems[i];
922
+ if (e.src) {
923
+ num++;
924
+ }
925
+ }
926
+ return num;
927
+ }
928
+ // Return the number of stylesheets in the page.
929
+ function numStylesheets() {
930
+ var aElems = document.getElementsByTagName("link");
931
+ var num = 0;
932
+ for (var i = 0, len = aElems.length; i < len; i++) {
933
+ var e = aElems[i];
934
+ if (e.href && "stylesheet" == e.rel) {
935
+ num++;
936
+ }
937
+ }
938
+ return num;
939
+ }
940
+ function inlineTagSize(tagName) {
941
+ var aElems = document.getElementsByTagName(tagName);
942
+ var size = 0;
943
+ for (var i = 0, len = aElems.length; i < len; i++) {
944
+ var e = aElems[i];
317
945
  try {
318
- n += a.innerHTML.length;
319
- } catch (a) {
320
- return pe("Error accessing inline element innerHTML."), -1;
946
+ size += e.innerHTML.length;
947
+ } catch (e) {
948
+ // It seems like IE throws an error when accessing the innerHTML property
949
+ logger.logEvent(LogEvent.InnerHtmlAccessError);
950
+ return -1;
321
951
  }
322
952
  }
323
- return n;
953
+ return size;
324
954
  }
325
- function z() {
326
- var e = "",
327
- t = M;
328
- if (_(h) && _(y)) {
329
- var n = Math.round(_(h).startTime);
330
- e = (t += n) + "fs0ls" + (u = Math.round(_(y).startTime) - n) + "le" + u;
331
- } else if (L && L.timing) {
332
- var r = L.timing,
333
- a = (function () {
334
- if (L && L.timing) {
335
- var e,
336
- t = L.timing,
337
- n = t.navigationStart;
338
- if (n) {
339
- if (
340
- L &&
341
- L.getEntriesByType &&
342
- L.getEntriesByType("paint") &&
343
- L.getEntriesByType("paint").length
344
- )
345
- for (var r = L.getEntriesByType("paint"), i = 0; i < r.length; i++) {
346
- var a = r[i];
347
- if ("first-paint" === a.name) {
348
- e = Math.round(a.startTime);
349
- break;
350
- }
351
- }
352
- else if (window.chrome && "function" == typeof window.chrome.loadTimes) {
353
- var o = window.chrome.loadTimes();
354
- o && (e = Math.round(1e3 * o.firstPaintTime - n));
355
- } else t.msFirstPaint && (e = Math.round(t.msFirstPaint - n));
356
- if (e > 0) return e;
357
- }
358
- }
359
- return pe("Paint Timing not supported."), null;
360
- })(),
361
- o = A(),
362
- s = (function () {
363
- if (i.length)
364
- for (var e = i.length - 1; e >= 0; e--) {
365
- var t = i[e];
366
- if ("largest-contentful-paint" === t.entryType) return Math.round(t.startTime);
367
- }
368
- return 0;
369
- })();
370
- e =
371
- t +
372
- (r.redirectStart ? "rs" + (r.redirectStart - t) : "") +
373
- (r.redirectEnd ? "re" + (r.redirectEnd - t) : "") +
374
- (r.fetchStart ? "fs" + (r.fetchStart - t) : "") +
375
- (r.domainLookupStart ? "ds" + (r.domainLookupStart - t) : "") +
376
- (r.domainLookupEnd ? "de" + (r.domainLookupEnd - t) : "") +
377
- (r.connectStart ? "cs" + (r.connectStart - t) : "") +
378
- (r.secureConnectionStart ? "sc" + (r.secureConnectionStart - t) : "") +
379
- (r.connectEnd ? "ce" + (r.connectEnd - t) : "") +
380
- (r.requestStart ? "qs" + (r.requestStart - t) : "") +
381
- (r.responseStart ? "bs" + (r.responseStart - t) : "") +
382
- (r.responseEnd ? "be" + (r.responseEnd - t) : "") +
383
- (r.domLoading ? "ol" + (r.domLoading - t) : "") +
384
- (r.domInteractive ? "oi" + (r.domInteractive - t) : "") +
385
- (r.domContentLoadedEventStart ? "os" + (r.domContentLoadedEventStart - t) : "") +
386
- (r.domContentLoadedEventEnd ? "oe" + (r.domContentLoadedEventEnd - t) : "") +
387
- (r.domComplete ? "oc" + (r.domComplete - t) : "") +
388
- (r.loadEventStart ? "ls" + (r.loadEventStart - t) : "") +
389
- (r.loadEventEnd ? "le" + (r.loadEventEnd - t) : "") +
390
- (a ? "sr" + a : "") +
391
- (o ? "fc" + o : "") +
392
- (s ? "lc" + s : "");
393
- } else if (_(y)) {
394
- var u;
395
- e = t + "fs0ls" + (u = Math.round(_(y).startTime)) + "le" + u;
396
- }
397
- return e;
398
- }
399
- function A() {
400
- if (L && L.getEntriesByType && L.getEntriesByType("paint"))
401
- for (var e = L.getEntriesByType("paint"), t = 0; t < e.length; t++) {
402
- var n = e[t];
403
- if ("first-contentful-paint" === n.name) return Math.round(n.startTime);
955
+ function getNavTiming() {
956
+ var s = "";
957
+ var ns = _navigationStart;
958
+ var startMark = _getMark(gStartMark);
959
+ var endMark = _getMark(gEndMark);
960
+ if (startMark && endMark) {
961
+ // This is a SPA page view, so send the SPA marks & measures instead of Nav Timing.
962
+ var start = Math.round(startMark.startTime); // the start mark is "zero"
963
+ ns += start; // "navigationStart" for a SPA is the real navigationStart plus the start mark
964
+ var end = Math.round(endMark.startTime) - start; // delta from start mark
965
+ s =
966
+ ns +
967
+ "fs" +
968
+ 0 + // fetchStart is the same as navigationStart for a SPA
969
+ "ls" +
970
+ end +
971
+ "le" +
972
+ end +
973
+ "";
974
+ } else if (perf && perf.timing) {
975
+ // Return the real Nav Timing metrics because this is the "main" page view (not a SPA)
976
+ var t = perf.timing;
977
+ var startRender = getStartRender(); // first paint
978
+ var fcp = getFcp(); // first contentful paint
979
+ var lcp = getLcp(); // largest contentful paint
980
+ s =
981
+ ns +
982
+ (t.redirectStart ? "rs" + (t.redirectStart - ns) : "") +
983
+ (t.redirectEnd ? "re" + (t.redirectEnd - ns) : "") +
984
+ (t.fetchStart ? "fs" + (t.fetchStart - ns) : "") +
985
+ (t.domainLookupStart ? "ds" + (t.domainLookupStart - ns) : "") +
986
+ (t.domainLookupEnd ? "de" + (t.domainLookupEnd - ns) : "") +
987
+ (t.connectStart ? "cs" + (t.connectStart - ns) : "") +
988
+ (t.secureConnectionStart
989
+ ? "sc" + (t.secureConnectionStart - ns)
990
+ : "") +
991
+ (t.connectEnd ? "ce" + (t.connectEnd - ns) : "") +
992
+ (t.requestStart ? "qs" + (t.requestStart - ns) : "") + // reQuest start
993
+ (t.responseStart ? "bs" + (t.responseStart - ns) : "") + // body start
994
+ (t.responseEnd ? "be" + (t.responseEnd - ns) : "") +
995
+ (t.domLoading ? "ol" + (t.domLoading - ns) : "") +
996
+ (t.domInteractive ? "oi" + (t.domInteractive - ns) : "") +
997
+ (t.domContentLoadedEventStart
998
+ ? "os" + (t.domContentLoadedEventStart - ns)
999
+ : "") +
1000
+ (t.domContentLoadedEventEnd
1001
+ ? "oe" + (t.domContentLoadedEventEnd - ns)
1002
+ : "") +
1003
+ (t.domComplete ? "oc" + (t.domComplete - ns) : "") +
1004
+ (t.loadEventStart ? "ls" + (t.loadEventStart - ns) : "") +
1005
+ (t.loadEventEnd ? "le" + (t.loadEventEnd - ns) : "") +
1006
+ (startRender ? "sr" + startRender : "") +
1007
+ (fcp ? "fc" + fcp : "") +
1008
+ (lcp ? "lc" + lcp : "") +
1009
+ "";
1010
+ } else if (endMark) {
1011
+ // This is a "main" page view that does NOT support Navigation Timing - strange.
1012
+ var end = Math.round(endMark.startTime);
1013
+ s =
1014
+ ns +
1015
+ "fs" +
1016
+ 0 + // fetchStart is the same as navigationStart
1017
+ "ls" +
1018
+ end +
1019
+ "le" +
1020
+ end +
1021
+ "";
1022
+ }
1023
+ return s;
1024
+ }
1025
+ // Return First Contentful Paint or zero if not supported.
1026
+ function getFcp() {
1027
+ if (
1028
+ perf &&
1029
+ perf.getEntriesByType &&
1030
+ perf.getEntriesByType("paint").length
1031
+ ) {
1032
+ for (
1033
+ var arr = perf.getEntriesByType("paint"), i = 0;
1034
+ i < arr.length;
1035
+ i++
1036
+ ) {
1037
+ var ppt = arr[i]; // PerformancePaintTiming object
1038
+ if ("first-contentful-paint" === ppt.name) {
1039
+ return Math.round(ppt.startTime);
1040
+ }
404
1041
  }
1042
+ }
405
1043
  return 0;
406
1044
  }
407
- function J() {
408
- if (void 0 !== LUX.customerid) return LUX.customerid;
409
- var e = Y("/js/lux.js");
410
- return e
411
- ? ((LUX.customerid = (function (e, t) {
412
- for (var n = e.split("?")[1].split("&"), r = 0, i = n.length; r < i; r++) {
413
- var a = n[r].split("=");
414
- if (t === a[0]) return a[1];
1045
+ // Return Largest Contentful Paint or zero if not supported.
1046
+ function getLcp() {
1047
+ if (gaPerfEntries.length) {
1048
+ // Find the *LAST* LCP per https://web.dev/largest-contentful-paint
1049
+ for (var i = gaPerfEntries.length - 1; i >= 0; i--) {
1050
+ var pe = gaPerfEntries[i];
1051
+ if ("largest-contentful-paint" === pe.entryType) {
1052
+ return Math.round(pe.startTime);
1053
+ }
1054
+ }
1055
+ }
1056
+ return 0;
1057
+ }
1058
+ // Return best guess at Start Render time (in ms).
1059
+ // Mostly works on just Chrome and IE.
1060
+ // Return null if not supported.
1061
+ function getStartRender() {
1062
+ if (perf && perf.timing) {
1063
+ var t = perf.timing;
1064
+ var ns = t.navigationStart;
1065
+ var startRender = void 0;
1066
+ if (ns) {
1067
+ if (
1068
+ perf &&
1069
+ perf.getEntriesByType &&
1070
+ perf.getEntriesByType("paint").length
1071
+ ) {
1072
+ // If Paint Timing API is supported, use it.
1073
+ for (
1074
+ var arr = perf.getEntriesByType("paint"), i = 0;
1075
+ i < arr.length;
1076
+ i++
1077
+ ) {
1078
+ var ppt = arr[i]; // PerformancePaintTiming object
1079
+ if ("first-paint" === ppt.name) {
1080
+ startRender = Math.round(ppt.startTime);
1081
+ break;
1082
+ }
415
1083
  }
416
- return;
417
- })(e.src, "id")),
418
- LUX.customerid)
419
- : "";
1084
+ } else if (
1085
+ window.chrome &&
1086
+ "function" === typeof window.chrome.loadTimes
1087
+ ) {
1088
+ // If chrome, get first paint time from `chrome.loadTimes`. Need extra error handling.
1089
+ var loadTimes = window.chrome.loadTimes();
1090
+ if (loadTimes) {
1091
+ startRender = Math.round(loadTimes.firstPaintTime * 1000 - ns);
1092
+ }
1093
+ } else if (t.msFirstPaint) {
1094
+ // If IE/Edge, use the prefixed `msFirstPaint` property (see http://msdn.microsoft.com/ff974719).
1095
+ startRender = Math.round(t.msFirstPaint - ns);
1096
+ }
1097
+ if (startRender) {
1098
+ return startRender;
1099
+ }
1100
+ }
1101
+ }
1102
+ logger.logEvent(LogEvent.PaintTimingNotSupported);
1103
+ return null;
420
1104
  }
421
- function Y(e) {
422
- for (var t = document.getElementsByTagName("script"), n = 0, r = t.length; n < r; n++) {
423
- var i = t[n];
424
- if (i.src && -1 !== i.src.indexOf(e)) return i;
1105
+ function getCustomerId() {
1106
+ if (typeof LUX.customerid !== "undefined") {
1107
+ // Return the id explicitly set in the JavaScript variable.
1108
+ return LUX.customerid;
1109
+ }
1110
+ // Extract the id of the lux.js script element.
1111
+ var luxScript = getScriptElement("/js/lux.js");
1112
+ if (luxScript) {
1113
+ LUX.customerid = getQuerystringParam(luxScript.src, "id");
1114
+ return LUX.customerid;
1115
+ }
1116
+ return "";
1117
+ }
1118
+ // Return the SCRIPT DOM element whose SRC contains the URL snippet.
1119
+ // This is used to find the LUX script element.
1120
+ function getScriptElement(urlsnippet) {
1121
+ var aScripts = document.getElementsByTagName("script");
1122
+ for (var i = 0, len = aScripts.length; i < len; i++) {
1123
+ var script = aScripts[i];
1124
+ if (script.src && -1 !== script.src.indexOf(urlsnippet)) {
1125
+ return script;
1126
+ }
425
1127
  }
426
1128
  return null;
427
1129
  }
428
- function K(e) {
429
- var t = 0;
430
- if (e.parentNode) for (; (e = e.parentNode); ) t++;
431
- return t;
1130
+ function getQuerystringParam(url, name) {
1131
+ var qs = url.split("?")[1];
1132
+ var aTuples = qs.split("&");
1133
+ for (var i = 0, len = aTuples.length; i < len; i++) {
1134
+ var tuple = aTuples[i];
1135
+ var aTuple = tuple.split("=");
1136
+ var key = aTuple[0];
1137
+ if (name === key) {
1138
+ return aTuple[1];
1139
+ }
1140
+ }
1141
+ return undefined;
432
1142
  }
433
- function Q() {
434
- if (L && L.getEntriesByType) {
435
- var e = performance.getEntriesByType("navigation");
436
- if (e && e.length > 0 && e[0].encodedBodySize) return e[0].encodedBodySize;
1143
+ function avgDomDepth() {
1144
+ var aElems = document.getElementsByTagName("*");
1145
+ var i = aElems.length;
1146
+ var totalParents = 0;
1147
+ while (i--) {
1148
+ totalParents += numParents(aElems[i]);
437
1149
  }
438
- return 0;
1150
+ var average = Math.round(totalParents / aElems.length);
1151
+ return average;
439
1152
  }
440
- function V() {
441
- var e = navigator.connection,
442
- t = "";
443
- return (
444
- e &&
445
- e.effectiveType &&
446
- (t =
447
- "slow-2g" === (t = e.effectiveType)
448
- ? "Slow 2G"
449
- : "2g" === t || "3g" === t || "4g" === t || "5g" === t
450
- ? t.toUpperCase()
451
- : t.charAt(0).toUpperCase() + t.slice(1)),
452
- t
1153
+ function numParents(elem) {
1154
+ var n = 0;
1155
+ if (elem.parentNode) {
1156
+ while ((elem = elem.parentNode)) {
1157
+ n++;
1158
+ }
1159
+ }
1160
+ return n;
1161
+ }
1162
+ function docHeight(doc) {
1163
+ var body = doc.body,
1164
+ docelem = doc.documentElement;
1165
+ var height = Math.max(
1166
+ body ? body.scrollHeight : 0,
1167
+ body ? body.offsetHeight : 0,
1168
+ docelem ? docelem.clientHeight : 0,
1169
+ docelem ? docelem.scrollHeight : 0,
1170
+ docelem ? docelem.offsetHeight : 0
453
1171
  );
1172
+ return height;
454
1173
  }
455
- function Z(e) {
456
- var t;
457
- if ((e || (e = document.body), e)) {
458
- var n = e.children;
459
- if (n)
460
- for (var r = 0, i = n.length; r < i; r++) {
461
- var a = n[r];
462
- $(a) && (t = a);
1174
+ function docWidth(doc) {
1175
+ var body = doc.body,
1176
+ docelem = doc.documentElement;
1177
+ var width = Math.max(
1178
+ body ? body.scrollWidth : 0,
1179
+ body ? body.offsetWidth : 0,
1180
+ docelem ? docelem.clientWidth : 0,
1181
+ docelem ? docelem.scrollWidth : 0,
1182
+ docelem ? docelem.offsetWidth : 0
1183
+ );
1184
+ return width;
1185
+ }
1186
+ // Return the main HTML document transfer size (in bytes).
1187
+ function docSize() {
1188
+ if (
1189
+ perf &&
1190
+ perf.getEntriesByType &&
1191
+ perf.getEntriesByType("navigation").length
1192
+ ) {
1193
+ var aEntries = performance.getEntriesByType("navigation");
1194
+ if (aEntries && aEntries.length > 0 && aEntries[0]["encodedBodySize"]) {
1195
+ return aEntries[0]["encodedBodySize"];
1196
+ }
1197
+ }
1198
+ return 0; // ERROR - NOT FOUND
1199
+ }
1200
+ // Return the navigation type. 0 = normal, 1 = reload, etc.
1201
+ // Return empty string if not available.
1202
+ function navigationType() {
1203
+ if (
1204
+ perf &&
1205
+ perf.navigation &&
1206
+ "undefined" != typeof perf.navigation.type
1207
+ ) {
1208
+ return perf.navigation.type;
1209
+ }
1210
+ return "";
1211
+ }
1212
+ // Return the connection type based on Network Information API.
1213
+ // Note this API is in flux.
1214
+ function connectionType() {
1215
+ var c = navigator.connection;
1216
+ var connType = "";
1217
+ if (c && c.effectiveType) {
1218
+ connType = c.effectiveType;
1219
+ if ("slow-2g" === connType) {
1220
+ connType = "Slow 2G";
1221
+ } else if (
1222
+ "2g" === connType ||
1223
+ "3g" === connType ||
1224
+ "4g" === connType ||
1225
+ "5g" === connType
1226
+ ) {
1227
+ connType = connType.toUpperCase();
1228
+ } else {
1229
+ connType = connType.charAt(0).toUpperCase() + connType.slice(1);
1230
+ }
1231
+ }
1232
+ return connType;
1233
+ }
1234
+ // Return an array of image elements that are in the top viewport.
1235
+ function imagesATF() {
1236
+ var aImages = document.getElementsByTagName("img");
1237
+ var aImagesAtf = [];
1238
+ if (aImages) {
1239
+ for (var i = 0, len = aImages.length; i < len; i++) {
1240
+ var image = aImages[i];
1241
+ if (inViewport(image)) {
1242
+ aImagesAtf.push(image);
463
1243
  }
1244
+ }
464
1245
  }
465
- return t ? Z(t) : e;
466
- }
467
- function $(e) {
468
- var t = document.documentElement.clientHeight,
469
- n = document.documentElement.clientWidth,
470
- r = (function (e) {
471
- var t = 0,
472
- n = 0;
473
- for (; e; ) (t += e.offsetLeft), (n += e.offsetTop), (e = e.offsetParent);
474
- return [t, n];
475
- })(e);
476
- return (
477
- r[0] >= 0 && r[1] >= 0 && r[0] < n && r[1] < t && e.offsetWidth > 0 && e.offsetHeight > 0
478
- );
1246
+ return aImagesAtf;
479
1247
  }
480
- function ee() {
481
- pe("Enter LUX.send().");
482
- var e = J();
483
- if (e && E && W() && !m) {
484
- j(y);
485
- var t = O(),
486
- r = (function () {
487
- var e = [];
488
- if (i.length)
489
- for (var t = 0; t < i.length; t++) {
490
- var n = i[t];
491
- "element" === n.entryType &&
492
- n.identifier &&
493
- n.startTime &&
494
- e.push(n.identifier + "|" + Math.round(n.startTime));
495
- }
496
- return e.join(",");
497
- })(),
498
- a = F(),
499
- o = "";
500
- v || (o = R());
501
- var u = H(),
502
- c = (function () {
503
- if ("function" != typeof LayoutShift) return !1;
504
- for (var e = 0, t = 0; t < i.length; t++) {
505
- var n = i[t];
506
- "layout-shift" !== n.entryType || n.hadRecentInput || (e += n.value);
507
- }
508
- return e.toFixed(6);
509
- })(),
510
- d = (function () {
511
- var e = "";
512
- if (L && L.getEntriesByName) {
513
- var t = Y("/js/lux.js");
514
- if (t) {
515
- var n = L.getEntriesByName(t.src);
516
- if (n && n.length) {
517
- var r = n[0],
518
- i = Math.round(r.domainLookupEnd - r.domainLookupStart),
519
- a = Math.round(r.connectEnd - r.connectStart),
520
- o = Math.round(r.responseStart - r.requestStart),
521
- s = Math.round(r.responseEnd - r.responseStart),
522
- u = i + a + o + s,
523
- c = LUX_t_end - LUX_t_start,
524
- d = r.encodedBodySize ? r.encodedBodySize : 0;
525
- e =
526
- "d" +
527
- i +
528
- "t" +
529
- a +
530
- "f" +
531
- o +
532
- "c" +
533
- s +
534
- "n" +
535
- u +
536
- "e" +
537
- c +
538
- "r" +
539
- X +
540
- (d ? "x" + d : "") +
541
- (B ? "l" + B : "") +
542
- "s" +
543
- (LUX_t_start - M);
544
- }
545
- }
1248
+ // Return the last element in the viewport.
1249
+ function lastViewportElement(parent) {
1250
+ if (!parent) {
1251
+ // We call this function recursively passing in the parent element,
1252
+ // but if no parent then start with BODY.
1253
+ parent = document.body;
1254
+ }
1255
+ var lastChildInViewport;
1256
+ if (parent) {
1257
+ // Got errors that parent was null so testing again here.
1258
+ // Find the last child that is in the viewport.
1259
+ // Elements are listed in DOM order.
1260
+ var aChildren = parent.children;
1261
+ if (aChildren) {
1262
+ for (var i = 0, len = aChildren.length; i < len; i++) {
1263
+ var child = aChildren[i];
1264
+ if (inViewport(child)) {
1265
+ // The children are in DOM order, so we just have to
1266
+ // save the LAST child that was in the viewport.
1267
+ lastChildInViewport = child;
546
1268
  }
547
- return e;
548
- })();
549
- document.visibilityState && "visible" !== document.visibilityState && (s |= 8);
550
- var f,
551
- g,
552
- h,
553
- w =
554
- U +
555
- "?v=" +
556
- "216&id=" +
557
- e +
558
- "&sid=" +
559
- E +
560
- "&uid=" +
561
- T +
562
- (a ? "&CD=" + a : "") +
563
- "&l=" +
564
- encodeURIComponent(ge()),
565
- k = q("script"),
566
- N = q("style"),
567
- x =
568
- (l ? "" : "&NT=" + z()) +
569
- (p ? "&LJS=" + d : "") +
570
- "&PS=ns" +
571
- (function () {
572
- for (
573
- var e = document.getElementsByTagName("script"), t = 0, n = 0, r = e.length;
574
- n < r;
575
- n++
576
- )
577
- e[n].src && t++;
578
- return t;
579
- })() +
580
- "bs" +
581
- G() +
582
- (k > -1 ? "is" + k : "") +
583
- "ss" +
584
- (function () {
585
- for (
586
- var e = document.getElementsByTagName("link"), t = 0, n = 0, r = e.length;
587
- n < r;
588
- n++
589
- ) {
590
- var i = e[n];
591
- i.href && "stylesheet" == i.rel && t++;
592
- }
593
- return t;
594
- })() +
595
- "bc" +
596
- (function () {
597
- for (
598
- var e = 0, t = document.getElementsByTagName("link"), n = 0, r = t.length;
599
- n < r;
600
- n++
601
- ) {
602
- var i = t[n];
603
- i.href &&
604
- "stylesheet" === i.rel &&
605
- 0 !== i.href.indexOf("data:") &&
606
- (i.onloadcssdefined ||
607
- "print" === i.media ||
608
- "style" === i.as ||
609
- ("function" == typeof i.onload && "all" === i.media) ||
610
- e++);
611
- }
612
- return e;
613
- })() +
614
- (N > -1 ? "ic" + N : "") +
615
- "ia" +
616
- (function () {
617
- var e = document.getElementsByTagName("img"),
618
- t = [];
619
- if (e)
620
- for (var n = 0, r = e.length; n < r; n++) {
621
- var i = e[n];
622
- $(i) && t.push(i);
623
- }
624
- return t;
625
- })().length +
626
- "it" +
627
- document.getElementsByTagName("img").length +
628
- "dd" +
629
- (function () {
630
- for (var e = document.getElementsByTagName("*"), t = e.length, n = 0; t--; )
631
- n += K(e[t]);
632
- return Math.round(n / e.length);
633
- })() +
634
- "nd" +
635
- document.getElementsByTagName("*").length +
636
- "vh" +
637
- document.documentElement.clientHeight +
638
- "vw" +
639
- document.documentElement.clientWidth +
640
- "dh" +
641
- ((f = document),
642
- (g = f.body),
643
- (h = f.documentElement),
644
- Math.max(
645
- g ? g.scrollHeight : 0,
646
- g ? g.offsetHeight : 0,
647
- h ? h.clientHeight : 0,
648
- h ? h.scrollHeight : 0,
649
- h ? h.offsetHeight : 0
650
- ) + "dw") +
651
- (function (e) {
652
- var t = e.body,
653
- n = e.documentElement;
654
- return Math.max(
655
- t ? t.scrollWidth : 0,
656
- t ? t.offsetWidth : 0,
657
- n ? n.clientWidth : 0,
658
- n ? n.scrollWidth : 0,
659
- n ? n.offsetWidth : 0
660
- );
661
- })(document) +
662
- (Q() ? "ds" + Q() : "") +
663
- (V() ? "ct" + V() + "_" : "") +
664
- "er" +
665
- n +
666
- "nt" +
667
- (L && L.navigation && void 0 !== L.navigation.type ? L.navigation.type : "") +
668
- (navigator.deviceMemory ? "dm" + Math.round(navigator.deviceMemory) : "") +
669
- (o ? "&IX=" + o : "") +
670
- (S ? "&FID=" + S : "") +
671
- (u ? "&CPU=" + u : "") +
672
- (s ? "&fl=" + s : "") +
673
- (r ? "&ET=" + r : "") +
674
- "&HN=" +
675
- encodeURIComponent(document.location.hostname) +
676
- (!1 !== c ? "&CLS=" + c : ""),
677
- C = "";
678
- if (t) {
679
- var I = w.length + x.length;
680
- if (I + t.length <= b) x += "&UT=" + t;
681
- else {
682
- var D = b - I,
683
- _ = t.lastIndexOf(",", D);
684
- (x += "&UT=" + t.substring(0, _)), (C = t.substring(_ + 1));
685
1269
  }
686
1270
  }
687
- var P = w + x;
688
- pe("Sending main LUX beacon: " + P), re(P), (m = 1), (l = 1), (v = o ? 1 : 0);
689
- for (var A = b - w.length; C; ) {
690
- var Z = "";
691
- if (C.length <= A) (Z = C), (C = "");
692
- else {
693
- var ee = C.lastIndexOf(",", A);
694
- -1 === ee && (ee = C.indexOf(",")),
695
- -1 === ee ? ((Z = C), (C = "")) : ((Z = C.substring(0, ee)), (C = C.substring(ee + 1)));
696
- }
697
- var te = w + "&UT=" + Z;
698
- pe("Sending extra User Timing beacon: " + te), re(te);
699
- }
700
- }
701
- }
702
- function te() {
703
- var e = J();
704
- if (e && E && W() && !v && m) {
705
- var t = R();
706
- if (t) {
707
- var n = F(),
708
- r =
709
- "?v=216&id=" +
710
- e +
711
- "&sid=" +
712
- E +
713
- "&uid=" +
714
- T +
715
- (n ? "&CD=" + n : "") +
716
- "&l=" +
717
- encodeURIComponent(ge()) +
718
- "&IX=" +
719
- t +
720
- (S ? "&FID=" + S : "") +
721
- "&HN=" +
722
- encodeURIComponent(document.location.hostname),
723
- i = U + r;
724
- pe("Sending Interaction Metrics beacon: " + i), re(i), (v = 1);
725
- }
726
- }
727
- }
728
- function ne() {
729
- var e = J();
730
- if (e && E && W() && m) {
731
- var t = F();
732
- if (t) {
733
- var n =
734
- "?v=216&id=" +
735
- e +
736
- "&sid=" +
737
- E +
738
- "&uid=" +
739
- T +
740
- "&CD=" +
741
- t +
742
- "&l=" +
743
- encodeURIComponent(ge()) +
744
- "&HN=" +
745
- encodeURIComponent(document.location.hostname),
746
- r = U + n;
747
- pe("Sending late Customer Data beacon: " + r), re(r);
748
- }
749
- }
750
- }
751
- function re(e) {
752
- if ("simple" !== LUX.beaconMode)
753
- return (function (e) {
754
- var t = document.createElement("script");
755
- (t.async = !0), (t.src = e);
756
- var n = document.getElementsByTagName("script");
757
- n.length
758
- ? n[0].parentNode.insertBefore(t, n[0])
759
- : ((n = document.getElementsByTagName("head")).length ||
760
- (n = document.getElementsByTagName("body")).length) &&
761
- n[0].appendChild(t);
762
- })(e);
763
- new Image().src = e;
764
- }
765
- function ie(e) {
766
- if (e.id) return e.id;
767
- for (var t, n = e; n.parentNode && n.parentNode.tagName; ) {
768
- if ((n = n.parentNode).hasAttribute("data-sctrack")) return n.getAttribute("data-sctrack");
769
- n.id && !t && (t = n.id);
770
- }
771
- var r = "INPUT" === e.tagName && "submit" === e.type,
772
- i = "BUTTON" === e.tagName,
773
- a = "A" === e.tagName;
774
- return r && e.value ? e.value : (i || a) && e.innerText ? e.innerText : t || "";
775
- }
776
- function ae() {
777
- void 0 === d.s && (d.s = Math.round(D()));
778
- }
779
- function oe(e) {
780
- if ((fe(), void 0 === d.k)) {
781
- if (((d.k = Math.round(D())), e && e.target)) {
782
- var t = ie(e.target);
783
- t && (d.ki = t);
784
- }
785
- te();
786
- }
787
- }
788
- function se(e) {
789
- if ((fe(), void 0 === d.c)) {
790
- d.c = Math.round(D());
791
- var t = null;
792
- try {
793
- e && e.target && (t = e.target);
794
- } catch (e) {
795
- pe("Error accessing event target."), (t = null);
1271
+ }
1272
+ if (lastChildInViewport) {
1273
+ // See if this last child has any children in the viewport.
1274
+ return lastViewportElement(lastChildInViewport);
1275
+ } else {
1276
+ // If NONE of the children are in the viewport, return the parent.
1277
+ // This assumes that the parent is in the viewport because it was passed in.
1278
+ return parent;
1279
+ }
1280
+ }
1281
+ // Return true if the element is in the viewport.
1282
+ function inViewport(e) {
1283
+ var vh = document.documentElement.clientHeight;
1284
+ var vw = document.documentElement.clientWidth;
1285
+ // Return true if the top-left corner is in the viewport and it has width & height.
1286
+ var lt = findPos(e);
1287
+ return (
1288
+ lt[0] >= 0 &&
1289
+ lt[1] >= 0 &&
1290
+ lt[0] < vw &&
1291
+ lt[1] < vh &&
1292
+ e.offsetWidth > 0 &&
1293
+ e.offsetHeight > 0
1294
+ );
1295
+ }
1296
+ // Return an array containing the top & left coordinates of the element.
1297
+ // from http://www.quirksmode.org/js/findpos.html
1298
+ function findPos(e) {
1299
+ var curleft = 0;
1300
+ var curtop = 0;
1301
+ while (e) {
1302
+ curleft += e.offsetLeft;
1303
+ curtop += e.offsetTop;
1304
+ e = e.offsetParent;
1305
+ }
1306
+ return [curleft, curtop];
1307
+ }
1308
+ // Mark the load time of the current page. Intended to be used in SPAs where it is not desirable to
1309
+ // send the beacon as soon as the page has finished loading.
1310
+ function _markLoadTime() {
1311
+ _mark(gEndMark);
1312
+ }
1313
+ // Beacon back the LUX data.
1314
+ function _sendLux() {
1315
+ logger.logEvent(LogEvent.SendCalled);
1316
+ var customerid = getCustomerId();
1317
+ if (
1318
+ !customerid ||
1319
+ !gSyncId ||
1320
+ !validDomain() ||
1321
+ !_sample() || // OUTSIDE the sampled range
1322
+ gbLuxSent // LUX data already sent
1323
+ ) {
1324
+ return;
1325
+ }
1326
+ var startMark = _getMark(gStartMark);
1327
+ var endMark = _getMark(gEndMark);
1328
+ if (!startMark || (endMark && endMark.startTime < startMark.startTime)) {
1329
+ // Record the synthetic loadEventStart time for this page, unless it was already recorded
1330
+ // with LUX.markLoadTime()
1331
+ _markLoadTime();
1332
+ }
1333
+ var sUT = userTimingValues(); // User Timing data
1334
+ var sET = elementTimingValues(); // Element Timing data
1335
+ var sCustomerData = customerDataValues(); // customer data
1336
+ var sIx = ""; // Interaction Metrics
1337
+ if (!gbIxSent) {
1338
+ // It is possible for the IX beacon to be sent BEFORE the "main" window.onload LUX beacon.
1339
+ // Make sure we do not send the IX data twice.
1340
+ sIx = ixValues();
1341
+ }
1342
+ var sCPU = cpuTimes();
1343
+ var DCLS = calculateDCLS();
1344
+ var sLuxjs = selfLoading();
1345
+ if (document.visibilityState && "visible" !== document.visibilityState) {
1346
+ gFlags = addFlag(gFlags, Flags.VisibilityStateNotVisible);
1347
+ }
1348
+ // We want ALL beacons to have ALL the data used for query filters (geo, pagelabel, browser, & customerdata).
1349
+ // So we create a base URL that has all the necessary information:
1350
+ var baseUrl =
1351
+ userConfig.beaconUrl +
1352
+ "?v=" +
1353
+ SCRIPT_VERSION +
1354
+ "&id=" +
1355
+ customerid +
1356
+ "&sid=" +
1357
+ gSyncId +
1358
+ "&uid=" +
1359
+ gUid +
1360
+ (sCustomerData ? "&CD=" + sCustomerData : "") +
1361
+ "&l=" +
1362
+ encodeURIComponent(_getPageLabel());
1363
+ var is = inlineTagSize("script");
1364
+ var ic = inlineTagSize("style");
1365
+ var querystring =
1366
+ // only send Nav Timing and lux.js metrics on initial pageload (not for SPA page views)
1367
+ (gbNavSent ? "" : "&NT=" + getNavTiming()) +
1368
+ (gbFirstPV ? "&LJS=" + sLuxjs : "") +
1369
+ // Page Stats
1370
+ "&PS=ns" +
1371
+ numScripts() +
1372
+ "bs" +
1373
+ blockingScripts() +
1374
+ (is > -1 ? "is" + is : "") +
1375
+ "ss" +
1376
+ numStylesheets() +
1377
+ "bc" +
1378
+ blockingStylesheets() +
1379
+ (ic > -1 ? "ic" + ic : "") +
1380
+ "ia" +
1381
+ imagesATF().length +
1382
+ "it" +
1383
+ document.getElementsByTagName("img").length + // total number of images
1384
+ "dd" +
1385
+ avgDomDepth() +
1386
+ "nd" +
1387
+ document.getElementsByTagName("*").length + // numdomelements
1388
+ "vh" +
1389
+ document.documentElement.clientHeight + // see http://www.quirksmode.org/mobile/viewports.html
1390
+ "vw" +
1391
+ document.documentElement.clientWidth +
1392
+ "dh" +
1393
+ docHeight(document) +
1394
+ "dw" +
1395
+ docWidth(document) +
1396
+ (docSize() ? "ds" + docSize() : "") + // document HTTP transfer size (bytes)
1397
+ (connectionType() ? "ct" + connectionType() + "_" : "") + // delimit with "_" since values can be non-numeric so need a way to extract with regex in VCL
1398
+ "er" +
1399
+ nErrors +
1400
+ "nt" +
1401
+ navigationType() + // reload
1402
+ (navigator.deviceMemory
1403
+ ? "dm" + Math.round(navigator.deviceMemory)
1404
+ : "") + // device memory (GB)
1405
+ (sIx ? "&IX=" + sIx : "") +
1406
+ (gFirstInputDelay ? "&FID=" + gFirstInputDelay : "") +
1407
+ (sCPU ? "&CPU=" + sCPU : "") +
1408
+ (gFlags ? "&fl=" + gFlags : "") +
1409
+ (sET ? "&ET=" + sET : "") + // element timing
1410
+ "&HN=" +
1411
+ encodeURIComponent(document.location.hostname) +
1412
+ (DCLS !== false ? "&CLS=" + DCLS : "") +
1413
+ "&PN=" +
1414
+ encodeURIComponent(document.location.pathname);
1415
+ // User Timing marks & measures
1416
+ var sUT_remainder = "";
1417
+ if (sUT) {
1418
+ var curLen = baseUrl.length + querystring.length;
1419
+ if (curLen + sUT.length <= gMaxQuerystring) {
1420
+ // Add all User Timing
1421
+ querystring += "&UT=" + sUT;
1422
+ } else {
1423
+ // Only add a substring of User Timing
1424
+ var avail_1 = gMaxQuerystring - curLen; // how much room is left in the querystring
1425
+ var iComma = sUT.lastIndexOf(",", avail_1); // as many UT tuples as possible
1426
+ querystring += "&UT=" + sUT.substring(0, iComma);
1427
+ sUT_remainder = sUT.substring(iComma + 1);
796
1428
  }
797
- if (t) {
798
- e.clientX && ((d.cx = e.clientX), (d.cy = e.clientY));
799
- var n = ie(e.target);
800
- n && (d.ci = n);
1429
+ }
1430
+ // Send the MAIN LUX beacon.
1431
+ var mainBeaconUrl = baseUrl + querystring;
1432
+ logger.logEvent(LogEvent.MainBeaconSent, [mainBeaconUrl]);
1433
+ _sendBeacon(mainBeaconUrl);
1434
+ // Set some states.
1435
+ gbLuxSent = 1;
1436
+ gbNavSent = 1;
1437
+ gbIxSent = sIx ? 1 : 0;
1438
+ // Send other beacons for JUST User Timing.
1439
+ var avail = gMaxQuerystring - baseUrl.length;
1440
+ while (sUT_remainder) {
1441
+ var sUT_cur = "";
1442
+ if (sUT_remainder.length <= avail) {
1443
+ // We can fit ALL the remaining UT params.
1444
+ sUT_cur = sUT_remainder;
1445
+ sUT_remainder = "";
1446
+ } else {
1447
+ // We have to take a subset of the remaining UT params.
1448
+ var iComma = sUT_remainder.lastIndexOf(",", avail); // as many UT tuples as possible
1449
+ if (-1 === iComma) {
1450
+ // Trouble: we have SO LITTLE available space we can not fit the first UT tuple.
1451
+ // Try it anyway but find it by searching from the front.
1452
+ iComma = sUT_remainder.indexOf(",");
1453
+ }
1454
+ if (-1 === iComma) {
1455
+ // The is only one UT tuple left, but it is bigger than the available space.
1456
+ // Take the whole tuple even tho it is too big.
1457
+ sUT_cur = sUT_remainder;
1458
+ sUT_remainder = "";
1459
+ } else {
1460
+ sUT_cur = sUT_remainder.substring(0, iComma);
1461
+ sUT_remainder = sUT_remainder.substring(iComma + 1);
1462
+ }
801
1463
  }
802
- te();
1464
+ var utBeaconUrl = baseUrl + "&UT=" + sUT_cur;
1465
+ logger.logEvent(LogEvent.UserTimingBeaconSent, [utBeaconUrl]);
1466
+ _sendBeacon(utBeaconUrl);
803
1467
  }
804
1468
  }
805
- function ue(e, t) {
806
- window.addEventListener
807
- ? window.addEventListener(e, t, !1)
808
- : window.attachEvent && window.attachEvent("on" + e, t);
1469
+ // Beacon back the IX data separately (need to sync with LUX beacon on the backend).
1470
+ function _sendIx() {
1471
+ var customerid = getCustomerId();
1472
+ if (
1473
+ !customerid ||
1474
+ !gSyncId ||
1475
+ !validDomain() ||
1476
+ !_sample() || // OUTSIDE the sampled range
1477
+ gbIxSent || // IX data already sent
1478
+ !gbLuxSent // LUX has NOT been sent yet, so wait to include it there
1479
+ ) {
1480
+ return;
1481
+ }
1482
+ var sIx = ixValues(); // Interaction Metrics
1483
+ if (sIx) {
1484
+ var sCustomerData = customerDataValues(); // customer data
1485
+ var querystring =
1486
+ "?v=" +
1487
+ SCRIPT_VERSION +
1488
+ "&id=" +
1489
+ customerid +
1490
+ "&sid=" +
1491
+ gSyncId +
1492
+ "&uid=" +
1493
+ gUid +
1494
+ (sCustomerData ? "&CD=" + sCustomerData : "") +
1495
+ "&l=" +
1496
+ encodeURIComponent(_getPageLabel()) +
1497
+ "&IX=" +
1498
+ sIx +
1499
+ (gFirstInputDelay ? "&FID=" + gFirstInputDelay : "") +
1500
+ "&HN=" +
1501
+ encodeURIComponent(document.location.hostname) +
1502
+ "&PN=" +
1503
+ encodeURIComponent(document.location.pathname);
1504
+ var beaconUrl = userConfig.beaconUrl + querystring;
1505
+ logger.logEvent(LogEvent.InteractionBeaconSent, [beaconUrl]);
1506
+ _sendBeacon(beaconUrl);
1507
+ gbIxSent = 1;
1508
+ }
1509
+ }
1510
+ // Beacon back customer data that is recorded _after_ the main beacon was sent
1511
+ // (i.e., customer data after window.onload).
1512
+ function _sendCustomerData() {
1513
+ var customerid = getCustomerId();
1514
+ if (
1515
+ !customerid ||
1516
+ !gSyncId ||
1517
+ !validDomain() ||
1518
+ !_sample() || // OUTSIDE the sampled range
1519
+ !gbLuxSent // LUX has NOT been sent yet, so wait to include it there
1520
+ ) {
1521
+ return;
1522
+ }
1523
+ var sCustomerData = customerDataValues(); // customer data
1524
+ if (sCustomerData) {
1525
+ var querystring =
1526
+ "?v=" +
1527
+ SCRIPT_VERSION +
1528
+ "&id=" +
1529
+ customerid +
1530
+ "&sid=" +
1531
+ gSyncId +
1532
+ "&uid=" +
1533
+ gUid +
1534
+ "&CD=" +
1535
+ sCustomerData +
1536
+ "&l=" +
1537
+ encodeURIComponent(_getPageLabel()) +
1538
+ "&HN=" +
1539
+ encodeURIComponent(document.location.hostname) +
1540
+ "&PN=" +
1541
+ encodeURIComponent(document.location.pathname);
1542
+ var beaconUrl = userConfig.beaconUrl + querystring;
1543
+ logger.logEvent(LogEvent.CustomDataBeaconSent, [beaconUrl]);
1544
+ _sendBeacon(beaconUrl);
1545
+ }
809
1546
  }
810
- function ce(e, t) {
811
- window.removeEventListener
812
- ? window.removeEventListener(e, t, !1)
813
- : window.detachEvent && window.detachEvent("on" + e, t);
1547
+ function _sendBeacon(url) {
1548
+ new Image().src = url;
814
1549
  }
815
- function de() {
816
- ue("scroll", ae), ue("keypress", oe), ue("mousedown", se);
1550
+ // INTERACTION METRICS
1551
+ // Register event handlers to detect Interaction Metrics.
1552
+ // We only need to detect the FIRST of each event, after which we remove the handler for that event..
1553
+ // Each event handler is a standalone function so we can reference that function in removeListener.
1554
+ // If the event(s) happen before LUX finishes, then the IX metric(s) is(are) sent with LUX.
1555
+ // Most of the time, however, IX happens *after* LUX, so we send a separate IX beacon but
1556
+ // only beacon back the first interaction that happens.
1557
+ /**
1558
+ * Get the interaction attribution name for an element
1559
+ *
1560
+ * @param {HTMLElement} el
1561
+ * @returns string
1562
+ */
1563
+ function interactionAttributionForElement(el) {
1564
+ // Default to using the element's own ID if it has one
1565
+ if (el.id) {
1566
+ return el.id;
1567
+ }
1568
+ // The next preference is to find an ancestor with the "data-sctrack" attribute
1569
+ var ancestor = el;
1570
+ // We also store the first ancestor ID that we find, so we can use it as
1571
+ // a fallback later.
1572
+ var ancestorId;
1573
+ while (ancestor.parentNode && ancestor.parentNode.tagName) {
1574
+ ancestor = ancestor.parentNode;
1575
+ if (ancestor.hasAttribute("data-sctrack")) {
1576
+ return ancestor.getAttribute("data-sctrack");
1577
+ }
1578
+ if (ancestor.id && !ancestorId) {
1579
+ ancestorId = ancestor.id;
1580
+ }
1581
+ }
1582
+ // The next preference is to use the text content of a button or link
1583
+ var isSubmitInput = el.tagName === "INPUT" && el.type === "submit";
1584
+ var isButton = el.tagName === "BUTTON";
1585
+ var isLink = el.tagName === "A";
1586
+ if (isSubmitInput && el.value) {
1587
+ return el.value;
1588
+ }
1589
+ if ((isButton || isLink) && el.innerText) {
1590
+ return el.innerText;
1591
+ }
1592
+ // The next preference is to use the first ancestor ID
1593
+ if (ancestorId) {
1594
+ return ancestorId;
1595
+ }
1596
+ // No suitable attribute was found
1597
+ return "";
817
1598
  }
818
- function fe() {
819
- ce("scroll", ae), ce("keypress", oe), ce("mousedown", se);
1599
+ function _scrollHandler() {
1600
+ // Leave handlers IN PLACE so we can track which ID is clicked/keyed.
1601
+ // _removeIxHandlers();
1602
+ if ("undefined" === typeof ghIx["s"]) {
1603
+ ghIx["s"] = Math.round(_now());
1604
+ // _sendIx(); // wait for key or click to send the IX beacon
1605
+ }
820
1606
  }
821
- function me(e) {
822
- var t, n;
823
- return e
824
- ? Number(new Date()) + "00000"
825
- : Number(new Date()) +
826
- "" +
827
- ((t = parseInt(1e5 * Math.random())), ((n = "00000") + t).slice(-n.length));
1607
+ function _keyHandler(e) {
1608
+ _removeIxHandlers();
1609
+ if ("undefined" === typeof ghIx["k"]) {
1610
+ ghIx["k"] = Math.round(_now());
1611
+ if (e && e.target) {
1612
+ var trackId = interactionAttributionForElement(e.target);
1613
+ if (trackId) {
1614
+ ghIx["ki"] = trackId;
1615
+ }
1616
+ }
1617
+ _sendIx();
1618
+ }
828
1619
  }
829
- function le(e) {
830
- var t = (function (e) {
1620
+ function _clickHandler(e) {
1621
+ _removeIxHandlers();
1622
+ if ("undefined" === typeof ghIx["c"]) {
1623
+ ghIx["c"] = Math.round(_now());
1624
+ var target = null;
831
1625
  try {
832
- for (var t = document.cookie.split(";"), n = 0; n < t.length; n++) {
833
- var r = t[n].split("=");
834
- if (e === r[0].trim()) return unescape(r[1]);
1626
+ // Seeing "Permission denied" errors, so do a simple try-catch.
1627
+ if (e && e.target) {
1628
+ target = e.target;
835
1629
  }
836
1630
  } catch (e) {
837
- pe("Error accessing document.cookie.");
1631
+ logger.logEvent(LogEvent.EventTargetAccessError);
1632
+ target = null;
838
1633
  }
839
- return;
840
- })("lux_uid");
841
- if (!t || t.length < 11) t = e;
842
- else {
843
- var n = parseInt(t.substring(0, 10));
844
- Number(new Date()) / 1e3 - n > 86400 && (t = e);
1634
+ if (target) {
1635
+ if (e.clientX) {
1636
+ // Save the x&y of the mouse click.
1637
+ ghIx["cx"] = e.clientX;
1638
+ ghIx["cy"] = e.clientY;
1639
+ }
1640
+ var trackId = interactionAttributionForElement(e.target);
1641
+ if (trackId) {
1642
+ ghIx["ci"] = trackId;
1643
+ }
1644
+ }
1645
+ _sendIx();
845
1646
  }
846
- return ve(t), t;
847
1647
  }
848
- function ve(e) {
849
- return (
850
- (function (e, t, n) {
851
- try {
852
- document.cookie =
853
- e + "=" + escape(t) + (n ? "; max-age=" + n : "") + "; path=/; SameSite=Lax";
854
- } catch (e) {
855
- pe("Error setting document.cookie.");
1648
+ // Wrapper to support older browsers (<= IE8)
1649
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1650
+ function addListener(type, callback, useCapture) {
1651
+ if (useCapture === void 0) {
1652
+ useCapture = false;
1653
+ }
1654
+ if (window.addEventListener) {
1655
+ window.addEventListener(type, callback, useCapture);
1656
+ } else if (window.attachEvent) {
1657
+ window.attachEvent("on" + type, callback);
1658
+ }
1659
+ }
1660
+ // Wrapper to support older browsers (<= IE8)
1661
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1662
+ function removeListener(type, callback, useCapture) {
1663
+ if (useCapture === void 0) {
1664
+ useCapture = false;
1665
+ }
1666
+ if (window.removeEventListener) {
1667
+ window.removeEventListener(type, callback, useCapture);
1668
+ } else if (window.detachEvent) {
1669
+ window.detachEvent("on" + type, callback);
1670
+ }
1671
+ }
1672
+ function _addUnloadHandlers() {
1673
+ var onunload = function () {
1674
+ gFlags = addFlag(gFlags, Flags.BeaconSentFromUnloadHandler);
1675
+ _sendLux();
1676
+ _sendIx();
1677
+ };
1678
+ // As well as visibilitychange, we also listen for pagehide. This is really only for browsers
1679
+ // with buggy visibilitychange implementations. For much older browsers that don't support
1680
+ // pagehide, we use unload and beforeunload.
1681
+ if ("onpagehide" in self) {
1682
+ addListener("pagehide", onunload, true);
1683
+ } else {
1684
+ addListener("unload", onunload, true);
1685
+ addListener("beforeunload", onunload, true);
1686
+ }
1687
+ addListener(
1688
+ "visibilitychange",
1689
+ function () {
1690
+ if (document.visibilityState === "hidden") {
1691
+ onunload();
856
1692
  }
857
- })("lux_uid", e, 1800),
858
- e
1693
+ },
1694
+ true
859
1695
  );
860
1696
  }
861
- function ge() {
862
- if (void 0 !== LUX.label) return LUX.label;
863
- if (void 0 !== LUX.jspagelabel) {
864
- var e = Function('"use strict"; return ' + LUX.jspagelabel);
1697
+ function _addIxHandlers() {
1698
+ addListener("scroll", _scrollHandler);
1699
+ addListener("keypress", _keyHandler);
1700
+ addListener("mousedown", _clickHandler);
1701
+ }
1702
+ function _removeIxHandlers() {
1703
+ removeListener("scroll", _scrollHandler);
1704
+ removeListener("keypress", _keyHandler);
1705
+ removeListener("mousedown", _clickHandler);
1706
+ }
1707
+ // This is a big number (epoch ms . random) that is used to matchup a LUX beacon with a separate IX beacon
1708
+ // (because they get sent at different times). Each "page view" (including SPA) should have a
1709
+ // unique gSyncId.
1710
+ function createSyncId(inSampleBucket) {
1711
+ if (inSampleBucket === void 0) {
1712
+ inSampleBucket = false;
1713
+ }
1714
+ if (inSampleBucket) {
1715
+ // "00" matches all sample rates
1716
+ return "".concat(Number(new Date()), "00000");
1717
+ }
1718
+ return ""
1719
+ .concat(Number(new Date()))
1720
+ .concat(_padLeft(String(Math.round(100000 * Math.random())), "00000"));
1721
+ }
1722
+ // Unique ID (also known as Session ID)
1723
+ // We use this to track all the page views in a single user session.
1724
+ // If there is NOT a UID then set it to the new value (which is the same as the "sync ID" for this page).
1725
+ // Refresh its expiration date and return its value.
1726
+ function refreshUniqueId(newValue) {
1727
+ var uid = _getCookie("lux_uid");
1728
+ if (!uid || uid.length < 11) {
1729
+ uid = newValue;
1730
+ } else {
1731
+ // Prevent sessions lasting more than 24 hours.
1732
+ // The first 10 characters of uid is the epoch time when the session started.
1733
+ var uidStart = parseInt(uid.substring(0, 10));
1734
+ var now_2 = Number(new Date()) / 1000; // in seconds
1735
+ if (now_2 - uidStart > 24 * 60 * 60) {
1736
+ // older than 24 hours - reset to new value
1737
+ uid = newValue;
1738
+ }
1739
+ }
1740
+ setUniqueId(uid);
1741
+ return uid;
1742
+ }
1743
+ function setUniqueId(uid) {
1744
+ _setCookie("lux_uid", uid, gSessionTimeout);
1745
+ return uid;
1746
+ }
1747
+ // We use gUid (session ID) to do sampling. We make this available to customers so
1748
+ // they can do sampling (A/B testing) using the same session ID.
1749
+ function _getUniqueId() {
1750
+ return gUid;
1751
+ }
1752
+ // Return the current page label.
1753
+ function _getPageLabel() {
1754
+ if (typeof LUX.label !== "undefined") {
1755
+ gFlags = addFlag(gFlags, Flags.PageLabelFromLabelProp);
1756
+ return LUX.label;
1757
+ } else if (typeof LUX.jspagelabel !== "undefined") {
1758
+ var evaluateJsPageLabel = Function(
1759
+ '"use strict"; return '.concat(LUX.jspagelabel)
1760
+ );
865
1761
  try {
866
- var t = e();
867
- if (t) return t;
1762
+ var label = evaluateJsPageLabel();
1763
+ if (label) {
1764
+ gFlags = addFlag(gFlags, Flags.PageLabelFromGlobalVariable);
1765
+ return label;
1766
+ }
868
1767
  } catch (e) {
869
- console.log("Error evaluating customer settings LUX page label:", e);
1768
+ logger.logEvent(LogEvent.PageLabelEvaluationError, [
1769
+ LUX.jspagelabel,
1770
+ e,
1771
+ ]);
870
1772
  }
871
1773
  }
1774
+ // default to document.title
1775
+ gFlags = addFlag(gFlags, Flags.PageLabelFromDocumentTitle);
872
1776
  return document.title;
873
1777
  }
874
- function pe(t) {
875
- e.push(t), LUX.debug && console.log("LUX: " + t);
876
- }
877
- N.forEach(function (e) {
878
- window.addEventListener(e, I, x);
879
- }),
880
- k &&
881
- ("complete" == document.readyState
882
- ? ee()
883
- : ue("load", function () {
884
- setTimeout(ee, 200);
885
- }),
886
- ue("beforeunload", ee),
887
- ue("unload", ee),
888
- ue("beforeunload", te),
889
- ue("unload", te)),
890
- de();
891
- var he = {
892
- mark: j,
893
- measure: function (e, t, n) {
894
- if ((pe("Enter LUX.measure(), name = " + e), void 0 === t && _(h) && (t = h), L)) {
895
- if (L.measure) return t ? (n ? L.measure(e, t, n) : L.measure(e, t)) : L.measure(e);
896
- if (L.webkitMeasure) return L.webkitMeasure(e, t, n);
897
- }
898
- var r = 0,
899
- i = D();
900
- if (t) {
901
- var a = _(t);
902
- if (a) r = a.startTime;
903
- else {
904
- if (!(L && L.timing && L.timing[t])) return;
905
- r = L.timing[t] - L.timing.navigationStart;
906
- }
907
- }
908
- if (n) {
909
- var o = _(n);
910
- if (o) i = o.startTime;
911
- else {
912
- if (!(L && L.timing && L.timing[n])) return;
913
- i = L.timing[n] - L.timing.navigationStart;
1778
+ // Return true if the hostname of the current page is one of the listed domains.
1779
+ function validDomain() {
1780
+ // Our signup process is such that a customer almost always deploys lux.js BEFORE we
1781
+ // enable LUX for their account. In which case, the list of domains is empty and no
1782
+ // beacons will be sent. Further, that version of lux.js will be cached at the CDN
1783
+ // and browser for a week. Instead, do the domain validation on the backend in VCL.
1784
+ return true;
1785
+ }
1786
+ function _getCookie(name) {
1787
+ try {
1788
+ // Seeing "Permission denied" errors, so do a simple try-catch.
1789
+ var aTuples = document.cookie.split(";");
1790
+ for (var i = 0; i < aTuples.length; i++) {
1791
+ var aTuple = aTuples[i].split("=");
1792
+ if (name === aTuple[0].trim()) {
1793
+ // cookie name starts with " " if not first
1794
+ return unescape(aTuple[1]);
914
1795
  }
915
1796
  }
916
- c.push({ name: e, entryType: "measure", startTime: r, duration: i - r });
917
- },
918
- init: function () {
919
- pe("Enter LUX.init()."),
920
- (d = {}),
921
- fe(),
922
- de(),
923
- (l = 0),
924
- (m = 0),
925
- (v = 0),
926
- (p = 0),
927
- (E = me()),
928
- (T = le(E)),
929
- i.splice(0),
930
- (s = 0),
931
- (s |= 1),
932
- j(h);
933
- },
934
- send: ee,
935
- addData: function (e, t) {
936
- pe("Enter LUX.addData(), name = " + e + ", value = " + t);
937
- var n = typeof t;
938
- "string" !== typeof e || ("string" !== n && "number" !== n && "boolean" !== n) || (f[e] = t),
939
- m && (o && clearTimeout(o), (o = setTimeout(ne, 100)));
940
- },
941
- getSessionId: function () {
942
- return T;
943
- },
944
- getDebug: function () {
945
- return e;
946
- },
947
- forceSample: function () {
948
- ve(me(!0)), console.log("Sampling has been turned on for this session.");
949
- },
950
- doUpdate: function (e, n) {
951
- if (e && t < e && document.body && !g) {
952
- pe("Updating cached version of lux.js from 216 to " + e + "."), (g = 1);
953
- var r = Y("/js/lux.js");
954
- if (r)
955
- if ("function" == typeof fetch) fetch(r.src, { cache: "reload" });
956
- else {
957
- var i = document.createElement("iframe");
958
- (i.style.display = "none"),
959
- (i.id = "LUX_update_iframe"),
960
- (i.src =
961
- "//cdn.speedcurve.com/luxupdate.php?src=" +
962
- encodeURIComponent(r.src) +
963
- (n ? "&tw=" + n : "")),
964
- document.body.appendChild(i);
965
- }
966
- }
967
- },
968
- cmd: function (e) {
969
- var t = e.shift();
970
- "function" == typeof he[t] && he[t].apply(he, e);
971
- },
972
- beaconMode: w,
973
- beaconUrl: U,
974
- samplerate: X,
975
- auto: k,
976
- label: void 0 !== LUX.label ? LUX.label : void 0,
977
- jspagelabel: void 0 !== LUX.jspagelabel ? LUX.jspagelabel : void 0,
978
- version: t,
979
- ae: [],
980
- al: [],
981
- debug: !!LUX.debug,
1797
+ } catch (e) {
1798
+ logger.logEvent(LogEvent.CookieReadError);
1799
+ }
1800
+ return undefined;
1801
+ }
1802
+ function _setCookie(name, value, seconds) {
1803
+ try {
1804
+ document.cookie =
1805
+ name +
1806
+ "=" +
1807
+ escape(value) +
1808
+ (seconds ? "; max-age=" + seconds : "") +
1809
+ "; path=/; SameSite=Lax";
1810
+ } catch (e) {
1811
+ logger.logEvent(LogEvent.CookieSetError);
1812
+ }
1813
+ }
1814
+ // "padding" MUST be the length of the resulting string, eg, "0000" if you want a result of length 4.
1815
+ function _padLeft(str, padding) {
1816
+ return (padding + str).slice(-padding.length);
1817
+ }
1818
+ // Set "LUX.auto=false" to disable send results automatically and
1819
+ // instead you must call LUX.send() explicitly.
1820
+ if (userConfig.auto) {
1821
+ if (document.readyState === "complete") {
1822
+ // If onload has already passed, send the beacon now.
1823
+ _sendLux();
1824
+ } else {
1825
+ // Ow, send the beacon slightly after window.onload.
1826
+ addListener("load", function () {
1827
+ setTimeout(_sendLux, 200);
1828
+ });
1829
+ }
1830
+ }
1831
+ // Add the unload handlers for auto mode, or when LUX.measureUntil is "pagehidden"
1832
+ if (userConfig.sendBeaconOnPageHidden) {
1833
+ _addUnloadHandlers();
1834
+ }
1835
+ // Regardless of userConfig.auto, we need to register the IX handlers immediately.
1836
+ _addIxHandlers();
1837
+ // This is the public API.
1838
+ var _LUX = userConfig;
1839
+ // Functions
1840
+ _LUX.mark = _mark;
1841
+ _LUX.measure = _measure;
1842
+ _LUX.init = _init;
1843
+ _LUX.markLoadTime = _markLoadTime;
1844
+ _LUX.send = _sendLux;
1845
+ _LUX.addData = _addData;
1846
+ _LUX.getSessionId = _getUniqueId; // so customers can do their own sampling
1847
+ _LUX.getDebug = function () {
1848
+ return logger.getEvents();
1849
+ };
1850
+ _LUX.forceSample = function () {
1851
+ logger.logEvent(LogEvent.ForceSampleCalled);
1852
+ setUniqueId(createSyncId(true));
982
1853
  };
983
- return (
984
- LUX.ac &&
985
- LUX.ac.length &&
986
- LUX.ac.forEach(function (e) {
987
- var t = e.shift();
988
- "function" == typeof he[t] && he[t].apply(he, e);
989
- }),
990
- void 0 !== window.LUX_ae &&
991
- window.LUX_ae.forEach(function (e) {
992
- r(e);
993
- }),
994
- pe("lux.js evaluation end."),
995
- he
996
- );
1854
+ _LUX.doUpdate = function () {
1855
+ // Deprecated, intentionally empty.
1856
+ };
1857
+ _LUX.cmd = _runCommand;
1858
+ // Public properties
1859
+ _LUX.version = SCRIPT_VERSION;
1860
+ // "Private" properties
1861
+ _LUX.ae = []; // array for error handler (ignored)
1862
+ _LUX.al = []; // array for Long Tasks (ignored)
1863
+ /**
1864
+ * Run a command from the command queue
1865
+ */
1866
+ function _runCommand(_a) {
1867
+ var fn = _a[0],
1868
+ args = _a.slice(1);
1869
+ if (typeof _LUX[fn] === "function") {
1870
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1871
+ _LUX[fn].apply(_LUX, args);
1872
+ }
1873
+ }
1874
+ // Process the command queue
1875
+ if (LUX.ac && LUX.ac.length) {
1876
+ LUX.ac.forEach(_runCommand);
1877
+ }
1878
+ // process the error events that happened before lux.js got loaded
1879
+ if (typeof window.LUX_ae !== "undefined") {
1880
+ window.LUX_ae.forEach(errorHandler);
1881
+ }
1882
+ logger.logEvent(LogEvent.EvaluationEnd);
1883
+ return _LUX;
997
1884
  })();
998
- var LUX_t_end = Date.now();
999
-
1000
- // -----------------------------------------------------------------------------
1001
- // More settings
1002
- // -----------------------------------------------------------------------------
1003
- //
1004
- // This ID usually appended to the end of the lux.js as a query string when
1005
- // using the SpeedCurve hosted version - but we have to include it here as this
1006
- // is self hosted.
1007
- LUX.customerid = 47044334;
1885
+ window.LUX = LUX;
1886
+ var LUX_t_end = now();
1008
1887
 
1009
- // Turn on the image-based beacon, rather than have a remote JavaScript file
1010
- // fetched and executed.
1011
- LUX.beaconMode = "simple";
1888
+ // ---------------------------------------------------------------------------
1889
+ // More settings
1890
+ // ---------------------------------------------------------------------------
1891
+ //
1892
+ // This ID usually appended to the end of the lux.js as a query string when
1893
+ // using the SpeedCurve hosted version - but we have to include it here as
1894
+ // this is self hosted.
1895
+ LUX.customerid = 47044334;
1012
1896
 
1013
- // Setting debug to `true` shows what happening as it happens. Running
1014
- // `LUX.getDebug()` in the browser's console will show the history of what's
1015
- // happened.
1016
- LUX.debug = false;
1897
+ // Setting debug to `true` shows what happening as it happens. Running
1898
+ // `LUX.getDebug()` in the browser's console will show the history of what's
1899
+ // happened.
1900
+ // LUX.debug = true;
1017
1901
 
1018
- // Forces sampling - useful for when used with `debug = true`
1019
- // LUX.forceSample()
1902
+ // Forces sampling - useful for when used with `debug = true`
1903
+ // LUX.forceSample()
1020
1904
 
1021
- // -----------------------------------------------------------------------------
1022
- // End of more settings
1023
- // -----------------------------------------------------------------------------
1905
+ // ---------------------------------------------------------------------------
1906
+ // End of more settings
1907
+ // ---------------------------------------------------------------------------
1908
+ })();