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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +1 -1
- data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +91 -14
- data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-schemas.js +3 -1
- data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +536 -481
- data/app/assets/stylesheets/component_guide/application.scss +10 -12
- data/app/views/govuk_publishing_components/component_guide/component_doc/_component.html.erb +1 -1
- data/app/views/govuk_publishing_components/components/_share_links.html.erb +2 -2
- data/lib/govuk_publishing_components/version.rb +1 -1
- metadata +3 -3
@@ -20,13 +20,13 @@
|
|
20
20
|
*/
|
21
21
|
|
22
22
|
(function () {
|
23
|
-
|
23
|
+
'use strict';
|
24
24
|
|
25
25
|
function now() {
|
26
26
|
return Date.now ? Date.now() : +new Date();
|
27
27
|
}
|
28
28
|
|
29
|
-
var
|
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
|
-
|
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 = "
|
293
|
+
var SCRIPT_VERSION = "302";
|
163
294
|
var logger = new Logger();
|
164
|
-
var
|
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 (!
|
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
|
-
|
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
|
-
|
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
|
-
}
|
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
|
-
|
255
|
-
var
|
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, [
|
275
|
-
}
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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, [
|
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
|
-
}
|
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"
|
462
|
+
if (evt.type === "pointerdown") {
|
364
463
|
// special case
|
365
464
|
onPointerDown(delay);
|
366
|
-
}
|
465
|
+
}
|
466
|
+
else {
|
367
467
|
recordDelay(delay);
|
368
468
|
}
|
369
469
|
}
|
@@ -374,118 +474,150 @@
|
|
374
474
|
});
|
375
475
|
////////////////////// FID END
|
376
476
|
/**
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
385
|
-
var startMark = _getMark(
|
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
|
488
|
+
return sinceNavigationStart - startMark.startTime;
|
389
489
|
}
|
390
490
|
// For "regular" page views, we can use performance.now() if it's available...
|
391
|
-
|
392
|
-
return performance.now();
|
393
|
-
}
|
394
|
-
// ... or we can use navigationStart as a reference point
|
395
|
-
return msSinceNavigationStart;
|
491
|
+
return sinceNavigationStart;
|
396
492
|
}
|
397
|
-
//
|
398
|
-
//
|
399
|
-
function _mark(
|
400
|
-
|
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
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
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
|
-
//
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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
|
-
|
442
|
-
|
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
|
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(
|
529
|
-
|
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
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
//
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
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
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
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(
|
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(
|
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
|
-
}
|
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
|
-
|
666
|
-
|
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
|
-
}
|
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
|
-
}
|
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 =
|
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
|
-
|
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
|
-
(
|
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" ===
|
802
|
-
|
803
|
-
|
804
|
-
|
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) <
|
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(
|
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(
|
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
|
-
}
|
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 =
|
982
|
-
var startMark = _getMark(
|
983
|
-
var endMark = _getMark(
|
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
|
-
}
|
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
|
-
|
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
|
-
}
|
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
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
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 (
|
1108
|
-
|
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
|
-
|
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
|
-
|
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
|
-
}
|
1224
|
-
|
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
|
-
}
|
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
|
-
}
|
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
|
-
|
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
|
-
},
|
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(
|
1341
|
-
var endMark = _getMark(
|
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
|
-
}
|
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
|
-
}
|
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
|
-
}
|
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 =
|
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 =
|
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
|
-
}
|
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
|
-
}
|
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
|
-
}
|
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
|
-
}
|
1732
|
+
}
|
1733
|
+
else {
|
1697
1734
|
addListener("unload", onunload, true);
|
1698
1735
|
addListener("beforeunload", onunload, true);
|
1699
1736
|
}
|
1700
|
-
addListener(
|
1701
|
-
"
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
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("
|
1745
|
+
addListener("keydown", _keyHandler);
|
1713
1746
|
addListener("mousedown", _clickHandler);
|
1714
1747
|
}
|
1715
1748
|
function _removeIxHandlers() {
|
1716
1749
|
removeListener("scroll", _scrollHandler);
|
1717
|
-
removeListener("
|
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
|
-
}
|
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 (
|
1797
|
+
if (LUX.label) {
|
1768
1798
|
gFlags = addFlag(gFlags, Flags.PageLabelFromLabelProp);
|
1769
1799
|
return LUX.label;
|
1770
|
-
}
|
1771
|
-
|
1772
|
-
|
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
|
-
}
|
1781
|
-
|
1782
|
-
|
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
|
-
}
|
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
|
-
}
|
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 (
|
1881
|
+
if (globalConfig.auto) {
|
1826
1882
|
var sendBeaconAfterMinimumMeasureTime_1 = function () {
|
1827
1883
|
var elapsedTime = _now();
|
1828
|
-
var timeRemaining =
|
1884
|
+
var timeRemaining = globalConfig.minMeasureTime - elapsedTime;
|
1829
1885
|
if (timeRemaining <= 0) {
|
1830
1886
|
logger.logEvent(LogEvent.OnloadHandlerTriggered, [
|
1831
1887
|
elapsedTime,
|
1832
|
-
|
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
|
-
}
|
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
|
-
}
|
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 (
|
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
|
-
|
1859
|
-
|
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
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
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
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
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
|
-
|
1937
|
+
globalLux.doUpdate = function () {
|
1879
1938
|
// Deprecated, intentionally empty.
|
1880
1939
|
};
|
1881
|
-
|
1940
|
+
globalLux.cmd = _runCommand;
|
1882
1941
|
// Public properties
|
1883
|
-
|
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
|
-
|
1889
|
-
|
1944
|
+
* Run a command from the command queue
|
1945
|
+
*/
|
1890
1946
|
function _runCommand(_a) {
|
1891
|
-
var fn = _a[0],
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
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
|
1962
|
+
return globalLux;
|
1908
1963
|
})();
|
1909
1964
|
window.LUX = LUX;
|
1910
|
-
|
1965
|
+
scriptEndTime = now();
|
1911
1966
|
|
1912
1967
|
// ---------------------------------------------------------------------------
|
1913
1968
|
// More settings
|