govuk_publishing_components 35.6.0 → 35.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/audit-filter.js +45 -0
  3. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +206 -114
  4. data/app/assets/stylesheets/component_guide/application.scss +0 -25
  5. data/app/assets/stylesheets/govuk_publishing_components/components/_document-list.scss +5 -9
  6. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-for-public.scss +4 -0
  7. data/app/views/govuk_publishing_components/audit/_component_contents.html.erb +37 -49
  8. data/app/views/govuk_publishing_components/audit/show.html.erb +2 -1
  9. data/app/views/govuk_publishing_components/components/_attachment.html.erb +38 -18
  10. data/app/views/govuk_publishing_components/components/_character_count.html.erb +4 -2
  11. data/app/views/govuk_publishing_components/components/_document_list.html.erb +8 -7
  12. data/app/views/govuk_publishing_components/components/_feedback.html.erb +1 -0
  13. data/app/views/govuk_publishing_components/components/_layout_for_public.html.erb +9 -2
  14. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +1 -0
  15. data/app/views/govuk_publishing_components/components/_metadata.html.erb +20 -14
  16. data/app/views/govuk_publishing_components/components/_radio.html.erb +1 -0
  17. data/app/views/govuk_publishing_components/components/_search.html.erb +1 -0
  18. data/app/views/govuk_publishing_components/components/_select.html.erb +1 -0
  19. data/app/views/govuk_publishing_components/components/_step_by_step_nav_related.html.erb +2 -2
  20. data/app/views/govuk_publishing_components/components/attachment/_thumbnail_document.html.erb +1 -1
  21. data/app/views/govuk_publishing_components/components/attachment/_thumbnail_generic.html.erb +1 -1
  22. data/app/views/govuk_publishing_components/components/attachment/_thumbnail_html.html.erb +3 -0
  23. data/app/views/govuk_publishing_components/components/attachment/_thumbnail_spreadsheet.html.erb +1 -1
  24. data/app/views/govuk_publishing_components/components/docs/attachment.yml +21 -0
  25. data/app/views/govuk_publishing_components/components/docs/document_list.yml +17 -0
  26. data/app/views/govuk_publishing_components/components/docs/layout_for_public.yml +6 -0
  27. data/app/views/layouts/govuk_publishing_components/application.html.erb +23 -20
  28. data/config/locales/ar.yml +1 -0
  29. data/config/locales/az.yml +1 -0
  30. data/config/locales/be.yml +1 -0
  31. data/config/locales/bg.yml +1 -0
  32. data/config/locales/bn.yml +1 -0
  33. data/config/locales/cs.yml +1 -0
  34. data/config/locales/cy.yml +1 -0
  35. data/config/locales/da.yml +1 -0
  36. data/config/locales/de.yml +1 -0
  37. data/config/locales/dr.yml +1 -0
  38. data/config/locales/el.yml +1 -0
  39. data/config/locales/en.yml +1 -0
  40. data/config/locales/es-419.yml +1 -0
  41. data/config/locales/es.yml +1 -0
  42. data/config/locales/et.yml +1 -0
  43. data/config/locales/fa.yml +1 -0
  44. data/config/locales/fi.yml +1 -0
  45. data/config/locales/fr.yml +1 -0
  46. data/config/locales/gd.yml +1 -0
  47. data/config/locales/gu.yml +1 -0
  48. data/config/locales/he.yml +1 -0
  49. data/config/locales/hi.yml +1 -0
  50. data/config/locales/hr.yml +1 -0
  51. data/config/locales/hu.yml +1 -0
  52. data/config/locales/hy.yml +1 -0
  53. data/config/locales/id.yml +1 -0
  54. data/config/locales/is.yml +1 -0
  55. data/config/locales/it.yml +1 -0
  56. data/config/locales/ja.yml +1 -0
  57. data/config/locales/ka.yml +1 -0
  58. data/config/locales/kk.yml +1 -0
  59. data/config/locales/ko.yml +1 -0
  60. data/config/locales/lt.yml +1 -0
  61. data/config/locales/lv.yml +1 -0
  62. data/config/locales/ms.yml +1 -0
  63. data/config/locales/mt.yml +1 -0
  64. data/config/locales/nl.yml +1 -0
  65. data/config/locales/no.yml +1 -0
  66. data/config/locales/pa-pk.yml +1 -0
  67. data/config/locales/pa.yml +1 -0
  68. data/config/locales/pl.yml +1 -0
  69. data/config/locales/ps.yml +1 -0
  70. data/config/locales/pt.yml +1 -0
  71. data/config/locales/ro.yml +1 -0
  72. data/config/locales/ru.yml +1 -0
  73. data/config/locales/si.yml +1 -0
  74. data/config/locales/sk.yml +1 -0
  75. data/config/locales/sl.yml +1 -0
  76. data/config/locales/so.yml +1 -0
  77. data/config/locales/sq.yml +1 -0
  78. data/config/locales/sr.yml +1 -0
  79. data/config/locales/sv.yml +1 -0
  80. data/config/locales/sw.yml +1 -0
  81. data/config/locales/ta.yml +1 -0
  82. data/config/locales/th.yml +1 -0
  83. data/config/locales/tk.yml +1 -0
  84. data/config/locales/tr.yml +1 -0
  85. data/config/locales/uk.yml +1 -0
  86. data/config/locales/ur.yml +1 -0
  87. data/config/locales/uz.yml +1 -0
  88. data/config/locales/vi.yml +1 -0
  89. data/config/locales/zh-hk.yml +1 -0
  90. data/config/locales/zh-tw.yml +1 -0
  91. data/config/locales/zh.yml +1 -0
  92. data/lib/govuk_publishing_components/app_helpers/asset_helper.rb +5 -1
  93. data/lib/govuk_publishing_components/presenters/attachment_helper.rb +15 -3
  94. data/lib/govuk_publishing_components/presenters/public_layout_helper.rb +5 -0
  95. data/lib/govuk_publishing_components/version.rb +1 -1
  96. data/node_modules/axe-core/README.md +4 -0
  97. data/node_modules/axe-core/axe.js +25 -19
  98. data/node_modules/axe-core/axe.min.js +2 -2
  99. data/node_modules/axe-core/package.json +1 -1
  100. data/node_modules/axe-core/sri-history.json +4 -0
  101. metadata +4 -3
@@ -42,7 +42,6 @@
42
42
  var autoMode = getProperty(obj, "auto", true);
43
43
  return {
44
44
  auto: autoMode,
45
- autoWhenHidden: getProperty(obj, "autoWhenHidden", false),
46
45
  beaconUrl: getProperty(obj, "beaconUrl", "https://lux.speedcurve.com/lux/"),
47
46
  conversions: getProperty(obj, "conversions", undefined),
48
47
  customerid: getProperty(obj, "customerid", undefined),
@@ -54,9 +53,12 @@
54
53
  maxErrors: getProperty(obj, "maxErrors", 5),
55
54
  maxMeasureTime: getProperty(obj, "maxMeasureTime", 60000),
56
55
  minMeasureTime: getProperty(obj, "minMeasureTime", 0),
56
+ newBeaconOnPageShow: getProperty(obj, "newBeaconOnPageShow", false),
57
57
  samplerate: getProperty(obj, "samplerate", 100),
58
58
  sendBeaconOnPageHidden: getProperty(obj, "sendBeaconOnPageHidden", autoMode),
59
+ serverTiming: getProperty(obj, "serverTiming", undefined),
59
60
  trackErrors: getProperty(obj, "trackErrors", true),
61
+ trackHiddenPages: getProperty(obj, "trackHiddenPages", false),
60
62
  pagegroups: getProperty(obj, "pagegroups", undefined),
61
63
  };
62
64
  }
@@ -102,9 +104,9 @@
102
104
  function valuesToString(values) {
103
105
  var strings = [];
104
106
  for (var key in values) {
105
- // Convert all values to strings
107
+ // Convert all values to strings
106
108
  var value = "" + values[key];
107
- // Strip out reserved characters (, and | are used as delimiters)
109
+ // Strip out reserved characters (, and | are used as delimiters)
108
110
  key = key.replace(/,/g, "").replace(/\|/g, "");
109
111
  value = value.replace(/,/g, "").replace(/\|/g, "");
110
112
  strings.push(key + "|" + value);
@@ -115,6 +117,13 @@
115
117
  function floor(x) {
116
118
  return Math.floor(x);
117
119
  }
120
+ var max = Math.max;
121
+ /**
122
+ * Clamp a number so that it is never less than 0
123
+ */
124
+ function clamp(x) {
125
+ return max(0, x);
126
+ }
118
127
 
119
128
  function now() {
120
129
  return Date.now ? Date.now() : +new Date();
@@ -147,10 +156,13 @@
147
156
  function getNavigationEntry() {
148
157
  var navEntries = getEntriesByType("navigation");
149
158
  if (navEntries.length) {
150
- var entry_1 = navEntries[0];
151
- entry_1.navigationStart = 0;
152
- if (typeof entry_1.activationStart === "undefined") {
153
- entry_1.activationStart = 0;
159
+ var nativeEntry = navEntries[0];
160
+ var entry_1 = {
161
+ navigationStart: 0,
162
+ activationStart: 0,
163
+ };
164
+ for (var key in nativeEntry) {
165
+ entry_1[key] = nativeEntry[key];
154
166
  }
155
167
  return entry_1;
156
168
  }
@@ -164,7 +176,7 @@
164
176
  if (__ENABLE_POLYFILLS) {
165
177
  for (var key in timing) {
166
178
  if (typeof timing[key] === "number" && key !== "navigationStart") {
167
- entry[key] = Math.max(0, timing[key] - timing.navigationStart);
179
+ entry[key] = floor(timing[key] - timing.navigationStart);
168
180
  }
169
181
  }
170
182
  }
@@ -189,26 +201,39 @@
189
201
  if (document.visibilityState) {
190
202
  return document.visibilityState === "visible";
191
203
  }
192
- // For browsers that don't support document.visibilityState, we assume the page is visible.
204
+ // For browsers that don't support document.visibilityState, we assume the page is visible.
193
205
  return true;
194
206
  }
195
207
  function onVisible(cb) {
196
- if (isVisible()) {
197
- cb();
208
+ afterPrerender(function () {
209
+ if (isVisible()) {
210
+ cb();
211
+ }
212
+ else {
213
+ var onVisibleCallback_1 = function () {
214
+ if (isVisible()) {
215
+ cb();
216
+ removeEventListener("visibilitychange", onVisibleCallback_1);
217
+ }
218
+ };
219
+ addEventListener("visibilitychange", onVisibleCallback_1, true);
220
+ }
221
+ });
222
+ }
223
+ function afterPrerender(cb) {
224
+ if (document.prerendering) {
225
+ document.addEventListener("prerenderingchange", cb, true);
198
226
  }
199
227
  else {
200
- var onVisibleCallback_1 = function () {
201
- if (isVisible()) {
202
- cb();
203
- removeEventListener("visibilitychange", onVisibleCallback_1);
204
- }
205
- };
206
- addEventListener("visibilitychange", onVisibleCallback_1, true);
228
+ cb();
207
229
  }
208
230
  }
209
231
  function wasPrerendered() {
210
232
  return document.prerendering || getNavigationEntry().activationStart > 0;
211
233
  }
234
+ function wasRedirected() {
235
+ return getNavigationEntry().redirectCount > 0 || timing.redirectEnd > 0;
236
+ }
212
237
 
213
238
  var Flags = {
214
239
  InitCalled: 1 << 0,
@@ -222,6 +247,7 @@
222
247
  PageLabelFromGlobalVariable: 1 << 8,
223
248
  PageLabelFromPagegroup: 1 << 9,
224
249
  PageWasPrerendered: 1 << 10,
250
+ PageWasBfCacheRestored: 1 << 11,
225
251
  };
226
252
  function addFlag(flags, flag) {
227
253
  return flags | flag;
@@ -235,8 +261,8 @@
235
261
  }
236
262
 
237
263
  /**
238
- * Get the interaction attribution name for an element
239
- */
264
+ * Get the interaction attribution name for an element
265
+ */
240
266
  function interactionAttributionForElement(el) {
241
267
  // Our first preference is to use the data-sctrack attribute from anywhere in the tree
242
268
  var trackId = getClosestScTrackAttribute(el);
@@ -291,6 +317,7 @@
291
317
  UnloadHandlerTriggered: 10,
292
318
  OnloadHandlerTriggered: 11,
293
319
  MarkLoadTimeCalled: 12,
320
+ SendCancelledPageHidden: 13,
294
321
  // Data collection events
295
322
  SessionIsSampled: 21,
296
323
  SessionIsNotSampled: 22,
@@ -315,39 +342,45 @@
315
342
  PaintTimingNotSupported: 72,
316
343
  };
317
344
  var Logger = /** @class */ (function () {
318
- function Logger() {
319
- this.events = [];
320
- }
321
- Logger.prototype.logEvent = function (event, args) {
322
- if (args === void 0) { args = []; }
323
- this.events.push([now(), event, args]);
324
- };
325
- Logger.prototype.getEvents = function () {
326
- return this.events;
327
- };
328
- return Logger;
345
+ function Logger() {
346
+ this.events = [];
347
+ }
348
+ Logger.prototype.logEvent = function (event, args) {
349
+ if (args === void 0) { args = []; }
350
+ this.events.push([now(), event, args]);
351
+ };
352
+ Logger.prototype.getEvents = function () {
353
+ return this.events;
354
+ };
355
+ return Logger;
329
356
  }());
330
357
  var sessionValue = 0;
331
358
  var sessionEntries = [];
359
+ var maximumSessionValue = 0;
332
360
  function addEntry$2(entry) {
333
361
  if (!entry.hadRecentInput) {
334
362
  var firstEntry = sessionEntries[0];
335
363
  var latestEntry = sessionEntries[sessionEntries.length - 1];
336
364
  if (sessionEntries.length &&
337
365
  (entry.startTime - latestEntry.startTime >= 1000 ||
338
- entry.startTime - firstEntry.startTime >= 5000)) {
339
- reset$1();
366
+ entry.startTime - firstEntry.startTime >= 5000)) {
367
+ sessionValue = entry.value;
368
+ sessionEntries = [entry];
340
369
  }
341
- sessionValue += entry.value;
342
- sessionEntries.push(entry);
370
+ else {
371
+ sessionValue += entry.value;
372
+ sessionEntries.push(entry);
373
+ }
374
+ maximumSessionValue = max(maximumSessionValue, sessionValue);
343
375
  }
344
376
  }
345
377
  function reset$1() {
346
378
  sessionValue = 0;
347
379
  sessionEntries = [];
380
+ maximumSessionValue = 0;
348
381
  }
349
382
  function getCLS() {
350
- return sessionValue;
383
+ return maximumSessionValue;
351
384
  }
352
385
 
353
386
  /**
@@ -372,7 +405,7 @@
372
405
  var duration = entry.duration, startTime = entry.startTime, interactionId = entry.interactionId;
373
406
  var existingEntry = slowestEntriesMap[interactionId];
374
407
  if (existingEntry) {
375
- existingEntry.duration = Math.max(duration, existingEntry.duration);
408
+ existingEntry.duration = max(duration, existingEntry.duration);
376
409
  }
377
410
  else {
378
411
  interactionCountEstimate++;
@@ -405,44 +438,6 @@
405
438
  return interactionCountEstimate;
406
439
  }
407
440
 
408
- /**
409
- * Get the number of milliseconds between navigationStart and the given PerformanceNavigationTiming key
410
- */
411
- function getNavTimingValue(key) {
412
- var navEntry = getNavigationEntry();
413
- var relativeTo = key === "activationStart" ? 0 : navEntry.activationStart;
414
- if (typeof navEntry[key] === "number") {
415
- return Math.max(0, navEntry[key] - relativeTo);
416
- }
417
- return undefined;
418
- }
419
-
420
- /******************************************************************************
421
- Copyright (c) Microsoft Corporation.
422
-
423
- Permission to use, copy, modify, and/or distribute this software for any
424
- purpose with or without fee is hereby granted.
425
-
426
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
427
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
428
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
429
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
430
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
431
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
432
- PERFORMANCE OF THIS SOFTWARE.
433
- ***************************************************************************** */
434
-
435
- var __assign = function() {
436
- __assign = Object.assign || function __assign(t) {
437
- for (var s, i = 1, n = arguments.length; i < n; i++) {
438
- s = arguments[i];
439
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
440
- }
441
- return t;
442
- };
443
- return __assign.apply(this, arguments);
444
- };
445
-
446
441
  var ALL_ENTRIES = [];
447
442
  function observe(type, callback, options) {
448
443
  if (typeof PerformanceObserver === "function" &&
@@ -450,10 +445,10 @@
450
445
  var po = new PerformanceObserver(function (list) {
451
446
  list.getEntries().forEach(function (entry) { return callback(entry); });
452
447
  });
453
- po.observe(__assign({ type: type, buffered: true }, options));
454
- return po;
455
- }
456
- return undefined;
448
+ po.observe(Object.assign({ type: type, buffered: true }, { options: options }));
449
+ return po;
450
+ }
451
+ return undefined;
457
452
  }
458
453
  function getEntries(type) {
459
454
  return ALL_ENTRIES.filter(function (entry) { return entry.entryType === type; });
@@ -465,6 +460,39 @@
465
460
  ALL_ENTRIES.splice(0);
466
461
  }
467
462
 
463
+ /**
464
+ * A server timing metric that has its value set to the duration field
465
+ */
466
+ var TYPE_DURATION = "r";
467
+ /**
468
+ * When a description metric has no value, we consider it to be a boolean and set it to this value.
469
+ */
470
+ var BOOLEAN_TRUE_VALUE = "true";
471
+ function getKeyValuePairs(config, serverTiming) {
472
+ var pairs = {};
473
+ serverTiming.forEach(function (stEntry) {
474
+ var name = stEntry.name;
475
+ var description = stEntry.description;
476
+ if (name in config) {
477
+ var spec = config[name];
478
+ var multiplier = spec[1];
479
+ if (spec[0] === TYPE_DURATION) {
480
+ pairs[name] = stEntry.duration * (multiplier || 1);
481
+ }
482
+ else if (description && multiplier) {
483
+ var numericValue = parseFloat(description);
484
+ if (!isNaN(numericValue)) {
485
+ pairs[name] = numericValue * multiplier;
486
+ }
487
+ }
488
+ else {
489
+ pairs[name] = description || BOOLEAN_TRUE_VALUE;
490
+ }
491
+ }
492
+ });
493
+ return pairs;
494
+ }
495
+
468
496
  function getMatchesFromPatternMap(patternMap, hostname, pathname, firstOnly) {
469
497
  var matches = [];
470
498
  for (var key in patternMap) {
@@ -496,7 +524,7 @@
496
524
  return regex.test(hostname + pathname);
497
525
  }
498
526
  function createRegExpFromPattern(pattern) {
499
- return new RegExp("^" + escapeStringForRegExp(pattern).replaceAll("*", ".*") + "$", "i");
527
+ return new RegExp("^" + escapeStringForRegExp(pattern).replace(/\*/g, ".*") + "$", "i");
500
528
  }
501
529
  function escapeStringForRegExp(str) {
502
530
  // Note: we don't escape * because it's our own special symbol!
@@ -514,7 +542,7 @@
514
542
  // -------------------------------------------------------------------------
515
543
  /// End
516
544
  // -------------------------------------------------------------------------
517
- var SCRIPT_VERSION = "308";
545
+ var SCRIPT_VERSION = "309";
518
546
  var logger = new Logger();
519
547
  var globalConfig = fromObject(LUX);
520
548
  logger.logEvent(LogEvent.EvaluationStart, [SCRIPT_VERSION]);
@@ -585,7 +613,7 @@
585
613
  observe("first-input", function (entry) {
586
614
  var fid = entry.processingStart - entry.startTime;
587
615
  if (!gFirstInputDelay || gFirstInputDelay < fid) {
588
- gFirstInputDelay = fid;
616
+ gFirstInputDelay = floor(fid);
589
617
  }
590
618
  // Allow first-input events to be considered for INP
591
619
  addEntry$1(entry);
@@ -612,7 +640,13 @@
612
640
  var gUid = refreshUniqueId(gSyncId); // cookie for this session ("Unique ID")
613
641
  var gCustomerDataTimeout; // setTimeout timer for sending a Customer Data beacon after onload
614
642
  var gMaxMeasureTimeout; // setTimeout timer for sending the beacon after a maximum measurement time
615
- var navEntry = getNavigationEntry();
643
+ var pageRestoreTime; // ms since navigationStart representing when the page was restored from the bfcache
644
+ /**
645
+ * To measure the way a user experienced a metric, we measure metrics relative to the time the user
646
+ * started viewing the page. On prerendered pages, this is activationStart. On bfcache restores, this
647
+ * is the page restore time. On all other pages this value will be zero.
648
+ */
649
+ var getZeroTime = function () { return pageRestoreTime || getNavigationEntry().activationStart; };
616
650
  if (_sample()) {
617
651
  logger.logEvent(LogEvent.SessionIsSampled, [globalConfig.samplerate]);
618
652
  }
@@ -713,7 +747,7 @@
713
747
  var startMark = _getMark(START_MARK);
714
748
  // For SPA page views, we use our internal mark as a reference point
715
749
  if (startMark && !absolute) {
716
- return sinceNavigationStart - startMark.startTime;
750
+ return floor(sinceNavigationStart - startMark.startTime);
717
751
  }
718
752
  // For "regular" page views, we can use performance.now() if it's available...
719
753
  return sinceNavigationStart;
@@ -796,6 +830,7 @@
796
830
  }
797
831
  // ...Otherwise provide a polyfill
798
832
  if (__ENABLE_POLYFILLS) {
833
+ var navEntry = getNavigationEntry();
799
834
  var startTime = typeof startMarkName === "number" ? startMarkName : 0;
800
835
  var endTime = typeof endMarkName === "number" ? endMarkName : _now();
801
836
  var throwError = function (missingMark) {
@@ -906,7 +941,7 @@
906
941
  hUT[name] = { startTime: startTime };
907
942
  }
908
943
  else {
909
- hUT[name].startTime = Math.max(startTime, hUT[name].startTime);
944
+ hUT[name].startTime = max(startTime, hUT[name].startTime);
910
945
  }
911
946
  });
912
947
  // measures
@@ -939,7 +974,7 @@
939
974
  function elementTimingValues() {
940
975
  var aET = [];
941
976
  var startMark = _getMark(START_MARK);
942
- var tZero = startMark ? startMark.startTime : 0;
977
+ var tZero = startMark ? startMark.startTime : getZeroTime();
943
978
  getEntries("element").forEach(function (entry) {
944
979
  if (entry.identifier && entry.startTime) {
945
980
  logger.logEvent(LogEvent.PerformanceEntryProcessed, [entry]);
@@ -1164,11 +1199,11 @@
1164
1199
  var nThis = ("" + gUid).substr(-2); // number for THIS page - from 00 to 99
1165
1200
  return parseInt(nThis) < globalConfig.samplerate;
1166
1201
  }
1167
- // _init()
1168
- // Use this function in Single Page Apps to reset things.
1169
- // This function should ONLY be called within a SPA!
1170
- // Otherwise, you might clear marks & measures that were set by a shim.
1171
- function _init() {
1202
+ /**
1203
+ * Re-initialize lux.js to start a new "page". This is typically called within a SPA at the
1204
+ * beginning of a page transition, but is also called internally when the BF cache is restored.
1205
+ */
1206
+ function _init(startTime) {
1172
1207
  // Some customers (incorrectly) call LUX.init on the very first page load of a SPA. This would
1173
1208
  // cause some first-page-only data (like paint metrics) to be lost. To prevent this, we silently
1174
1209
  // bail from this function when we detect an unnecessary LUX.init call.
@@ -1176,6 +1211,14 @@
1176
1211
  if (!endMark) {
1177
1212
  return;
1178
1213
  }
1214
+ // Mark the "navigationStart" for this SPA page. A start time can be passed through, for example
1215
+ // to set a page's start time as an event timestamp.
1216
+ if (startTime) {
1217
+ _mark(START_MARK, { startTime: startTime });
1218
+ }
1219
+ else {
1220
+ _mark(START_MARK);
1221
+ }
1179
1222
  logger.logEvent(LogEvent.InitCalled);
1180
1223
  // Clear all interactions from the previous "page".
1181
1224
  _clearIx();
@@ -1197,8 +1240,6 @@
1197
1240
  // Clear flags then set the flag that init was called (ie, this is a SPA).
1198
1241
  gFlags = 0;
1199
1242
  gFlags = addFlag(gFlags, Flags.InitCalled);
1200
- // Mark the "navigationStart" for this SPA page.
1201
- _mark(START_MARK);
1202
1243
  // Reset the maximum measure timeout
1203
1244
  createMaxMeasureTimeout();
1204
1245
  }
@@ -1305,8 +1346,9 @@
1305
1346
  var ns = timing.navigationStart;
1306
1347
  var startMark = _getMark(START_MARK);
1307
1348
  var endMark = _getMark(END_MARK);
1308
- if (startMark && endMark) {
1349
+ if (startMark && endMark && !pageRestoreTime) {
1309
1350
  // This is a SPA page view, so send the SPA marks & measures instead of Nav Timing.
1351
+ // Note: pageRestoreTime indicates this was a bfcache restore, which we don't want to treat as a SPA.
1310
1352
  var start = floor(startMark.startTime); // the start mark is "zero"
1311
1353
  ns += start; // "navigationStart" for a SPA is the real navigationStart plus the start mark
1312
1354
  var end = floor(endMark.startTime) - start; // delta from start mark
@@ -1322,21 +1364,33 @@
1322
1364
  }
1323
1365
  else if (performance.timing) {
1324
1366
  // Return the real Nav Timing metrics because this is the "main" page view (not a SPA)
1367
+ var navEntry_1 = getNavigationEntry();
1325
1368
  var startRender = getStartRender();
1326
1369
  var fcp = getFcp();
1327
1370
  var lcp = getLcp();
1328
1371
  var prefixNTValue = function (key, prefix) {
1329
- var value = getNavTimingValue(key);
1330
- if (typeof value === "undefined") {
1331
- return "";
1372
+ // activationStart is always absolute. Other values are relative to activationStart.
1373
+ var zero = key === "activationStart" ? 0 : getZeroTime();
1374
+ if (typeof navEntry_1[key] === "number") {
1375
+ var value = clamp(floor(navEntry_1[key] - zero));
1376
+ return prefix + value;
1332
1377
  }
1333
- return prefix + value;
1378
+ return "";
1334
1379
  };
1380
+ var loadEventStartStr = prefixNTValue("loadEventStart", "ls");
1381
+ var loadEventEndStr = prefixNTValue("loadEventEnd", "le");
1382
+ if (pageRestoreTime && startMark && endMark) {
1383
+ // For bfcache restores, we set the load time to the time it took for the page to be restored.
1384
+ var loadTime = floor(endMark.startTime - startMark.startTime);
1385
+ loadEventStartStr = "ls" + loadTime;
1386
+ loadEventEndStr = "le" + loadTime;
1387
+ }
1388
+ var redirect = wasRedirected();
1335
1389
  s = [
1336
1390
  ns,
1337
1391
  prefixNTValue("activationStart", "as"),
1338
- prefixNTValue("redirectStart", "rs"),
1339
- prefixNTValue("redirectEnd", "re"),
1392
+ redirect ? prefixNTValue("redirectStart", "rs") : "",
1393
+ redirect ? prefixNTValue("redirectEnd", "re") : "",
1340
1394
  prefixNTValue("fetchStart", "fs"),
1341
1395
  prefixNTValue("domainLookupStart", "ds"),
1342
1396
  prefixNTValue("domainLookupEnd", "de"),
@@ -1350,11 +1404,11 @@
1350
1404
  prefixNTValue("domContentLoadedEventStart", "os"),
1351
1405
  prefixNTValue("domContentLoadedEventEnd", "oe"),
1352
1406
  prefixNTValue("domComplete", "oc"),
1353
- prefixNTValue("loadEventStart", "ls"),
1354
- prefixNTValue("loadEventEnd", "le"),
1355
- typeof startRender !== "undefined" ? "sr" + startRender : "",
1356
- typeof fcp !== "undefined" ? "fc" + fcp : "",
1357
- typeof lcp !== "undefined" ? "lc" + lcp : "",
1407
+ loadEventStartStr,
1408
+ loadEventEndStr,
1409
+ typeof startRender !== "undefined" ? "sr" + clamp(startRender) : "",
1410
+ typeof fcp !== "undefined" ? "fc" + clamp(fcp) : "",
1411
+ typeof lcp !== "undefined" ? "lc" + clamp(lcp) : "",
1358
1412
  ].join("");
1359
1413
  }
1360
1414
  else if (endMark) {
@@ -1378,7 +1432,7 @@
1378
1432
  for (var i = 0; i < paintEntries.length; i++) {
1379
1433
  var entry = paintEntries[i];
1380
1434
  if (entry.name === "first-contentful-paint") {
1381
- return floor(entry.startTime);
1435
+ return floor(entry.startTime - getZeroTime());
1382
1436
  }
1383
1437
  }
1384
1438
  return undefined;
@@ -1389,7 +1443,7 @@
1389
1443
  if (lcpEntries.length) {
1390
1444
  var lastEntry = lcpEntries[lcpEntries.length - 1];
1391
1445
  logger.logEvent(LogEvent.PerformanceEntryProcessed, [lastEntry]);
1392
- return floor(lastEntry.startTime);
1446
+ return floor(lastEntry.startTime - getZeroTime());
1393
1447
  }
1394
1448
  return undefined;
1395
1449
  }
@@ -1402,7 +1456,7 @@
1402
1456
  if (paintEntries.length) {
1403
1457
  // If the Paint Timing API is supported, use the value of the first paint event
1404
1458
  var paintValues = paintEntries.map(function (entry) { return entry.startTime; });
1405
- return floor(Math.min.apply(null, paintValues));
1459
+ return floor(Math.min.apply(null, paintValues) - getZeroTime());
1406
1460
  }
1407
1461
  }
1408
1462
  if (performance.timing && timing.msFirstPaint && __ENABLE_POLYFILLS) {
@@ -1474,17 +1528,17 @@
1474
1528
  }
1475
1529
  function docHeight(doc) {
1476
1530
  var body = doc.body, docelem = doc.documentElement;
1477
- var height = Math.max(body ? body.scrollHeight : 0, body ? body.offsetHeight : 0, docelem ? docelem.clientHeight : 0, docelem ? docelem.scrollHeight : 0, docelem ? docelem.offsetHeight : 0);
1531
+ var height = max(body ? body.scrollHeight : 0, body ? body.offsetHeight : 0, docelem ? docelem.clientHeight : 0, docelem ? docelem.scrollHeight : 0, docelem ? docelem.offsetHeight : 0);
1478
1532
  return height;
1479
1533
  }
1480
1534
  function docWidth(doc) {
1481
1535
  var body = doc.body, docelem = doc.documentElement;
1482
- var width = Math.max(body ? body.scrollWidth : 0, body ? body.offsetWidth : 0, docelem ? docelem.clientWidth : 0, docelem ? docelem.scrollWidth : 0, docelem ? docelem.offsetWidth : 0);
1536
+ var width = max(body ? body.scrollWidth : 0, body ? body.offsetWidth : 0, docelem ? docelem.clientWidth : 0, docelem ? docelem.scrollWidth : 0, docelem ? docelem.offsetWidth : 0);
1483
1537
  return width;
1484
1538
  }
1485
1539
  // Return the main HTML document transfer size (in bytes).
1486
1540
  function docSize() {
1487
- return navEntry.encodedBodySize || 0;
1541
+ return getNavigationEntry().encodedBodySize || 0;
1488
1542
  }
1489
1543
  // Return the connection type based on Network Information API.
1490
1544
  // Note this API is in flux.
@@ -1624,6 +1678,10 @@
1624
1678
  // Beacon back the LUX data.
1625
1679
  function _sendLux() {
1626
1680
  var _a;
1681
+ if (!isVisible() && !globalConfig.trackHiddenPages) {
1682
+ logger.logEvent(LogEvent.SendCancelledPageHidden);
1683
+ return;
1684
+ }
1627
1685
  clearMaxMeasureTimeout();
1628
1686
  var customerid = getCustomerId();
1629
1687
  if (!customerid ||
@@ -1663,6 +1721,15 @@
1663
1721
  if (wasPrerendered()) {
1664
1722
  gFlags = addFlag(gFlags, Flags.PageWasPrerendered);
1665
1723
  }
1724
+ if (globalConfig.serverTiming) {
1725
+ var navEntry = getNavigationEntry();
1726
+ if (navEntry.serverTiming) {
1727
+ var stPairs = getKeyValuePairs(globalConfig.serverTiming, navEntry.serverTiming);
1728
+ for (var name_2 in stPairs) {
1729
+ _addData(name_2, stPairs[name_2]);
1730
+ }
1731
+ }
1732
+ }
1666
1733
  if (LUX.conversions) {
1667
1734
  getMatchesFromPatternMap(LUX.conversions, location.hostname, location.pathname).forEach(function (conversion) {
1668
1735
  LUX.addData(conversion, BOOLEAN_TRUE);
@@ -1757,7 +1824,7 @@
1757
1824
  var sIx = ixValues(); // Interaction Metrics
1758
1825
  var INP = getINP();
1759
1826
  if (sIx) {
1760
- var beaconUrl = _getBeaconUrl(getUpdatedCustomData()) +
1827
+ var beaconUrl = _getBeaconUrl(getUpdatedCustomData()) +
1761
1828
  "&IX=" +
1762
1829
  sIx +
1763
1830
  (typeof gFirstInputDelay !== "undefined" ? "&FID=" + gFirstInputDelay : "") +
@@ -2031,8 +2098,8 @@
2031
2098
  setTimeout(sendBeaconAfterMinimumMeasureTime_1, timeRemaining);
2032
2099
  }
2033
2100
  };
2034
- if (globalConfig.autoWhenHidden) {
2035
- // The autoWhenHidden config forces the beacon to be sent even when the page is not visible.
2101
+ if (globalConfig.trackHiddenPages) {
2102
+ // The trackHiddenPages config forces the beacon to be sent even when the page is not visible.
2036
2103
  sendBeaconAfterMinimumMeasureTime_1();
2037
2104
  }
2038
2105
  else {
@@ -2040,6 +2107,31 @@
2040
2107
  onVisible(sendBeaconAfterMinimumMeasureTime_1);
2041
2108
  }
2042
2109
  }
2110
+ // When newBeaconOnPageShow = true, we initiate a new page view whenever a page is restored from
2111
+ // bfcache. Since we have no "onload" event to hook into after a bfcache restore, we rely on the
2112
+ // unload and maxMeasureTime handlers to send the beacon.
2113
+ if (globalConfig.newBeaconOnPageShow) {
2114
+ window.addEventListener("pageshow", function (event) {
2115
+ if (event.persisted) {
2116
+ // Record the timestamp of the bfcache restore
2117
+ pageRestoreTime = event.timeStamp;
2118
+ // In Chromium, document.visibilityState is still "hidden" when pageshow fires after a bfcache
2119
+ // restore. Wrapping this in a setTimeout ensures the browser has enough time to update the
2120
+ // visibility.
2121
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=1133363
2122
+ setTimeout(function () {
2123
+ if (gbLuxSent) {
2124
+ // If the beacon was already sent for this page, we start a new page view and mark the
2125
+ // load time as the time it took to restore the page.
2126
+ _init(pageRestoreTime);
2127
+ _markLoadTime();
2128
+ }
2129
+ // Flag the current page as a bfcache restore
2130
+ gFlags = addFlag(gFlags, Flags.PageWasBfCacheRestored);
2131
+ }, 0);
2132
+ }
2133
+ });
2134
+ }
2043
2135
  // Add the unload handlers when sendBeaconOnPageHidden is enabled
2044
2136
  if (globalConfig.sendBeaconOnPageHidden) {
2045
2137
  _addUnloadHandlers();
@@ -6,31 +6,6 @@ $govuk-new-link-styles: true;
6
6
  @import "govuk_publishing_components/govuk_frontend_support";
7
7
  @import "govuk_publishing_components/component_support";
8
8
 
9
- // Import the same stylesheets used in static
10
- // https://github.com/alphagov/static/blob/198a598682df40ce4a2c3c286c06244297c18cf0/app/assets/stylesheets/application.scss
11
- // Although the component guide does not use static, we still need to import the same stylesheets used in static
12
- // to avoid any rendering issues
13
- // By following the same approach as frontend rendering applications,
14
- // we ensure that stylesheets are loaded in the expected order and components render correctly
15
- @import "govuk_publishing_components/components/breadcrumbs";
16
- @import "govuk_publishing_components/components/button";
17
- @import "govuk_publishing_components/components/error-message";
18
- @import "govuk_publishing_components/components/heading";
19
- @import "govuk_publishing_components/components/hint";
20
- @import "govuk_publishing_components/components/input";
21
- @import "govuk_publishing_components/components/label";
22
- @import "govuk_publishing_components/components/search";
23
- @import "govuk_publishing_components/components/skip-link";
24
- @import "govuk_publishing_components/components/textarea";
25
- @import "govuk_publishing_components/components/title";
26
-
27
- @import "govuk_publishing_components/components/cookie-banner";
28
- @import "govuk_publishing_components/components/feedback";
29
- @import "govuk_publishing_components/components/layout-footer";
30
- @import "govuk_publishing_components/components/layout-for-public";
31
- @import "govuk_publishing_components/components/layout-header";
32
- @import "govuk_publishing_components/components/layout-super-navigation-header";
33
-
34
9
  // Include required helpers
35
10
  @import "../../stylesheets/govuk_publishing_components/components/helpers/markdown-typography";
36
11