govuk_publishing_components 31.1.0 → 31.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,13 +20,13 @@
20
20
  */
21
21
 
22
22
  (function () {
23
- "use strict";
23
+ 'use strict';
24
24
 
25
25
  function now() {
26
26
  return Date.now ? Date.now() : +new Date();
27
27
  }
28
28
 
29
- var LUX_t_start = now();
29
+ var scriptStartTime = now();
30
30
 
31
31
  function fromObject(obj) {
32
32
  var autoMode = getProperty(obj, "auto", true);
@@ -45,6 +45,7 @@
45
45
  samplerate: getProperty(obj, "samplerate", 100),
46
46
  sendBeaconOnPageHidden: getProperty(obj, "sendBeaconOnPageHidden", autoMode),
47
47
  trackErrors: getProperty(obj, "trackErrors", true),
48
+ pagegroups: getProperty(obj, "pagegroups", undefined),
48
49
  };
49
50
  }
50
51
  function getProperty(obj, key, defaultValue) {
@@ -67,6 +68,7 @@
67
68
  DataCollectionStart: 9,
68
69
  UnloadHandlerTriggered: 10,
69
70
  OnloadHandlerTriggered: 11,
71
+ MarkLoadTimeCalled: 12,
70
72
  // Data collection events
71
73
  SessionIsSampled: 21,
72
74
  SessionIsNotSampled: 22,
@@ -95,16 +97,17 @@
95
97
  this.events = [];
96
98
  }
97
99
  Logger.prototype.logEvent = function (event, args) {
98
- if (args === void 0) {
99
- args = [];
100
- }
100
+ if (args === void 0) { args = []; }
101
101
  this.events.push([now(), event, args]);
102
102
  };
103
103
  Logger.prototype.getEvents = function () {
104
104
  return this.events;
105
105
  };
106
106
  return Logger;
107
- })();
107
+ }());
108
+
109
+ var START_MARK = "LUX_start";
110
+ var END_MARK = "LUX_end";
108
111
 
109
112
  var Flags = {
110
113
  InitCalled: 1 << 0,
@@ -116,15 +119,81 @@
116
119
  PageLabelFromDocumentTitle: 1 << 6,
117
120
  PageLabelFromLabelProp: 1 << 7,
118
121
  PageLabelFromGlobalVariable: 1 << 8,
122
+ PageLabelFromPagegroup: 1 << 9,
119
123
  };
120
124
  function addFlag(flags, flag) {
121
125
  return flags | flag;
122
126
  }
123
127
 
128
+ function hasParentNode(el) {
129
+ if (el.parentNode && el.parentNode.tagName) {
130
+ return true;
131
+ }
132
+ return false;
133
+ }
134
+
135
+ /**
136
+ * Get the interaction attribution name for an element
137
+ *
138
+ * @param {HTMLElement} el
139
+ * @returns string
140
+ */
141
+ function interactionAttributionForElement(el) {
142
+ // Our first preference is to use the data-sctrack attribute from anywhere in the tree
143
+ var trackId = getClosestScTrackAttribute(el);
144
+ if (trackId) {
145
+ return trackId;
146
+ }
147
+ // The second preference is to use the element's ID
148
+ if (el.id) {
149
+ return el.id;
150
+ }
151
+ // The third preference is to use the text content of a button or link
152
+ var isSubmitInput = el.tagName === "INPUT" && el.type === "submit";
153
+ var isButton = el.tagName === "BUTTON";
154
+ var isLink = el.tagName === "A";
155
+ if (isSubmitInput && el.value) {
156
+ return el.value;
157
+ }
158
+ if ((isButton || isLink) && el.innerText) {
159
+ return el.innerText;
160
+ }
161
+ if (hasParentNode(el)) {
162
+ return interactionAttributionForElement(el.parentNode);
163
+ }
164
+ // No suitable attribute was found
165
+ return "";
166
+ }
167
+ function getClosestScTrackAttribute(el) {
168
+ var _a;
169
+ if (el.hasAttribute("data-sctrack")) {
170
+ var trackId = (_a = el.getAttribute("data-sctrack")) === null || _a === void 0 ? void 0 : _a.trim();
171
+ if (trackId) {
172
+ return trackId;
173
+ }
174
+ }
175
+ if (hasParentNode(el)) {
176
+ return getClosestScTrackAttribute(el.parentNode);
177
+ }
178
+ return null;
179
+ }
180
+
181
+ var _a;
124
182
  // If the various performance APIs aren't available, we export an empty object to
125
183
  // prevent having to make regular typeof checks.
126
184
  var performance = window.performance || {};
127
- var timing = performance.timing || {};
185
+ var timing = performance.timing || {
186
+ // If performance.timing isn't available, we attempt to polyfill the navigationStart value.
187
+ // Our first attempt is from LUX.ns, which is the time that the snippet execution began. If this
188
+ // is not available, we fall back to the time that the current script execution began.
189
+ navigationStart: ((_a = window.LUX) === null || _a === void 0 ? void 0 : _a.ns) || scriptStartTime,
190
+ };
191
+ function msSinceNavigationStart() {
192
+ if (performance.now) {
193
+ return performance.now();
194
+ }
195
+ return now() - timing.navigationStart;
196
+ }
128
197
  /**
129
198
  * Simple wrapper around performance.getEntriesByType to provide fallbacks for
130
199
  * legacy browsers, and work around edge cases where undefined is returned instead
@@ -136,19 +205,81 @@
136
205
  if (entries && entries.length) {
137
206
  return entries;
138
207
  }
139
- } else if (typeof performance.webkitGetEntriesByType === "function") {
140
- var entries = performance.webkitGetEntriesByType(type);
141
- if (entries && entries.length) {
142
- return entries;
143
- }
144
208
  }
145
209
  return [];
146
210
  }
147
211
 
212
+ var Matching = /** @class */ (function () {
213
+ function Matching() {
214
+ }
215
+ Matching.isMatching = function (pattern, url) {
216
+ var regexp = Matching.createRegexpFromPattern(pattern);
217
+ return url.match(regexp) ? true : false;
218
+ };
219
+ /**
220
+ * Converts string pattern to RegExp object
221
+ * @return RegExp
222
+ */
223
+ Matching.createRegexpFromPattern = function (pattern) {
224
+ var regexp;
225
+ if (pattern == "/") {
226
+ regexp = this.getRegexpForHostnameRoot();
227
+ }
228
+ else if (!pattern.includes(Matching.wildcard)) {
229
+ regexp = this.getRegexpForExactString(pattern);
230
+ }
231
+ else if (pattern.charAt(0) == "/") {
232
+ regexp = this.createRegexpFromPathname(pattern);
233
+ }
234
+ else {
235
+ regexp = this.createRegexpFromPathname(pattern, false);
236
+ }
237
+ return regexp;
238
+ };
239
+ /**
240
+ * Converts URL pathname string pattern to RegExp object
241
+ * Multile wildcards (*) are supported
242
+ * @return RegExp
243
+ */
244
+ Matching.createRegexpFromPathname = function (pattern, anyDomain) {
245
+ if (anyDomain === void 0) { anyDomain = true; }
246
+ pattern = this.escapeStringForRegexp(pattern);
247
+ var expression = "^" +
248
+ (anyDomain ? Matching.domainExpression : "") +
249
+ pattern.replaceAll(Matching.wildcard, ".*?") +
250
+ "$";
251
+ return new RegExp(expression, "i");
252
+ };
253
+ /**
254
+ * Matches hostname root (e.g. "/", "somedomain.com/", "www.somedomain.co.nz/")
255
+ * Trailing slash is mandatory
256
+ * @return RegExp
257
+ */
258
+ Matching.getRegexpForHostnameRoot = function () {
259
+ return new RegExp("^" + Matching.domainExpression + "/$", "i");
260
+ };
261
+ /**
262
+ * Matches exact string (no wildcard provided)
263
+ * @return RegExp
264
+ */
265
+ Matching.getRegexpForExactString = function (string) {
266
+ return new RegExp("^" + this.escapeStringForRegexp(string) + "/?$", "i");
267
+ };
268
+ /**
269
+ * Escape special symbols in regexp string
270
+ * @param string
271
+ */
272
+ Matching.escapeStringForRegexp = function (string) {
273
+ // we don't escape * because it's our own special symbol!
274
+ return string.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&");
275
+ };
276
+ Matching.wildcard = "*";
277
+ Matching.domainExpression = "[a-zA-Z0-9-.]{1,61}[a-zA-Z0-9]\\.[a-zA-Z]{2,}";
278
+ return Matching;
279
+ }());
280
+
148
281
  var LUX = window.LUX || {};
149
- // Get a timestamp as close to navigationStart as possible.
150
- var _navigationStart = LUX.ns ? LUX.ns : now();
151
- var LUX_t_end = LUX_t_start;
282
+ var scriptEndTime = scriptStartTime;
152
283
  LUX = (function () {
153
284
  // -------------------------------------------------------------------------
154
285
  // Settings
@@ -159,30 +290,25 @@
159
290
  /// End
160
291
  // -------------------------------------------------------------------------
161
292
 
162
- var SCRIPT_VERSION = "301";
293
+ var SCRIPT_VERSION = "302";
163
294
  var logger = new Logger();
164
- var userConfig = fromObject(LUX);
295
+ var globalConfig = fromObject(LUX);
165
296
  logger.logEvent(LogEvent.EvaluationStart, [SCRIPT_VERSION]);
166
297
  // Log JS errors.
167
298
  var nErrors = 0;
168
299
  function errorHandler(e) {
169
- if (!userConfig.trackErrors) {
300
+ if (!globalConfig.trackErrors) {
170
301
  return;
171
302
  }
172
303
  nErrors++;
173
- if (
174
- e &&
175
- typeof e.filename !== "undefined" &&
176
- typeof e.message !== "undefined"
177
- ) {
304
+ if (e && typeof e.filename !== "undefined" && typeof e.message !== "undefined") {
178
305
  // Always send LUX errors
179
- var isLuxError =
180
- e.filename.indexOf("/lux.js?") > -1 || e.message.indexOf("LUX") > -1;
181
- if (isLuxError || (nErrors <= userConfig.maxErrors && _sample())) {
306
+ var isLuxError = e.filename.indexOf("/lux.js?") > -1 || e.message.indexOf("LUX") > -1;
307
+ if (isLuxError || (nErrors <= globalConfig.maxErrors && _sample())) {
182
308
  // Sample & limit other errors.
183
309
  // Send the error beacon.
184
310
  new Image().src =
185
- userConfig.errorBeaconUrl +
311
+ globalConfig.errorBeaconUrl +
186
312
  "?v=" +
187
313
  SCRIPT_VERSION +
188
314
  "&id=" +
@@ -210,18 +336,14 @@
210
336
  // Note: This code was later added to the LUX snippet. In the snippet we ONLY collect
211
337
  // Long Task entries because that is the only entry type that can not be buffered.
212
338
  // We _copy_ any Long Tasks collected by the snippet and ignore it after that.
213
- var gaSnippetLongTasks =
214
- typeof window.LUX_al === "object" ? window.LUX_al : [];
339
+ var gaSnippetLongTasks = typeof window.LUX_al === "object" ? window.LUX_al : [];
215
340
  var gaPerfEntries = gaSnippetLongTasks.slice(); // array of Long Tasks (prefer the array from the snippet)
216
341
  if (typeof PerformanceObserver === "function") {
217
342
  var perfObserver = new PerformanceObserver(function (list) {
218
343
  list.getEntries().forEach(function (entry) {
219
344
  logger.logEvent(LogEvent.PerformanceEntryReceived, [entry]);
220
345
  // Only record long tasks that weren't already recorded by the PerformanceObserver in the snippet
221
- if (
222
- entry.entryType !== "longtask" ||
223
- gaPerfEntries.indexOf(entry) === -1
224
- ) {
346
+ if (entry.entryType !== "longtask" || gaPerfEntries.indexOf(entry) === -1) {
225
347
  gaPerfEntries.push(entry);
226
348
  }
227
349
  });
@@ -231,10 +353,7 @@
231
353
  perfObserver.observe({ type: "longtask", buffered: true });
232
354
  }
233
355
  if (typeof LargestContentfulPaint === "function") {
234
- perfObserver.observe({
235
- type: "largest-contentful-paint",
236
- buffered: true,
237
- });
356
+ perfObserver.observe({ type: "largest-contentful-paint", buffered: true });
238
357
  }
239
358
  if (typeof PerformanceElementTiming === "function") {
240
359
  perfObserver.observe({ type: "element", buffered: true });
@@ -245,25 +364,21 @@
245
364
  if (typeof LayoutShift === "function") {
246
365
  perfObserver.observe({ type: "layout-shift", buffered: true });
247
366
  }
248
- } catch (e) {
367
+ }
368
+ catch (e) {
249
369
  logger.logEvent(LogEvent.PerformanceObserverError, [e]);
250
370
  }
251
371
  }
252
372
  // Bitmask of flags for this session & page
253
373
  var gFlags = 0;
254
- // array of marks where each element is a hash
255
- var gaMarks = typeof LUX.gaMarks !== "undefined" ? LUX.gaMarks : [];
256
- // array of measures where each element is a hash
257
- var gaMeasures =
258
- typeof LUX.gaMeasures !== "undefined" ? LUX.gaMeasures : [];
374
+ var gaMarks = [];
375
+ var gaMeasures = [];
259
376
  var ghIx = {}; // hash for Interaction Metrics (scroll, click, keyboard)
260
377
  var ghData = {}; // hash for data that is specific to the customer (eg, userid, conversion info)
261
378
  var gbLuxSent = 0; // have we sent the LUX data? (avoid sending twice in unload)
262
379
  var gbNavSent = 0; // have we sent the Nav Timing beacon yet? (avoid sending twice for SPA)
263
380
  var gbIxSent = 0; // have we sent the IX data? (avoid sending twice for SPA)
264
381
  var gbFirstPV = 1; // this is the first page view (vs. a SPA "soft nav")
265
- var gStartMark = "LUX_start"; // the name of the mark that corresponds to "navigationStart" for SPA
266
- var gEndMark = "LUX_end"; // the name of the mark that corresponds to "loadEventStart" for SPA
267
382
  var gSessionTimeout = 30 * 60; // number of seconds after which we consider a session to have "timed out" (used for calculating bouncerate)
268
383
  var gSyncId = createSyncId(); // if we send multiple beacons, use this to sync them (eg, LUX & IX) (also called "luxid")
269
384
  var gUid = refreshUniqueId(gSyncId); // cookie for this session ("Unique ID")
@@ -271,32 +386,23 @@
271
386
  var gMaxMeasureTimeout; // setTimeout timer for sending the beacon after a maximum measurement time
272
387
  var gMaxQuerystring = 8190; // split the beacon querystring if it gets longer than this
273
388
  if (_sample()) {
274
- logger.logEvent(LogEvent.SessionIsSampled, [userConfig.samplerate]);
275
- } else {
276
- logger.logEvent(LogEvent.SessionIsNotSampled, [userConfig.samplerate]);
277
- }
278
- var gLuxSnippetStart = 0;
279
- if (timing.navigationStart) {
280
- _navigationStart = timing.navigationStart;
281
- // Record when the LUX snippet was evaluated relative to navigationStart.
282
- gLuxSnippetStart = LUX.ns ? LUX.ns - _navigationStart : 0;
283
- } else {
389
+ logger.logEvent(LogEvent.SessionIsSampled, [globalConfig.samplerate]);
390
+ }
391
+ else {
392
+ logger.logEvent(LogEvent.SessionIsNotSampled, [globalConfig.samplerate]);
393
+ }
394
+ var gLuxSnippetStart = LUX.ns ? LUX.ns - timing.navigationStart : 0;
395
+ if (!performance.timing) {
284
396
  logger.logEvent(LogEvent.NavTimingNotSupported);
285
397
  gFlags = addFlag(gFlags, Flags.NavTimingNotSupported);
286
398
  }
287
- logger.logEvent(LogEvent.NavigationStart, [_navigationStart]);
399
+ logger.logEvent(LogEvent.NavigationStart, [timing.navigationStart]);
288
400
  ////////////////////// FID BEGIN
289
401
  // FIRST INPUT DELAY (FID)
290
402
  // The basic idea behind FID is to attach various input event listeners and measure the time
291
403
  // between when the event happens and when the handler executes. That is FID.
292
404
  var gFirstInputDelay; // this is FID
293
- var gaEventTypes = [
294
- "click",
295
- "mousedown",
296
- "keydown",
297
- "touchstart",
298
- "pointerdown",
299
- ]; // NOTE: does NOT include scroll!
405
+ var gaEventTypes = ["click", "mousedown", "keydown", "touchstart", "pointerdown"]; // NOTE: does NOT include scroll!
300
406
  var ghListenerOptions = { passive: true, capture: true };
301
407
  // Record the FIRST input delay.
302
408
  function recordDelay(delay) {
@@ -321,18 +427,10 @@
321
427
  }
322
428
  function removeListeners() {
323
429
  window.removeEventListener("pointerup", onPointerUp, ghListenerOptions);
324
- window.removeEventListener(
325
- "pointercancel",
326
- onPointerCancel,
327
- ghListenerOptions
328
- );
430
+ window.removeEventListener("pointercancel", onPointerCancel, ghListenerOptions);
329
431
  }
330
432
  window.addEventListener("pointerup", onPointerUp, ghListenerOptions);
331
- window.addEventListener(
332
- "pointercancel",
333
- onPointerCancel,
334
- ghListenerOptions
335
- );
433
+ window.addEventListener("pointercancel", onPointerCancel, ghListenerOptions);
336
434
  }
337
435
  // Record FID as the delta between when the event happened and when the
338
436
  // listener was able to execute.
@@ -341,7 +439,8 @@
341
439
  try {
342
440
  // Seeing "Permission denied" errors, so do a simple try-catch.
343
441
  bCancelable = evt.cancelable;
344
- } catch (e) {
442
+ }
443
+ catch (e) {
345
444
  // bail - no need to return anything
346
445
  logger.logEvent(LogEvent.InputEventPermissionError);
347
446
  return;
@@ -360,10 +459,11 @@
360
459
  return;
361
460
  }
362
461
  var delay = now_1 - eventTimeStamp;
363
- if ("pointerdown" == evt.type) {
462
+ if (evt.type === "pointerdown") {
364
463
  // special case
365
464
  onPointerDown(delay);
366
- } else {
465
+ }
466
+ else {
367
467
  recordDelay(delay);
368
468
  }
369
469
  }
@@ -374,118 +474,150 @@
374
474
  });
375
475
  ////////////////////// FID END
376
476
  /**
377
- * Returns the time elapsed (in ms) since navigationStart. For SPAs, returns
378
- * the time elapsed since the last LUX.init call.
379
- *
380
- * When `absolute = true` the time is always relative to navigationStart, even
381
- * in SPAs.
382
- */
477
+ * Returns the time elapsed (in ms) since navigationStart. For SPAs, returns
478
+ * the time elapsed since the last LUX.init call.
479
+ *
480
+ * When `absolute = true` the time is always relative to navigationStart, even
481
+ * in SPAs.
482
+ */
383
483
  function _now(absolute) {
384
- var msSinceNavigationStart = now() - _navigationStart;
385
- var startMark = _getMark(gStartMark);
484
+ var sinceNavigationStart = msSinceNavigationStart();
485
+ var startMark = _getMark(START_MARK);
386
486
  // For SPA page views, we use our internal mark as a reference point
387
487
  if (startMark && !absolute) {
388
- return msSinceNavigationStart - startMark.startTime;
488
+ return sinceNavigationStart - startMark.startTime;
389
489
  }
390
490
  // For "regular" page views, we can use performance.now() if it's available...
391
- if (performance.now) {
392
- return performance.now();
393
- }
394
- // ... or we can use navigationStart as a reference point
395
- return msSinceNavigationStart;
491
+ return sinceNavigationStart;
396
492
  }
397
- // set a mark
398
- // NOTE: It's possible to set multiple marks with the same name.
399
- function _mark(name) {
400
- logger.logEvent(LogEvent.MarkCalled, [name]);
493
+ // This is a wrapper around performance.mark that falls back to a polyfill when the User Timing
494
+ // API isn't supported.
495
+ function _mark() {
496
+ var _a, _b;
497
+ var args = [];
498
+ for (var _i = 0; _i < arguments.length; _i++) {
499
+ args[_i] = arguments[_i];
500
+ }
501
+ logger.logEvent(LogEvent.MarkCalled, args);
401
502
  if (performance.mark) {
402
- return performance.mark(name);
403
- } else if (performance.webkitMark) {
404
- return performance.webkitMark(name);
405
- }
406
- gFlags = addFlag(gFlags, Flags.UserTimingNotSupported);
407
- // Shim
408
- var entry = {
409
- name: name,
410
- detail: null,
411
- entryType: "mark",
412
- startTime: _now(),
413
- duration: 0,
414
- };
415
- gaMarks.push(entry);
416
- return entry;
417
- }
418
- // compute a measurement (delta)
419
- function _measure(name, startMarkName, endMarkName) {
420
- logger.logEvent(LogEvent.MeasureCalled, [
421
- name,
422
- startMarkName,
423
- endMarkName,
424
- ]);
425
- if (typeof startMarkName === "undefined" && _getMark(gStartMark)) {
426
- // If a start mark is not specified, but the user has called _init() to set a new start,
427
- // then use the new start base time (similar to navigationStart) as the start mark.
428
- startMarkName = gStartMark;
503
+ // Use the native performance.mark where possible...
504
+ return performance.mark.apply(performance, args);
505
+ }
506
+ // ...Otherwise provide a polyfill
507
+ if (__ENABLE_POLYFILLS) {
508
+ var name_1 = args[0];
509
+ var detail = ((_a = args[1]) === null || _a === void 0 ? void 0 : _a.detail) || null;
510
+ var startTime = ((_b = args[1]) === null || _b === void 0 ? void 0 : _b.startTime) || _now();
511
+ var entry = {
512
+ entryType: "mark",
513
+ duration: 0,
514
+ name: name_1,
515
+ detail: detail,
516
+ startTime: startTime,
517
+ };
518
+ gaMarks.push(entry);
519
+ gFlags = addFlag(gFlags, Flags.UserTimingNotSupported);
520
+ return entry;
521
+ }
522
+ }
523
+ // This is a wrapper around performance.measure that falls back to a polyfill when the User Timing
524
+ // API isn't supported.
525
+ function _measure() {
526
+ var args = [];
527
+ for (var _i = 0; _i < arguments.length; _i++) {
528
+ args[_i] = arguments[_i];
529
+ }
530
+ logger.logEvent(LogEvent.MeasureCalled, args);
531
+ var name = args[0];
532
+ var startMarkName = args[1];
533
+ var endMarkName = args[2];
534
+ var options;
535
+ if (typeof startMarkName === "object") {
536
+ options = args[1];
537
+ startMarkName = options.start;
538
+ endMarkName = options.end;
539
+ }
540
+ if (typeof startMarkName === "undefined") {
541
+ // Without a start mark specified, performance.measure defaults to using navigationStart
542
+ if (_getMark(START_MARK)) {
543
+ // For SPAs that have already called LUX.init(), we use our internal start mark instead of
544
+ // navigationStart
545
+ startMarkName = START_MARK;
546
+ }
547
+ else {
548
+ // For regular page views, we need to patch the navigationStart behaviour because IE11 throws
549
+ // a SyntaxError without a start mark
550
+ startMarkName = "navigationStart";
551
+ }
552
+ // Since we've potentially modified the start mark, we need to shove it back into whichever
553
+ // argument it belongs in.
554
+ if (options) {
555
+ // If options were provided, we need to avoid specifying a start mark if an end mark and
556
+ // duration were already specified.
557
+ if (!options.end || !options.duration) {
558
+ args[1].start = startMarkName;
559
+ }
560
+ }
561
+ else {
562
+ args[1] = startMarkName;
563
+ }
429
564
  }
430
565
  if (performance.measure) {
431
- // IE 11 does not handle null and undefined correctly
432
- if (startMarkName) {
433
- if (endMarkName) {
434
- return performance.measure(name, startMarkName, endMarkName);
435
- } else {
436
- return performance.measure(name, startMarkName);
566
+ // Use the native performance.measure where possible...
567
+ return performance.measure.apply(performance, args);
568
+ }
569
+ // ...Otherwise provide a polyfill
570
+ if (__ENABLE_POLYFILLS) {
571
+ var startTime = typeof startMarkName === "number" ? startMarkName : 0;
572
+ var endTime = typeof endMarkName === "number" ? endMarkName : _now();
573
+ var throwError = function (missingMark) {
574
+ throw new DOMException("Failed to execute 'measure' on 'Performance': The mark '".concat(missingMark, "' does not exist"));
575
+ };
576
+ if (typeof startMarkName === "string") {
577
+ var startMark = _getMark(startMarkName);
578
+ if (startMark) {
579
+ startTime = startMark.startTime;
580
+ }
581
+ else if (timing[startMarkName]) {
582
+ // the mark name can also be a property from Navigation Timing
583
+ startTime = timing[startMarkName] - timing.navigationStart;
584
+ }
585
+ else {
586
+ throwError(startMarkName);
437
587
  }
438
- } else {
439
- return performance.measure(name);
440
588
  }
441
- } else if (performance.webkitMeasure) {
442
- return performance.webkitMeasure(name, startMarkName, endMarkName);
589
+ if (typeof endMarkName === "string") {
590
+ var endMark = _getMark(endMarkName);
591
+ if (endMark) {
592
+ endTime = endMark.startTime;
593
+ }
594
+ else if (timing[endMarkName]) {
595
+ // the mark name can also be a property from Navigation Timing
596
+ endTime = timing[endMarkName] - timing.navigationStart;
597
+ }
598
+ else {
599
+ throwError(endMarkName);
600
+ }
601
+ }
602
+ var duration = Math.round(endTime) - Math.round(startTime);
603
+ var detail = null;
604
+ if (options) {
605
+ if (options.duration) {
606
+ duration = options.duration;
607
+ }
608
+ detail = options.detail;
609
+ }
610
+ var entry = {
611
+ entryType: "measure",
612
+ name: name,
613
+ detail: detail,
614
+ startTime: startTime,
615
+ duration: duration,
616
+ };
617
+ gaMeasures.push(entry);
618
+ gFlags = addFlag(gFlags, Flags.UserTimingNotSupported);
619
+ return entry;
443
620
  }
444
- // shim:
445
- var startTime = 0,
446
- endTime = _now();
447
- if (startMarkName) {
448
- var startMark = _getMark(startMarkName);
449
- if (startMark) {
450
- startTime = startMark.startTime;
451
- } else if (timing[startMarkName]) {
452
- // the mark name can also be a property from Navigation Timing
453
- startTime = timing[startMarkName] - timing.navigationStart;
454
- } else {
455
- throw new DOMException(
456
- "Failed to execute 'measure' on 'Performance': The mark '".concat(
457
- startMarkName,
458
- "' does not exist"
459
- )
460
- );
461
- }
462
- }
463
- if (endMarkName) {
464
- var endMark = _getMark(endMarkName);
465
- if (endMark) {
466
- endTime = endMark.startTime;
467
- } else if (timing[endMarkName]) {
468
- // the mark name can also be a property from Navigation Timing
469
- endTime = timing[endMarkName] - timing.navigationStart;
470
- } else {
471
- throw new DOMException(
472
- "Failed to execute 'measure' on 'Performance': The mark '".concat(
473
- endMarkName,
474
- "' does not exist"
475
- )
476
- );
477
- }
478
- }
479
- // Shim
480
- var entry = {
481
- name: name,
482
- detail: null,
483
- entryType: "measure",
484
- startTime: startTime,
485
- duration: endTime - startTime,
486
- };
487
- gaMeasures.push(entry);
488
- return entry;
489
621
  }
490
622
  // Return THE LAST mark that matches the name.
491
623
  function _getMark(name) {
@@ -522,62 +654,55 @@
522
654
  function userTimingValues() {
523
655
  // The User Timing spec allows for there to be multiple marks with the same name,
524
656
  // and multiple measures with the same name. But we can only send back one value
525
- // for a name, so we always take the MAX value. We do this by first creating a
526
- // hash that has the max value for each name.
657
+ // for a name, so we always take the maximum value.
527
658
  var hUT = {};
528
- var startMark = _getMark(gStartMark);
529
- var endMark = _getMark(gEndMark);
659
+ var startMark = _getMark(START_MARK);
660
+ // For user timing values taken in a SPA page load, we need to adjust them
661
+ // so that they're zeroed against the last LUX.init() call.
662
+ var tZero = startMark ? startMark.startTime : 0;
530
663
  // marks
531
- var aMarks = _getMarks();
532
- if (aMarks) {
533
- aMarks.forEach(function (m) {
534
- if (m === startMark || m === endMark) {
535
- // Don't include the internal marks in the beacon
536
- return;
537
- }
538
- var name = m.name;
539
- // For user timing values taken in a SPA page load, we need to adjust them
540
- // so that they're zeroed against the last LUX.init() call. We zero every
541
- // UT value except for the internal LUX start mark.
542
- var tZero =
543
- name !== gStartMark && startMark ? startMark.startTime : 0;
544
- var markTime = Math.round(m.startTime - tZero);
545
- if (markTime < 0) {
546
- // Exclude marks that were taken before the current SPA page view
547
- return;
548
- }
549
- if (typeof hUT[name] === "undefined") {
550
- hUT[name] = markTime;
551
- } else {
552
- hUT[name] = Math.max(markTime, hUT[name]);
553
- }
554
- });
555
- }
664
+ _getMarks().forEach(function (mark) {
665
+ var name = mark.name;
666
+ if (name === START_MARK || name === END_MARK) {
667
+ // Don't include the internal marks in the beacon
668
+ return;
669
+ }
670
+ var startTime = Math.round(mark.startTime - tZero);
671
+ if (startTime < 0) {
672
+ // Exclude marks that were taken before the current SPA page view
673
+ return;
674
+ }
675
+ if (typeof hUT[name] === "undefined") {
676
+ hUT[name] = { startTime: startTime };
677
+ }
678
+ else {
679
+ hUT[name].startTime = Math.max(startTime, hUT[name].startTime);
680
+ }
681
+ });
556
682
  // measures
557
- var aMeasures = _getMeasures();
558
- if (aMeasures) {
559
- aMeasures.forEach(function (m) {
560
- if (startMark && m.startTime < startMark.startTime) {
561
- // Exclude measures that were taken before the current SPA page view
562
- return;
563
- }
564
- var name = m.name;
565
- var measureTime = Math.round(m.duration);
566
- if (typeof hUT[name] === "undefined") {
567
- hUT[name] = measureTime;
568
- } else {
569
- hUT[name] = Math.max(measureTime, hUT[name]);
570
- }
571
- });
572
- }
573
- // OK. hUT is now a hash (associative array) whose keys are the names of the
574
- // marks & measures, and the value is the max value. Here we create a tuple
575
- // for each name|value pair and then join them.
576
- var aUT = [];
577
- var aNames = Object.keys(hUT);
578
- aNames.forEach(function (name) {
579
- aUT.push(name + "|" + hUT[name]);
683
+ _getMeasures().forEach(function (measure) {
684
+ if (startMark && measure.startTime < startMark.startTime) {
685
+ // Exclude measures that were taken before the current SPA page view
686
+ return;
687
+ }
688
+ var name = measure.name;
689
+ var startTime = Math.round(measure.startTime - tZero);
690
+ var duration = Math.round(measure.duration);
691
+ if (typeof hUT[name] === "undefined" || startTime > hUT[name].startTime) {
692
+ hUT[name] = { startTime: startTime, duration: duration };
693
+ }
580
694
  });
695
+ // Convert the user timing values into a delimited string. This string takes the format
696
+ // markName|startTime,measureName|startTime|duration,[markName...]
697
+ var aUT = [];
698
+ for (var utName in hUT) {
699
+ var _a = hUT[utName], startTime = _a.startTime, duration = _a.duration;
700
+ var utParts = [utName, startTime];
701
+ if (typeof duration !== "undefined") {
702
+ utParts.push(duration);
703
+ }
704
+ aUT.push(utParts.join("|"));
705
+ }
581
706
  return aUT.join(",");
582
707
  }
583
708
  // Return a string of Element Timing Metrics formatted for beacon querystring.
@@ -607,14 +732,14 @@
607
732
  if (gaPerfEntries.length) {
608
733
  // Long Task start times are relative to NavigationStart which is "0".
609
734
  // But if it is a SPA then the relative start time is gStartMark.
610
- var startMark = _getMark(gStartMark);
735
+ var startMark = _getMark(START_MARK);
611
736
  var tZero = startMark ? startMark.startTime : 0;
612
737
  // Do not include Long Tasks that start after the page is done.
613
738
  // For full page loads, "done" is loadEventEnd.
614
739
  var tEnd = timing.loadEventEnd - timing.navigationStart;
615
740
  if (startMark) {
616
741
  // For SPA page loads (determined by the presence of a start mark), "done" is gEndMark.
617
- var endMark = _getMark(gEndMark);
742
+ var endMark = _getMark(END_MARK);
618
743
  if (endMark) {
619
744
  tEnd = endMark.startTime;
620
745
  }
@@ -629,7 +754,8 @@
629
754
  // In a SPA it is possible that we were in the middle of a Long Task when
630
755
  // LUX.init() was called. If so, only include the duration after tZero.
631
756
  dur -= tZero - p.startTime;
632
- } else if (p.startTime >= tEnd) {
757
+ }
758
+ else if (p.startTime >= tEnd) {
633
759
  // In a SPA it is possible that a Long Task started after loadEventEnd but before our
634
760
  // callback from setTimeout(200) happened. Do not include anything that started after tEnd.
635
761
  continue;
@@ -654,16 +780,15 @@
654
780
  hCPUDetails[jsType] = "";
655
781
  }
656
782
  var hStats = cpuStats(hCPUDetails[jsType]);
657
- var sStats =
658
- ",n|" +
783
+ var sStats = ",n|" +
659
784
  hStats["count"] +
660
785
  ",d|" +
661
786
  hStats["median"] +
662
787
  ",x|" +
663
788
  hStats["max"] +
664
789
  (0 === hStats["fci"] ? "" : ",i|" + hStats["fci"]); // only add FCI if it is non-zero
665
- sCPU += "s|" + hCPU[jsType] + sStats + hCPUDetails[jsType];
666
- return sCPU;
790
+ sCPU += "s|" + hCPU[jsType] + sStats + hCPUDetails[jsType];
791
+ return sCPU;
667
792
  }
668
793
  // Return a hash of "stats" about the CPU details incl. count, max, and median.
669
794
  function cpuStats(sDetails) {
@@ -690,7 +815,8 @@
690
815
  // More than 5 seconds of inactivity!
691
816
  // FCI is the previous value we set (eg, FCI or the _end_ of the previous Long Task)
692
817
  bFoundFci = true;
693
- } else {
818
+ }
819
+ else {
694
820
  // Less than 5 seconds of inactivity
695
821
  fci = start + dur; // FCI is now the end of this Long Task
696
822
  }
@@ -730,7 +856,8 @@
730
856
  if (aValues.length % 2) {
731
857
  // Return the middle value.
732
858
  return aValues[half];
733
- } else {
859
+ }
860
+ else {
734
861
  // Return the average of the two middle values.
735
862
  return Math.round((aValues[half - 1] + aValues[half]) / 2.0);
736
863
  }
@@ -752,7 +879,7 @@
752
879
  var fb = Math.round(r.responseStart - r.requestStart); // first byte
753
880
  var content = Math.round(r.responseEnd - r.responseStart);
754
881
  var networkDuration = dns + tcp + fb + content;
755
- var parseEval = LUX_t_end - LUX_t_start;
882
+ var parseEval = scriptEndTime - scriptStartTime;
756
883
  var transferSize = r.encodedBodySize ? r.encodedBodySize : 0;
757
884
  // Instead of a delimiter use a 1-letter abbreviation as a separator.
758
885
  sLuxjs =
@@ -769,11 +896,11 @@
769
896
  "e" +
770
897
  parseEval +
771
898
  "r" +
772
- userConfig.samplerate + // sample rate
773
- (transferSize ? "x" + transferSize : "") +
774
- (gLuxSnippetStart ? "l" + gLuxSnippetStart : "") +
899
+ globalConfig.samplerate + // sample rate
900
+ (typeof transferSize === "number" ? "x" + transferSize : "") +
901
+ (typeof gLuxSnippetStart === "number" ? "l" + gLuxSnippetStart : "") +
775
902
  "s" +
776
- (LUX_t_start - _navigationStart) + // when lux.js started getting evaluated relative to navigationStart
903
+ (scriptStartTime - timing.navigationStart) + // when lux.js started getting evaluated relative to navigationStart
777
904
  "";
778
905
  }
779
906
  }
@@ -795,13 +922,14 @@
795
922
  // _addData()
796
923
  function _addData(name, value) {
797
924
  logger.logEvent(LogEvent.AddDataCalled, [name, value]);
798
- var typeN = typeof name;
799
925
  var typeV = typeof value;
800
- if (
801
- "string" === typeN &&
802
- ("string" === typeV || "number" === typeV || "boolean" === typeV)
803
- ) {
804
- ghData[name] = value;
926
+ if (typeof name === "string") {
927
+ if (typeV === "string" || typeV === "number" || typeV === "boolean") {
928
+ ghData[name] = value;
929
+ }
930
+ if (typeV === "undefined" || value === null) {
931
+ delete ghData[name];
932
+ }
805
933
  }
806
934
  if (gbLuxSent) {
807
935
  // This is special: We want to allow customers to call LUX.addData()
@@ -820,14 +948,11 @@
820
948
  // _sample()
821
949
  // Return true if beacons for this page should be sampled.
822
950
  function _sample() {
823
- if (
824
- typeof gUid === "undefined" ||
825
- typeof userConfig.samplerate === "undefined"
826
- ) {
951
+ if (typeof gUid === "undefined" || typeof globalConfig.samplerate === "undefined") {
827
952
  return false; // bail
828
953
  }
829
954
  var nThis = ("" + gUid).substr(-2); // number for THIS page - from 00 to 99
830
- return parseInt(nThis) < userConfig.samplerate;
955
+ return parseInt(nThis) < globalConfig.samplerate;
831
956
  }
832
957
  // Return a string of Customer Data formatted for beacon querystring.
833
958
  function customerDataValues() {
@@ -849,7 +974,7 @@
849
974
  // Some customers (incorrectly) call LUX.init on the very first page load of a SPA. This would
850
975
  // cause some first-page-only data (like paint metrics) to be lost. To prevent this, we silently
851
976
  // bail from this function when we detect an unnecessary LUX.init call.
852
- var endMark = _getMark(gEndMark);
977
+ var endMark = _getMark(END_MARK);
853
978
  if (!endMark) {
854
979
  return;
855
980
  }
@@ -872,7 +997,7 @@
872
997
  gFlags = 0;
873
998
  gFlags = addFlag(gFlags, Flags.InitCalled);
874
999
  // Mark the "navigationStart" for this SPA page.
875
- _mark(gStartMark);
1000
+ _mark(START_MARK);
876
1001
  // Reset the maximum measure timeout
877
1002
  createMaxMeasureTimeout();
878
1003
  }
@@ -890,12 +1015,7 @@
890
1015
  var num = 0;
891
1016
  for (var i = 0, len = aElems.length; i < len; i++) {
892
1017
  var e = aElems[i];
893
- if (
894
- e.src &&
895
- !e.async &&
896
- !e.defer &&
897
- 0 !== (e.compareDocumentPosition(lastViewportElem) & 4)
898
- ) {
1018
+ if (e.src && !e.async && !e.defer && 0 !== (e.compareDocumentPosition(lastViewportElem) & 4)) {
899
1019
  // If the script has a SRC and async is false and it occurs BEFORE the last viewport element,
900
1020
  // then increment the counter.
901
1021
  num++;
@@ -915,8 +1035,7 @@
915
1035
  e.onloadcssdefined ||
916
1036
  "print" === e.media ||
917
1037
  "style" === e.as ||
918
- (typeof e.onload === "function" && e.media === "all")
919
- );
1038
+ (typeof e.onload === "function" && e.media === "all")) ;
920
1039
  else {
921
1040
  nBlocking++;
922
1041
  }
@@ -968,7 +1087,8 @@
968
1087
  var e = aElems[i];
969
1088
  try {
970
1089
  size += e.innerHTML.length;
971
- } catch (e) {
1090
+ }
1091
+ catch (e) {
972
1092
  // It seems like IE throws an error when accessing the innerHTML property
973
1093
  logger.logEvent(LogEvent.InnerHtmlAccessError);
974
1094
  return -1;
@@ -978,9 +1098,9 @@
978
1098
  }
979
1099
  function getNavTiming() {
980
1100
  var s = "";
981
- var ns = _navigationStart;
982
- var startMark = _getMark(gStartMark);
983
- var endMark = _getMark(gEndMark);
1101
+ var ns = timing.navigationStart;
1102
+ var startMark = _getMark(START_MARK);
1103
+ var endMark = _getMark(END_MARK);
984
1104
  if (startMark && endMark) {
985
1105
  // This is a SPA page view, so send the SPA marks & measures instead of Nav Timing.
986
1106
  var start = Math.round(startMark.startTime); // the start mark is "zero"
@@ -995,7 +1115,8 @@
995
1115
  "le" +
996
1116
  end +
997
1117
  "";
998
- } else if (performance.timing) {
1118
+ }
1119
+ else if (performance.timing) {
999
1120
  // Return the real Nav Timing metrics because this is the "main" page view (not a SPA)
1000
1121
  var t = timing;
1001
1122
  var startRender = getStartRender(); // first paint
@@ -1009,21 +1130,15 @@
1009
1130
  (t.domainLookupStart ? "ds" + (t.domainLookupStart - ns) : "") +
1010
1131
  (t.domainLookupEnd ? "de" + (t.domainLookupEnd - ns) : "") +
1011
1132
  (t.connectStart ? "cs" + (t.connectStart - ns) : "") +
1012
- (t.secureConnectionStart
1013
- ? "sc" + (t.secureConnectionStart - ns)
1014
- : "") +
1133
+ (t.secureConnectionStart ? "sc" + (t.secureConnectionStart - ns) : "") +
1015
1134
  (t.connectEnd ? "ce" + (t.connectEnd - ns) : "") +
1016
1135
  (t.requestStart ? "qs" + (t.requestStart - ns) : "") + // reQuest start
1017
1136
  (t.responseStart ? "bs" + (t.responseStart - ns) : "") + // body start
1018
1137
  (t.responseEnd ? "be" + (t.responseEnd - ns) : "") +
1019
1138
  (t.domLoading ? "ol" + (t.domLoading - ns) : "") +
1020
1139
  (t.domInteractive ? "oi" + (t.domInteractive - ns) : "") +
1021
- (t.domContentLoadedEventStart
1022
- ? "os" + (t.domContentLoadedEventStart - ns)
1023
- : "") +
1024
- (t.domContentLoadedEventEnd
1025
- ? "oe" + (t.domContentLoadedEventEnd - ns)
1026
- : "") +
1140
+ (t.domContentLoadedEventStart ? "os" + (t.domContentLoadedEventStart - ns) : "") +
1141
+ (t.domContentLoadedEventEnd ? "oe" + (t.domContentLoadedEventEnd - ns) : "") +
1027
1142
  (t.domComplete ? "oc" + (t.domComplete - ns) : "") +
1028
1143
  (t.loadEventStart ? "ls" + (t.loadEventStart - ns) : "") +
1029
1144
  (t.loadEventEnd ? "le" + (t.loadEventEnd - ns) : "") +
@@ -1031,7 +1146,8 @@
1031
1146
  (fcp ? "fc" + fcp : "") +
1032
1147
  (lcp ? "lc" + lcp : "") +
1033
1148
  "";
1034
- } else if (endMark) {
1149
+ }
1150
+ else if (endMark) {
1035
1151
  // This is a "main" page view that does NOT support Navigation Timing - strange.
1036
1152
  var end = Math.round(endMark.startTime);
1037
1153
  s =
@@ -1076,36 +1192,19 @@
1076
1192
  // Return null if not supported.
1077
1193
  function getStartRender() {
1078
1194
  if (performance.timing) {
1079
- var t = timing;
1080
- var ns = t.navigationStart;
1081
- var startRender = void 0;
1082
- if (ns) {
1083
- var paintEntries = getEntriesByType("paint");
1084
- if (paintEntries.length) {
1085
- // If Paint Timing API is supported, use it.
1086
- for (var i = 0; i < paintEntries.length; i++) {
1087
- var entry = paintEntries[i];
1088
- if (entry.name === "first-paint") {
1089
- startRender = Math.round(entry.startTime);
1090
- break;
1091
- }
1195
+ var paintEntries = getEntriesByType("paint");
1196
+ if (paintEntries.length) {
1197
+ // If Paint Timing API is supported, use it.
1198
+ for (var i = 0; i < paintEntries.length; i++) {
1199
+ var entry = paintEntries[i];
1200
+ if (entry.name === "first-paint") {
1201
+ return Math.round(entry.startTime);
1092
1202
  }
1093
- } else if (
1094
- window.chrome &&
1095
- typeof window.chrome.loadTimes === "function"
1096
- ) {
1097
- // If chrome, get first paint time from `chrome.loadTimes`. Need extra error handling.
1098
- var loadTimes = window.chrome.loadTimes();
1099
- if (loadTimes) {
1100
- startRender = Math.round(loadTimes.firstPaintTime * 1000 - ns);
1101
- }
1102
- } else if (t.msFirstPaint) {
1103
- // If IE/Edge, use the prefixed `msFirstPaint` property (see http://msdn.microsoft.com/ff974719).
1104
- startRender = Math.round(t.msFirstPaint - ns);
1105
1203
  }
1106
1204
  }
1107
- if (startRender) {
1108
- return startRender;
1205
+ else if (timing.msFirstPaint && __ENABLE_POLYFILLS) {
1206
+ // If IE/Edge, use the prefixed `msFirstPaint` property (see http://msdn.microsoft.com/ff974719).
1207
+ return Math.round(timing.msFirstPaint - timing.navigationStart);
1109
1208
  }
1110
1209
  }
1111
1210
  logger.logEvent(LogEvent.PaintTimingNotSupported);
@@ -1169,27 +1268,13 @@
1169
1268
  return n;
1170
1269
  }
1171
1270
  function docHeight(doc) {
1172
- var body = doc.body,
1173
- docelem = doc.documentElement;
1174
- var height = Math.max(
1175
- body ? body.scrollHeight : 0,
1176
- body ? body.offsetHeight : 0,
1177
- docelem ? docelem.clientHeight : 0,
1178
- docelem ? docelem.scrollHeight : 0,
1179
- docelem ? docelem.offsetHeight : 0
1180
- );
1271
+ var body = doc.body, docelem = doc.documentElement;
1272
+ var height = Math.max(body ? body.scrollHeight : 0, body ? body.offsetHeight : 0, docelem ? docelem.clientHeight : 0, docelem ? docelem.scrollHeight : 0, docelem ? docelem.offsetHeight : 0);
1181
1273
  return height;
1182
1274
  }
1183
1275
  function docWidth(doc) {
1184
- var body = doc.body,
1185
- docelem = doc.documentElement;
1186
- var width = Math.max(
1187
- body ? body.scrollWidth : 0,
1188
- body ? body.offsetWidth : 0,
1189
- docelem ? docelem.clientWidth : 0,
1190
- docelem ? docelem.scrollWidth : 0,
1191
- docelem ? docelem.offsetWidth : 0
1192
- );
1276
+ var body = doc.body, docelem = doc.documentElement;
1277
+ var width = Math.max(body ? body.scrollWidth : 0, body ? body.offsetWidth : 0, docelem ? docelem.clientWidth : 0, docelem ? docelem.scrollWidth : 0, docelem ? docelem.offsetWidth : 0);
1193
1278
  return width;
1194
1279
  }
1195
1280
  // Return the main HTML document transfer size (in bytes).
@@ -1203,10 +1288,7 @@
1203
1288
  // Return the navigation type. 0 = normal, 1 = reload, etc.
1204
1289
  // Return empty string if not available.
1205
1290
  function navigationType() {
1206
- if (
1207
- performance.navigation &&
1208
- typeof performance.navigation.type !== "undefined"
1209
- ) {
1291
+ if (performance.navigation && typeof performance.navigation.type !== "undefined") {
1210
1292
  return performance.navigation.type;
1211
1293
  }
1212
1294
  return "";
@@ -1220,14 +1302,11 @@
1220
1302
  connType = c.effectiveType;
1221
1303
  if ("slow-2g" === connType) {
1222
1304
  connType = "Slow 2G";
1223
- } else if (
1224
- "2g" === connType ||
1225
- "3g" === connType ||
1226
- "4g" === connType ||
1227
- "5g" === connType
1228
- ) {
1305
+ }
1306
+ else if ("2g" === connType || "3g" === connType || "4g" === connType || "5g" === connType) {
1229
1307
  connType = connType.toUpperCase();
1230
- } else {
1308
+ }
1309
+ else {
1231
1310
  connType = connType.charAt(0).toUpperCase() + connType.slice(1);
1232
1311
  }
1233
1312
  }
@@ -1274,7 +1353,8 @@
1274
1353
  if (lastChildInViewport) {
1275
1354
  // See if this last child has any children in the viewport.
1276
1355
  return lastViewportElement(lastChildInViewport);
1277
- } else {
1356
+ }
1357
+ else {
1278
1358
  // If NONE of the children are in the viewport, return the parent.
1279
1359
  // This assumes that the parent is in the viewport because it was passed in.
1280
1360
  return parent;
@@ -1286,14 +1366,12 @@
1286
1366
  var vw = document.documentElement.clientWidth;
1287
1367
  // Return true if the top-left corner is in the viewport and it has width & height.
1288
1368
  var lt = findPos(e);
1289
- return (
1290
- lt[0] >= 0 &&
1369
+ return (lt[0] >= 0 &&
1291
1370
  lt[1] >= 0 &&
1292
1371
  lt[0] < vw &&
1293
1372
  lt[1] < vh &&
1294
1373
  e.offsetWidth > 0 &&
1295
- e.offsetHeight > 0
1296
- );
1374
+ e.offsetHeight > 0);
1297
1375
  }
1298
1376
  // Return an array containing the top & left coordinates of the element.
1299
1377
  // from http://www.quirksmode.org/js/findpos.html
@@ -1309,15 +1387,21 @@
1309
1387
  }
1310
1388
  // Mark the load time of the current page. Intended to be used in SPAs where it is not desirable to
1311
1389
  // send the beacon as soon as the page has finished loading.
1312
- function _markLoadTime() {
1313
- _mark(gEndMark);
1390
+ function _markLoadTime(time) {
1391
+ logger.logEvent(LogEvent.MarkLoadTimeCalled, [time]);
1392
+ if (time) {
1393
+ _mark(END_MARK, { startTime: time });
1394
+ }
1395
+ else {
1396
+ _mark(END_MARK);
1397
+ }
1314
1398
  }
1315
1399
  function createMaxMeasureTimeout() {
1316
1400
  clearMaxMeasureTimeout();
1317
1401
  gMaxMeasureTimeout = window.setTimeout(function () {
1318
1402
  gFlags = addFlag(gFlags, Flags.BeaconSentAfterTimeout);
1319
1403
  _sendLux();
1320
- }, userConfig.maxMeasureTime - _now());
1404
+ }, globalConfig.maxMeasureTime - _now());
1321
1405
  }
1322
1406
  function clearMaxMeasureTimeout() {
1323
1407
  if (gMaxMeasureTimeout) {
@@ -1328,8 +1412,7 @@
1328
1412
  function _sendLux() {
1329
1413
  clearMaxMeasureTimeout();
1330
1414
  var customerid = getCustomerId();
1331
- if (
1332
- !customerid ||
1415
+ if (!customerid ||
1333
1416
  !gSyncId ||
1334
1417
  !_sample() || // OUTSIDE the sampled range
1335
1418
  gbLuxSent // LUX data already sent
@@ -1337,8 +1420,8 @@
1337
1420
  return;
1338
1421
  }
1339
1422
  logger.logEvent(LogEvent.DataCollectionStart);
1340
- var startMark = _getMark(gStartMark);
1341
- var endMark = _getMark(gEndMark);
1423
+ var startMark = _getMark(START_MARK);
1424
+ var endMark = _getMark(END_MARK);
1342
1425
  if (!startMark || (endMark && endMark.startTime < startMark.startTime)) {
1343
1426
  // Record the synthetic loadEventStart time for this page, unless it was already recorded
1344
1427
  // with LUX.markLoadTime()
@@ -1361,8 +1444,7 @@
1361
1444
  }
1362
1445
  // We want ALL beacons to have ALL the data used for query filters (geo, pagelabel, browser, & customerdata).
1363
1446
  // So we create a base URL that has all the necessary information:
1364
- var baseUrl =
1365
- userConfig.beaconUrl +
1447
+ var baseUrl = globalConfig.beaconUrl +
1366
1448
  "?v=" +
1367
1449
  SCRIPT_VERSION +
1368
1450
  "&id=" +
@@ -1413,11 +1495,9 @@
1413
1495
  nErrors +
1414
1496
  "nt" +
1415
1497
  navigationType() + // reload
1416
- (navigator.deviceMemory
1417
- ? "dm" + Math.round(navigator.deviceMemory)
1418
- : "") + // device memory (GB)
1498
+ (navigator.deviceMemory ? "dm" + Math.round(navigator.deviceMemory) : "") + // device memory (GB)
1419
1499
  (sIx ? "&IX=" + sIx : "") +
1420
- (gFirstInputDelay ? "&FID=" + gFirstInputDelay : "") +
1500
+ (typeof gFirstInputDelay !== "undefined" ? "&FID=" + gFirstInputDelay : "") +
1421
1501
  (sCPU ? "&CPU=" + sCPU : "") +
1422
1502
  (gFlags ? "&fl=" + gFlags : "") +
1423
1503
  (sET ? "&ET=" + sET : "") + // element timing
@@ -1433,7 +1513,8 @@
1433
1513
  if (curLen + sUT.length <= gMaxQuerystring) {
1434
1514
  // Add all User Timing
1435
1515
  querystring += "&UT=" + sUT;
1436
- } else {
1516
+ }
1517
+ else {
1437
1518
  // Only add a substring of User Timing
1438
1519
  var avail_1 = gMaxQuerystring - curLen; // how much room is left in the querystring
1439
1520
  var iComma = sUT.lastIndexOf(",", avail_1); // as many UT tuples as possible
@@ -1457,7 +1538,8 @@
1457
1538
  // We can fit ALL the remaining UT params.
1458
1539
  sUT_cur = sUT_remainder;
1459
1540
  sUT_remainder = "";
1460
- } else {
1541
+ }
1542
+ else {
1461
1543
  // We have to take a subset of the remaining UT params.
1462
1544
  var iComma = sUT_remainder.lastIndexOf(",", avail); // as many UT tuples as possible
1463
1545
  if (-1 === iComma) {
@@ -1470,7 +1552,8 @@
1470
1552
  // Take the whole tuple even tho it is too big.
1471
1553
  sUT_cur = sUT_remainder;
1472
1554
  sUT_remainder = "";
1473
- } else {
1555
+ }
1556
+ else {
1474
1557
  sUT_cur = sUT_remainder.substring(0, iComma);
1475
1558
  sUT_remainder = sUT_remainder.substring(iComma + 1);
1476
1559
  }
@@ -1483,8 +1566,7 @@
1483
1566
  // Beacon back the IX data separately (need to sync with LUX beacon on the backend).
1484
1567
  function _sendIx() {
1485
1568
  var customerid = getCustomerId();
1486
- if (
1487
- !customerid ||
1569
+ if (!customerid ||
1488
1570
  !gSyncId ||
1489
1571
  !_sample() || // OUTSIDE the sampled range
1490
1572
  gbIxSent || // IX data already sent
@@ -1495,8 +1577,7 @@
1495
1577
  var sIx = ixValues(); // Interaction Metrics
1496
1578
  if (sIx) {
1497
1579
  var sCustomerData = customerDataValues(); // customer data
1498
- var querystring =
1499
- "?v=" +
1580
+ var querystring = "?v=" +
1500
1581
  SCRIPT_VERSION +
1501
1582
  "&id=" +
1502
1583
  customerid +
@@ -1514,7 +1595,7 @@
1514
1595
  encodeURIComponent(document.location.hostname) +
1515
1596
  "&PN=" +
1516
1597
  encodeURIComponent(document.location.pathname);
1517
- var beaconUrl = userConfig.beaconUrl + querystring;
1598
+ var beaconUrl = globalConfig.beaconUrl + querystring;
1518
1599
  logger.logEvent(LogEvent.InteractionBeaconSent, [beaconUrl]);
1519
1600
  _sendBeacon(beaconUrl);
1520
1601
  gbIxSent = 1;
@@ -1524,8 +1605,7 @@
1524
1605
  // (i.e., customer data after window.onload).
1525
1606
  function _sendCustomerData() {
1526
1607
  var customerid = getCustomerId();
1527
- if (
1528
- !customerid ||
1608
+ if (!customerid ||
1529
1609
  !gSyncId ||
1530
1610
  !_sample() || // OUTSIDE the sampled range
1531
1611
  !gbLuxSent // LUX has NOT been sent yet, so wait to include it there
@@ -1534,8 +1614,7 @@
1534
1614
  }
1535
1615
  var sCustomerData = customerDataValues(); // customer data
1536
1616
  if (sCustomerData) {
1537
- var querystring =
1538
- "?v=" +
1617
+ var querystring = "?v=" +
1539
1618
  SCRIPT_VERSION +
1540
1619
  "&id=" +
1541
1620
  customerid +
@@ -1551,7 +1630,7 @@
1551
1630
  encodeURIComponent(document.location.hostname) +
1552
1631
  "&PN=" +
1553
1632
  encodeURIComponent(document.location.pathname);
1554
- var beaconUrl = userConfig.beaconUrl + querystring;
1633
+ var beaconUrl = globalConfig.beaconUrl + querystring;
1555
1634
  logger.logEvent(LogEvent.CustomDataBeaconSent, [beaconUrl]);
1556
1635
  _sendBeacon(beaconUrl);
1557
1636
  }
@@ -1566,48 +1645,6 @@
1566
1645
  // If the event(s) happen before LUX finishes, then the IX metric(s) is(are) sent with LUX.
1567
1646
  // Most of the time, however, IX happens *after* LUX, so we send a separate IX beacon but
1568
1647
  // only beacon back the first interaction that happens.
1569
- /**
1570
- * Get the interaction attribution name for an element
1571
- *
1572
- * @param {HTMLElement} el
1573
- * @returns string
1574
- */
1575
- function interactionAttributionForElement(el) {
1576
- // Default to using the element's own ID if it has one
1577
- if (el.id) {
1578
- return el.id;
1579
- }
1580
- // The next preference is to find an ancestor with the "data-sctrack" attribute
1581
- var ancestor = el;
1582
- // We also store the first ancestor ID that we find, so we can use it as
1583
- // a fallback later.
1584
- var ancestorId;
1585
- while (ancestor.parentNode && ancestor.parentNode.tagName) {
1586
- ancestor = ancestor.parentNode;
1587
- if (ancestor.hasAttribute("data-sctrack")) {
1588
- return ancestor.getAttribute("data-sctrack");
1589
- }
1590
- if (ancestor.id && !ancestorId) {
1591
- ancestorId = ancestor.id;
1592
- }
1593
- }
1594
- // The next preference is to use the text content of a button or link
1595
- var isSubmitInput = el.tagName === "INPUT" && el.type === "submit";
1596
- var isButton = el.tagName === "BUTTON";
1597
- var isLink = el.tagName === "A";
1598
- if (isSubmitInput && el.value) {
1599
- return el.value;
1600
- }
1601
- if ((isButton || isLink) && el.innerText) {
1602
- return el.innerText;
1603
- }
1604
- // The next preference is to use the first ancestor ID
1605
- if (ancestorId) {
1606
- return ancestorId;
1607
- }
1608
- // No suitable attribute was found
1609
- return "";
1610
- }
1611
1648
  function _scrollHandler() {
1612
1649
  // Leave handlers IN PLACE so we can track which ID is clicked/keyed.
1613
1650
  // _removeIxHandlers();
@@ -1639,7 +1676,8 @@
1639
1676
  if (e && e.target) {
1640
1677
  target = e.target;
1641
1678
  }
1642
- } catch (e) {
1679
+ }
1680
+ catch (e) {
1643
1681
  logger.logEvent(LogEvent.EventTargetAccessError);
1644
1682
  target = null;
1645
1683
  }
@@ -1660,24 +1698,22 @@
1660
1698
  // Wrapper to support older browsers (<= IE8)
1661
1699
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1662
1700
  function addListener(type, callback, useCapture) {
1663
- if (useCapture === void 0) {
1664
- useCapture = false;
1665
- }
1701
+ if (useCapture === void 0) { useCapture = false; }
1666
1702
  if (window.addEventListener) {
1667
1703
  window.addEventListener(type, callback, useCapture);
1668
- } else if (window.attachEvent) {
1704
+ }
1705
+ else if (window.attachEvent && __ENABLE_POLYFILLS) {
1669
1706
  window.attachEvent("on" + type, callback);
1670
1707
  }
1671
1708
  }
1672
1709
  // Wrapper to support older browsers (<= IE8)
1673
1710
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1674
1711
  function removeListener(type, callback, useCapture) {
1675
- if (useCapture === void 0) {
1676
- useCapture = false;
1677
- }
1712
+ if (useCapture === void 0) { useCapture = false; }
1678
1713
  if (window.removeEventListener) {
1679
1714
  window.removeEventListener(type, callback, useCapture);
1680
- } else if (window.detachEvent) {
1715
+ }
1716
+ else if (window.detachEvent && __ENABLE_POLYFILLS) {
1681
1717
  window.detachEvent("on" + type, callback);
1682
1718
  }
1683
1719
  }
@@ -1693,44 +1729,37 @@
1693
1729
  // pagehide, we use unload and beforeunload.
1694
1730
  if ("onpagehide" in self) {
1695
1731
  addListener("pagehide", onunload, true);
1696
- } else {
1732
+ }
1733
+ else {
1697
1734
  addListener("unload", onunload, true);
1698
1735
  addListener("beforeunload", onunload, true);
1699
1736
  }
1700
- addListener(
1701
- "visibilitychange",
1702
- function () {
1703
- if (document.visibilityState === "hidden") {
1704
- onunload();
1705
- }
1706
- },
1707
- true
1708
- );
1737
+ addListener("visibilitychange", function () {
1738
+ if (document.visibilityState === "hidden") {
1739
+ onunload();
1740
+ }
1741
+ }, true);
1709
1742
  }
1710
1743
  function _addIxHandlers() {
1711
1744
  addListener("scroll", _scrollHandler);
1712
- addListener("keypress", _keyHandler);
1745
+ addListener("keydown", _keyHandler);
1713
1746
  addListener("mousedown", _clickHandler);
1714
1747
  }
1715
1748
  function _removeIxHandlers() {
1716
1749
  removeListener("scroll", _scrollHandler);
1717
- removeListener("keypress", _keyHandler);
1750
+ removeListener("keydown", _keyHandler);
1718
1751
  removeListener("mousedown", _clickHandler);
1719
1752
  }
1720
1753
  // This is a big number (epoch ms . random) that is used to matchup a LUX beacon with a separate IX beacon
1721
1754
  // (because they get sent at different times). Each "page view" (including SPA) should have a
1722
1755
  // unique gSyncId.
1723
1756
  function createSyncId(inSampleBucket) {
1724
- if (inSampleBucket === void 0) {
1725
- inSampleBucket = false;
1726
- }
1757
+ if (inSampleBucket === void 0) { inSampleBucket = false; }
1727
1758
  if (inSampleBucket) {
1728
1759
  // "00" matches all sample rates
1729
1760
  return "".concat(Number(new Date()), "00000");
1730
1761
  }
1731
- return ""
1732
- .concat(Number(new Date()))
1733
- .concat(_padLeft(String(Math.round(100000 * Math.random())), "00000"));
1762
+ return "".concat(Number(new Date())).concat(_padLeft(String(Math.round(100000 * Math.random())), "00000"));
1734
1763
  }
1735
1764
  // Unique ID (also known as Session ID)
1736
1765
  // We use this to track all the page views in a single user session.
@@ -1740,7 +1769,8 @@
1740
1769
  var uid = _getCookie("lux_uid");
1741
1770
  if (!uid || uid.length < 11) {
1742
1771
  uid = newValue;
1743
- } else {
1772
+ }
1773
+ else {
1744
1774
  // Prevent sessions lasting more than 24 hours.
1745
1775
  // The first 10 characters of uid is the epoch time when the session started.
1746
1776
  var uidStart = parseInt(uid.substring(0, 10));
@@ -1764,24 +1794,48 @@
1764
1794
  }
1765
1795
  // Return the current page label.
1766
1796
  function _getPageLabel() {
1767
- if (typeof LUX.label !== "undefined") {
1797
+ if (LUX.label) {
1768
1798
  gFlags = addFlag(gFlags, Flags.PageLabelFromLabelProp);
1769
1799
  return LUX.label;
1770
- } else if (typeof LUX.jspagelabel !== "undefined") {
1771
- var evaluateJsPageLabel = Function(
1772
- '"use strict"; return '.concat(LUX.jspagelabel)
1773
- );
1800
+ }
1801
+ else if (typeof LUX.pagegroups !== "undefined") {
1802
+ var pagegroups = LUX.pagegroups;
1803
+ var url_1 = "".concat(document.location.hostname).concat(document.location.pathname);
1804
+ var label_1 = "";
1805
+ var _loop_1 = function (pagegroup) {
1806
+ var rules = pagegroups[pagegroup];
1807
+ if (Array.isArray(rules)) {
1808
+ rules.every(function (rule) {
1809
+ if (Matching.isMatching(rule, url_1)) {
1810
+ label_1 = pagegroup;
1811
+ return false; // stop when first match is found
1812
+ }
1813
+ return true;
1814
+ });
1815
+ }
1816
+ // exits loop when first match is found
1817
+ if (label_1.length) {
1818
+ gFlags = addFlag(gFlags, Flags.PageLabelFromPagegroup);
1819
+ return { value: label_1 };
1820
+ }
1821
+ };
1822
+ for (var pagegroup in pagegroups) {
1823
+ var state_1 = _loop_1(pagegroup);
1824
+ if (typeof state_1 === "object")
1825
+ return state_1.value;
1826
+ }
1827
+ }
1828
+ if (typeof LUX.jspagelabel !== "undefined") {
1829
+ var evaluateJsPageLabel = Function("\"use strict\"; return ".concat(LUX.jspagelabel));
1774
1830
  try {
1775
1831
  var label = evaluateJsPageLabel();
1776
1832
  if (label) {
1777
1833
  gFlags = addFlag(gFlags, Flags.PageLabelFromGlobalVariable);
1778
1834
  return label;
1779
1835
  }
1780
- } catch (e) {
1781
- logger.logEvent(LogEvent.PageLabelEvaluationError, [
1782
- LUX.jspagelabel,
1783
- e,
1784
- ]);
1836
+ }
1837
+ catch (e) {
1838
+ logger.logEvent(LogEvent.PageLabelEvaluationError, [LUX.jspagelabel, e]);
1785
1839
  }
1786
1840
  }
1787
1841
  // default to document.title
@@ -1799,7 +1853,8 @@
1799
1853
  return unescape(aTuple[1]);
1800
1854
  }
1801
1855
  }
1802
- } catch (e) {
1856
+ }
1857
+ catch (e) {
1803
1858
  logger.logEvent(LogEvent.CookieReadError);
1804
1859
  }
1805
1860
  return undefined;
@@ -1812,7 +1867,8 @@
1812
1867
  escape(value) +
1813
1868
  (seconds ? "; max-age=" + seconds : "") +
1814
1869
  "; path=/; SameSite=Lax";
1815
- } catch (e) {
1870
+ }
1871
+ catch (e) {
1816
1872
  logger.logEvent(LogEvent.CookieSetError);
1817
1873
  }
1818
1874
  }
@@ -1822,25 +1878,27 @@
1822
1878
  }
1823
1879
  // Set "LUX.auto=false" to disable send results automatically and
1824
1880
  // instead you must call LUX.send() explicitly.
1825
- if (userConfig.auto) {
1881
+ if (globalConfig.auto) {
1826
1882
  var sendBeaconAfterMinimumMeasureTime_1 = function () {
1827
1883
  var elapsedTime = _now();
1828
- var timeRemaining = userConfig.minMeasureTime - elapsedTime;
1884
+ var timeRemaining = globalConfig.minMeasureTime - elapsedTime;
1829
1885
  if (timeRemaining <= 0) {
1830
1886
  logger.logEvent(LogEvent.OnloadHandlerTriggered, [
1831
1887
  elapsedTime,
1832
- userConfig.minMeasureTime,
1888
+ globalConfig.minMeasureTime,
1833
1889
  ]);
1834
1890
  if (document.readyState === "complete") {
1835
1891
  // If onload has already passed, send the beacon now.
1836
1892
  _sendLux();
1837
- } else {
1893
+ }
1894
+ else {
1838
1895
  // Ow, send the beacon slightly after window.onload.
1839
1896
  addListener("load", function () {
1840
1897
  setTimeout(_sendLux, 200);
1841
1898
  });
1842
1899
  }
1843
- } else {
1900
+ }
1901
+ else {
1844
1902
  // Try again after the minimum measurement time has elapsed
1845
1903
  setTimeout(sendBeaconAfterMinimumMeasureTime_1, timeRemaining);
1846
1904
  }
@@ -1848,51 +1906,48 @@
1848
1906
  sendBeaconAfterMinimumMeasureTime_1();
1849
1907
  }
1850
1908
  // Add the unload handlers for auto mode, or when LUX.measureUntil is "pagehidden"
1851
- if (userConfig.sendBeaconOnPageHidden) {
1909
+ if (globalConfig.sendBeaconOnPageHidden) {
1852
1910
  _addUnloadHandlers();
1853
1911
  }
1854
1912
  // Regardless of userConfig.auto, we need to register the IX handlers immediately.
1855
1913
  _addIxHandlers();
1856
1914
  // Set the maximum measurement timer
1857
1915
  createMaxMeasureTimeout();
1858
- // This is the public API.
1859
- var _LUX = userConfig;
1916
+ /**
1917
+ * LUX functions and properties must be attached to the existing global object to ensure that
1918
+ * changes made to the global object are reflected in the "internal" LUX object, and vice versa.
1919
+ */
1920
+ var globalLux = globalConfig;
1860
1921
  // Functions
1861
- _LUX.mark = _mark;
1862
- _LUX.measure = _measure;
1863
- _LUX.init = _init;
1864
- _LUX.markLoadTime = _markLoadTime;
1865
- _LUX.send = function () {
1922
+ globalLux.mark = _mark;
1923
+ globalLux.measure = _measure;
1924
+ globalLux.init = _init;
1925
+ globalLux.markLoadTime = _markLoadTime;
1926
+ globalLux.send = function () {
1866
1927
  logger.logEvent(LogEvent.SendCalled);
1867
1928
  _sendLux();
1868
1929
  };
1869
- _LUX.addData = _addData;
1870
- _LUX.getSessionId = _getUniqueId; // so customers can do their own sampling
1871
- _LUX.getDebug = function () {
1872
- return logger.getEvents();
1873
- };
1874
- _LUX.forceSample = function () {
1930
+ globalLux.addData = _addData;
1931
+ globalLux.getSessionId = _getUniqueId; // so customers can do their own sampling
1932
+ globalLux.getDebug = function () { return logger.getEvents(); };
1933
+ globalLux.forceSample = function () {
1875
1934
  logger.logEvent(LogEvent.ForceSampleCalled);
1876
1935
  setUniqueId(createSyncId(true));
1877
1936
  };
1878
- _LUX.doUpdate = function () {
1937
+ globalLux.doUpdate = function () {
1879
1938
  // Deprecated, intentionally empty.
1880
1939
  };
1881
- _LUX.cmd = _runCommand;
1940
+ globalLux.cmd = _runCommand;
1882
1941
  // Public properties
1883
- _LUX.version = SCRIPT_VERSION;
1884
- // "Private" properties
1885
- _LUX.ae = []; // array for error handler (ignored)
1886
- _LUX.al = []; // array for Long Tasks (ignored)
1942
+ globalLux.version = SCRIPT_VERSION;
1887
1943
  /**
1888
- * Run a command from the command queue
1889
- */
1944
+ * Run a command from the command queue
1945
+ */
1890
1946
  function _runCommand(_a) {
1891
- var fn = _a[0],
1892
- args = _a.slice(1);
1893
- if (typeof _LUX[fn] === "function") {
1894
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1895
- _LUX[fn].apply(_LUX, args);
1947
+ var fn = _a[0], args = _a.slice(1);
1948
+ if (typeof globalLux[fn] === "function") {
1949
+ // eslint-disable-next-line @typescript-eslint/ban-types
1950
+ globalLux[fn].apply(globalLux, args);
1896
1951
  }
1897
1952
  }
1898
1953
  // Process the command queue
@@ -1904,10 +1959,10 @@
1904
1959
  window.LUX_ae.forEach(errorHandler);
1905
1960
  }
1906
1961
  logger.logEvent(LogEvent.EvaluationEnd);
1907
- return _LUX;
1962
+ return globalLux;
1908
1963
  })();
1909
1964
  window.LUX = LUX;
1910
- LUX_t_end = now();
1965
+ scriptEndTime = now();
1911
1966
 
1912
1967
  // ---------------------------------------------------------------------------
1913
1968
  // More settings