govuk_publishing_components 31.1.0 → 31.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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