lighthouse 9.5.0-dev.20230130 → 9.5.0-dev.20230201

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.
Files changed (92) hide show
  1. package/core/audits/byte-efficiency/offscreen-images.js +1 -1
  2. package/core/audits/critical-request-chains.d.ts +6 -6
  3. package/core/audits/critical-request-chains.js +15 -15
  4. package/core/audits/dobetterweb/doctype.js +4 -4
  5. package/core/audits/dobetterweb/{password-inputs-can-be-pasted-into.d.ts → paste-preventing-inputs.d.ts} +3 -3
  6. package/core/audits/dobetterweb/{password-inputs-can-be-pasted-into.js → paste-preventing-inputs.js} +17 -15
  7. package/core/audits/font-display.js +1 -1
  8. package/core/audits/network-requests.js +4 -4
  9. package/core/audits/redirects.js +2 -1
  10. package/core/audits/uses-rel-preconnect.js +5 -4
  11. package/core/config/default-config.js +2 -4
  12. package/core/gather/driver/network-monitor.js +2 -2
  13. package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +4 -4
  14. package/core/gather/gatherers/inputs.js +6 -0
  15. package/core/legacy/config/legacy-default-config.js +0 -1
  16. package/core/lib/bf-cache-strings.js +1 -1
  17. package/core/lib/dependency-graph/network-node.js +2 -2
  18. package/core/lib/dependency-graph/simulator/network-analyzer.js +8 -6
  19. package/core/lib/lighthouse-compatibility.d.ts +8 -0
  20. package/core/lib/lighthouse-compatibility.js +139 -0
  21. package/core/lib/navigation-error.js +3 -1
  22. package/core/lib/network-recorder.js +1 -1
  23. package/core/lib/network-request.d.ts +7 -7
  24. package/core/lib/network-request.js +26 -27
  25. package/core/runner.d.ts +1 -6
  26. package/core/runner.js +9 -20
  27. package/dist/report/bundle.esm.js +145 -129
  28. package/dist/report/flow.js +19 -13
  29. package/dist/report/standalone.js +10 -4
  30. package/package.json +1 -1
  31. package/report/assets/styles.css +1 -1
  32. package/report/renderer/components.js +1 -1
  33. package/report/renderer/i18n-formatter.js +1 -1
  34. package/report/renderer/report-ui-features.js +1 -1
  35. package/report/renderer/report-utils.d.ts +4 -4
  36. package/report/renderer/report-utils.js +13 -130
  37. package/report/test/renderer/report-ui-features-test.js +20 -31
  38. package/report/test/renderer/report-utils-test.js +0 -170
  39. package/report/tsconfig.json +1 -0
  40. package/shared/localization/locales/ar-XB.json +0 -9
  41. package/shared/localization/locales/ar.json +0 -9
  42. package/shared/localization/locales/bg.json +0 -9
  43. package/shared/localization/locales/ca.json +0 -9
  44. package/shared/localization/locales/cs.json +0 -9
  45. package/shared/localization/locales/da.json +0 -9
  46. package/shared/localization/locales/de.json +0 -9
  47. package/shared/localization/locales/el.json +0 -9
  48. package/shared/localization/locales/en-GB.json +0 -9
  49. package/shared/localization/locales/en-US.json +9 -9
  50. package/shared/localization/locales/en-XA.json +0 -9
  51. package/shared/localization/locales/en-XL.json +9 -9
  52. package/shared/localization/locales/es-419.json +0 -9
  53. package/shared/localization/locales/es.json +0 -9
  54. package/shared/localization/locales/fi.json +0 -9
  55. package/shared/localization/locales/fil.json +0 -9
  56. package/shared/localization/locales/fr.json +0 -9
  57. package/shared/localization/locales/he.json +0 -9
  58. package/shared/localization/locales/hi.json +0 -9
  59. package/shared/localization/locales/hr.json +0 -9
  60. package/shared/localization/locales/hu.json +0 -9
  61. package/shared/localization/locales/id.json +0 -9
  62. package/shared/localization/locales/it.json +0 -9
  63. package/shared/localization/locales/ja.json +0 -9
  64. package/shared/localization/locales/ko.json +0 -9
  65. package/shared/localization/locales/lt.json +0 -9
  66. package/shared/localization/locales/lv.json +0 -9
  67. package/shared/localization/locales/nl.json +0 -9
  68. package/shared/localization/locales/no.json +0 -9
  69. package/shared/localization/locales/pl.json +0 -9
  70. package/shared/localization/locales/pt-PT.json +0 -9
  71. package/shared/localization/locales/pt.json +0 -9
  72. package/shared/localization/locales/ro.json +0 -9
  73. package/shared/localization/locales/ru.json +0 -9
  74. package/shared/localization/locales/sk.json +0 -9
  75. package/shared/localization/locales/sl.json +0 -9
  76. package/shared/localization/locales/sr-Latn.json +0 -9
  77. package/shared/localization/locales/sr.json +0 -9
  78. package/shared/localization/locales/sv.json +0 -9
  79. package/shared/localization/locales/ta.json +0 -9
  80. package/shared/localization/locales/te.json +0 -9
  81. package/shared/localization/locales/th.json +0 -9
  82. package/shared/localization/locales/tr.json +0 -9
  83. package/shared/localization/locales/uk.json +0 -9
  84. package/shared/localization/locales/vi.json +0 -9
  85. package/shared/localization/locales/zh-HK.json +0 -9
  86. package/shared/localization/locales/zh-TW.json +0 -9
  87. package/shared/localization/locales/zh.json +0 -9
  88. package/tsconfig.json +1 -1
  89. package/types/artifacts.d.ts +2 -5
  90. package/types/lhr/lhr.d.ts +3 -11
  91. package/core/gather/gatherers/dobetterweb/password-inputs-with-prevented-paste.d.ts +0 -10
  92. package/core/gather/gatherers/dobetterweb/password-inputs-with-prevented-paste.js +0 -49
@@ -102,7 +102,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
102
102
  return {
103
103
  node: ByteEfficiencyAudit.makeNodeItem(image.node),
104
104
  url,
105
- requestStartTime: networkRecord.startTime,
105
+ requestStartTime: networkRecord.networkRequestTime,
106
106
  totalBytes,
107
107
  wastedBytes,
108
108
  wastedPercent: 100 * wastedRatio,
@@ -1,23 +1,23 @@
1
1
  export default CriticalRequestChains;
2
2
  declare class CriticalRequestChains extends Audit {
3
- /** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Audit.Details.SimpleCriticalRequestNode[string]}} CrcNodeInfo */
3
+ /** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
4
4
  /**
5
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
5
+ * @param {LH.Artifacts.CriticalRequestNode} tree
6
6
  * @param {function(CrcNodeInfo): void} cb
7
7
  */
8
- static _traverse(tree: LH.Audit.Details.SimpleCriticalRequestNode, cb: (arg0: {
8
+ static _traverse(tree: LH.Artifacts.CriticalRequestNode, cb: (arg0: {
9
9
  depth: number;
10
10
  id: string;
11
11
  chainDuration: number;
12
12
  chainTransferSize: number;
13
- node: LH.Audit.Details.SimpleCriticalRequestNode[string];
13
+ node: LH.Artifacts.CriticalRequestNode[string];
14
14
  }) => void): void;
15
15
  /**
16
16
  * Get stats about the longest initiator chain (as determined by time duration)
17
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
17
+ * @param {LH.Artifacts.CriticalRequestNode} tree
18
18
  * @return {{duration: number, length: number, transferSize: number}}
19
19
  */
20
- static _getLongestChain(tree: LH.Audit.Details.SimpleCriticalRequestNode): {
20
+ static _getLongestChain(tree: LH.Artifacts.CriticalRequestNode): {
21
21
  duration: number;
22
22
  length: number;
23
23
  transferSize: number;
@@ -41,28 +41,28 @@ class CriticalRequestChains extends Audit {
41
41
  };
42
42
  }
43
43
 
44
- /** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Audit.Details.SimpleCriticalRequestNode[string]}} CrcNodeInfo */
44
+ /** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
45
45
 
46
46
  /**
47
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
47
+ * @param {LH.Artifacts.CriticalRequestNode} tree
48
48
  * @param {function(CrcNodeInfo): void} cb
49
49
  */
50
50
  static _traverse(tree, cb) {
51
51
  /**
52
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} node
52
+ * @param {LH.Artifacts.CriticalRequestNode} node
53
53
  * @param {number} depth
54
- * @param {number=} startTime
54
+ * @param {number=} networkRequestTime
55
55
  * @param {number=} transferSize
56
56
  */
57
- function walk(node, depth, startTime, transferSize = 0) {
57
+ function walk(node, depth, networkRequestTime, transferSize = 0) {
58
58
  const children = Object.keys(node);
59
59
  if (children.length === 0) {
60
60
  return;
61
61
  }
62
62
  children.forEach(id => {
63
63
  const child = node[id];
64
- if (!startTime) {
65
- startTime = child.request.startTime;
64
+ if (!networkRequestTime) {
65
+ networkRequestTime = child.request.networkRequestTime;
66
66
  }
67
67
 
68
68
  // Call the callback with the info for this child.
@@ -70,13 +70,13 @@ class CriticalRequestChains extends Audit {
70
70
  depth,
71
71
  id,
72
72
  node: child,
73
- chainDuration: child.request.endTime - startTime,
73
+ chainDuration: child.request.networkEndTime - networkRequestTime,
74
74
  chainTransferSize: transferSize + child.request.transferSize,
75
75
  });
76
76
 
77
77
  // Carry on walking.
78
78
  if (child.children) {
79
- walk(child.children, depth + 1, startTime);
79
+ walk(child.children, depth + 1, networkRequestTime);
80
80
  }
81
81
  }, '');
82
82
  }
@@ -86,7 +86,7 @@ class CriticalRequestChains extends Audit {
86
86
 
87
87
  /**
88
88
  * Get stats about the longest initiator chain (as determined by time duration)
89
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
89
+ * @param {LH.Artifacts.CriticalRequestNode} tree
90
90
  * @return {{duration: number, length: number, transferSize: number}}
91
91
  */
92
92
  static _getLongestChain(tree) {
@@ -96,7 +96,7 @@ class CriticalRequestChains extends Audit {
96
96
  transferSize: 0,
97
97
  };
98
98
  CriticalRequestChains._traverse(tree, opts => {
99
- const duration = opts.chainDuration * 1000;
99
+ const duration = opts.chainDuration;
100
100
  if (duration > longest.duration) {
101
101
  longest.duration = duration;
102
102
  longest.transferSize = opts.chainTransferSize;
@@ -123,9 +123,9 @@ class CriticalRequestChains extends Audit {
123
123
  const request = opts.node.request;
124
124
  const simpleRequest = {
125
125
  url: request.url,
126
- startTime: request.startTime / 1000,
127
- endTime: request.endTime / 1000,
128
- responseReceivedTime: request.responseReceivedTime / 1000,
126
+ startTime: request.networkRequestTime / 1000,
127
+ endTime: request.networkEndTime / 1000,
128
+ responseReceivedTime: request.responseHeadersEndTime / 1000,
129
129
  transferSize: request.transferSize,
130
130
  };
131
131
 
@@ -199,7 +199,7 @@ class CriticalRequestChains extends Audit {
199
199
  walk(initialNavChildren, 0);
200
200
  }
201
201
 
202
- const longestChain = CriticalRequestChains._getLongestChain(flattenedChains);
202
+ const longestChain = CriticalRequestChains._getLongestChain(chains);
203
203
 
204
204
  return {
205
205
  score: Number(chainCount === 0),
@@ -18,10 +18,10 @@ const UIStrings = {
18
18
  '[Learn more about the doctype declaration](https://developer.chrome.com/docs/lighthouse/best-practices/doctype/).',
19
19
  /** Explanatory message stating that the document has no doctype. */
20
20
  explanationNoDoctype: 'Document must contain a doctype',
21
- /** Explanatory message stating that the document has wrong doctype */
22
- explanationWrongDoctype: 'Document contains a doctype that triggers quirks-mode',
23
- /** Explanatory message stating that the document has wrong doctype */
24
- explanationLimitedQuirks: 'Document contains a doctype that triggers limited-quirks-mode',
21
+ /** Explanatory message stating that the document has wrong doctype that triggers `quirks-mode` */
22
+ explanationWrongDoctype: 'Document contains a `doctype` that triggers `quirks-mode`',
23
+ /** Explanatory message stating that the document has wrong doctype that triggers `limited-quirks-mode` */
24
+ explanationLimitedQuirks: 'Document contains a `doctype` that triggers `limited-quirks-mode`',
25
25
  /** Explanatory message stating that the publicId field is not empty. */
26
26
  explanationPublicId: 'Expected publicId to be an empty string',
27
27
  /** Explanatory message stating that the systemId field is not empty. */
@@ -1,5 +1,5 @@
1
- export default PasswordInputsCanBePastedIntoAudit;
2
- declare class PasswordInputsCanBePastedIntoAudit extends Audit {
1
+ export default PastePreventingInputsAudit;
2
+ declare class PastePreventingInputsAudit extends Audit {
3
3
  /**
4
4
  * @param {LH.Artifacts} artifacts
5
5
  * @return {LH.Audit.Product}
@@ -12,4 +12,4 @@ export namespace UIStrings {
12
12
  const description: string;
13
13
  }
14
14
  import { Audit } from "../audit.js";
15
- //# sourceMappingURL=password-inputs-can-be-pasted-into.d.ts.map
15
+ //# sourceMappingURL=paste-preventing-inputs.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright 2016 The Lighthouse Authors. All Rights Reserved.
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
3
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
4
  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
5
  */
@@ -8,28 +8,29 @@ import {Audit} from '../audit.js';
8
8
  import * as i18n from '../../lib/i18n/i18n.js';
9
9
 
10
10
  const UIStrings = {
11
- /** Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page allows pasting of content into password fields. */
12
- title: 'Allows users to paste into password fields',
13
- /** Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page does not allow pasting of content into password fields. */
14
- failureTitle: 'Prevents users to paste into password fields',
15
- /** Description of a Lighthouse audit that tells the user why they should allow pasting of content into password fields. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
16
- description: 'Preventing password pasting undermines good security policy. ' +
17
- '[Learn more about user-friendly password fields](https://developer.chrome.com/docs/lighthouse/best-practices/password-inputs-can-be-pasted-into/).',
11
+ /** Title of a Lighthouse audit that provides detail on the ability to paste into input fields. This descriptive title is shown to users when the page allows pasting of content into input fields. */
12
+ title: 'Allows users to paste into input fields',
13
+ /** Title of a Lighthouse audit that provides detail on the ability to paste into input fields. This descriptive title is shown to users when the page does not allow pasting of content into input fields. */
14
+ failureTitle: 'Prevents users from pasting into input fields',
15
+ /** Description of a Lighthouse audit that tells the user why they should allow pasting of content into input fields. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
16
+ description: 'Preventing input pasting is a UX anti-pattern, ' +
17
+ 'and undermines good security policy. ' +
18
+ '[Learn more about user-friendly input fields](https://developer.chrome.com/docs/lighthouse/best-practices/paste-preventing-inputs/).',
18
19
  };
19
20
 
20
21
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
21
22
 
22
- class PasswordInputsCanBePastedIntoAudit extends Audit {
23
+ class PastePreventingInputsAudit extends Audit {
23
24
  /**
24
25
  * @return {LH.Audit.Meta}
25
26
  */
26
27
  static get meta() {
27
28
  return {
28
- id: 'password-inputs-can-be-pasted-into',
29
+ id: 'paste-preventing-inputs',
29
30
  title: str_(UIStrings.title),
30
31
  failureTitle: str_(UIStrings.failureTitle),
31
32
  description: str_(UIStrings.description),
32
- requiredArtifacts: ['PasswordInputsWithPreventedPaste'],
33
+ requiredArtifacts: ['Inputs'],
33
34
  };
34
35
  }
35
36
 
@@ -38,14 +39,15 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
38
39
  * @return {LH.Audit.Product}
39
40
  */
40
41
  static audit(artifacts) {
41
- const passwordInputsWithPreventedPaste = artifacts.PasswordInputsWithPreventedPaste;
42
+ const inputsWithPreventsPaste = artifacts.Inputs.inputs.filter(input => input.preventsPaste);
42
43
 
43
44
  /** @type {LH.Audit.Details.Table['items']} */
44
45
  const items = [];
45
46
 
46
- passwordInputsWithPreventedPaste.forEach(input => {
47
+ inputsWithPreventsPaste.forEach(input => {
47
48
  items.push({
48
49
  node: Audit.makeNodeItem(input.node),
50
+ type: input.type,
49
51
  });
50
52
  });
51
53
 
@@ -55,11 +57,11 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
55
57
  ];
56
58
 
57
59
  return {
58
- score: Number(passwordInputsWithPreventedPaste.length === 0),
60
+ score: Number(inputsWithPreventsPaste.length === 0),
59
61
  details: Audit.makeTableDetails(headings, items),
60
62
  };
61
63
  }
62
64
  }
63
65
 
64
- export default PasswordInputsCanBePastedIntoAudit;
66
+ export default PastePreventingInputsAudit;
65
67
  export {UIStrings};
@@ -165,7 +165,7 @@ class FontDisplay extends Audit {
165
165
  .map(record => {
166
166
  // In reality the end time should be calculated with paint time included
167
167
  // all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time
168
- const wastedMs = Math.min(record.endTime - record.startTime, 3000);
168
+ const wastedMs = Math.min(record.networkEndTime - record.networkRequestTime, 3000);
169
169
 
170
170
  return {
171
171
  url: record.url,
@@ -65,8 +65,8 @@ class NetworkRequests extends Audit {
65
65
  url: UrlUtils.elideDataURI(record.url),
66
66
  protocol: record.protocol,
67
67
  rendererStartTime: normalizeTime(record.rendererStartTime),
68
- startTime: normalizeTime(record.startTime),
69
- endTime: normalizeTime(record.endTime),
68
+ networkRequestTime: normalizeTime(record.networkRequestTime),
69
+ networkEndTime: normalizeTime(record.networkEndTime),
70
70
  finished: record.finished,
71
71
  transferSize: record.transferSize,
72
72
  resourceSize: record.resourceSize,
@@ -88,8 +88,8 @@ class NetworkRequests extends Audit {
88
88
  const headings = [
89
89
  {key: 'url', valueType: 'url', label: 'URL'},
90
90
  {key: 'protocol', valueType: 'text', label: 'Protocol'},
91
- {key: 'startTime', valueType: 'ms', granularity: 1, label: 'Start Time'},
92
- {key: 'endTime', valueType: 'ms', granularity: 1, label: 'End Time'},
91
+ {key: 'networkRequestTime', valueType: 'ms', granularity: 1, label: 'Network Request Time'},
92
+ {key: 'networkEndTime', valueType: 'ms', granularity: 1, label: 'Network End Time'},
93
93
  {
94
94
  key: 'transferSize',
95
95
  valueType: 'bytes',
@@ -125,7 +125,8 @@ class Redirects extends Audit {
125
125
  }
126
126
 
127
127
  const lanternTimingDeltaMs = redirectedTiming.startTime - initialTiming.startTime;
128
- const observedTimingDeltaMs = redirectedRequest.startTime - initialRequest.startTime;
128
+ const observedTimingDeltaMs = redirectedRequest.networkRequestTime -
129
+ initialRequest.networkRequestTime;
129
130
  const wastedMs = settings.throttlingMethod === 'simulate' ?
130
131
  lanternTimingDeltaMs : observedTimingDeltaMs;
131
132
  totalWastedMs += wastedMs;
@@ -95,7 +95,8 @@ class UsesRelPreconnectAudit extends Audit {
95
95
  * @return {boolean}
96
96
  */
97
97
  static socketStartTimeIsBelowThreshold(record, mainResource) {
98
- return Math.max(0, record.startTime - mainResource.endTime) < PRECONNECT_SOCKET_MAX_IDLE_IN_MS;
98
+ const timeSinceMainEnd = Math.max(0, record.networkRequestTime - mainResource.networkEndTime);
99
+ return timeSinceMainEnd < PRECONNECT_SOCKET_MAX_IDLE_IN_MS;
99
100
  }
100
101
 
101
102
  /**
@@ -169,7 +170,7 @@ class UsesRelPreconnectAudit extends Audit {
169
170
  // Sometimes requests are done simultaneous and the connection has not been made
170
171
  // chrome will try to connect for each network record, we get the first record
171
172
  const firstRecordOfOrigin = records.reduce((firstRecord, record) => {
172
- return (record.startTime < firstRecord.startTime) ? record : firstRecord;
173
+ return (record.networkRequestTime < firstRecord.networkRequestTime) ? record : firstRecord;
173
174
  });
174
175
 
175
176
  // Skip the origin if we don't have timing information
@@ -186,8 +187,8 @@ class UsesRelPreconnectAudit extends Audit {
186
187
  if (firstRecordOfOrigin.parsedURL.scheme === 'https') connectionTime = connectionTime * 2;
187
188
 
188
189
  const timeBetweenMainResourceAndDnsStart =
189
- firstRecordOfOrigin.startTime -
190
- mainResource.endTime +
190
+ firstRecordOfOrigin.networkRequestTime -
191
+ mainResource.networkEndTime +
191
192
  firstRecordOfOrigin.timing.dnsStart;
192
193
 
193
194
  const wastedMs = Math.min(connectionTime, timeBetweenMainResourceAndDnsStart);
@@ -150,7 +150,6 @@ const artifacts = {
150
150
  MetaElements: '',
151
151
  NetworkUserAgent: '',
152
152
  OptimizedImages: '',
153
- PasswordInputsWithPreventedPaste: '',
154
153
  ResponseCompression: '',
155
154
  RobotsTxt: '',
156
155
  ServiceWorker: '',
@@ -200,7 +199,6 @@ const defaultConfig = {
200
199
  {id: artifacts.MetaElements, gatherer: 'meta-elements'},
201
200
  {id: artifacts.NetworkUserAgent, gatherer: 'network-user-agent'},
202
201
  {id: artifacts.OptimizedImages, gatherer: 'dobetterweb/optimized-images'},
203
- {id: artifacts.PasswordInputsWithPreventedPaste, gatherer: 'dobetterweb/password-inputs-with-prevented-paste'},
204
202
  {id: artifacts.ResponseCompression, gatherer: 'dobetterweb/response-compression'},
205
203
  {id: artifacts.RobotsTxt, gatherer: 'seo/robots-txt'},
206
204
  {id: artifacts.ServiceWorker, gatherer: 'service-worker'},
@@ -361,7 +359,7 @@ const defaultConfig = {
361
359
  'dobetterweb/no-document-write',
362
360
  'dobetterweb/js-libraries',
363
361
  'dobetterweb/notification-on-start',
364
- 'dobetterweb/password-inputs-can-be-pasted-into',
362
+ 'dobetterweb/paste-preventing-inputs',
365
363
  'dobetterweb/uses-http2',
366
364
  'dobetterweb/uses-passive-event-listeners',
367
365
  'seo/meta-description',
@@ -614,7 +612,7 @@ const defaultConfig = {
614
612
  {id: 'notification-on-start', weight: 1, group: 'best-practices-trust-safety'},
615
613
  {id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
616
614
  // User Experience
617
- {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'},
615
+ {id: 'paste-preventing-inputs', weight: 1, group: 'best-practices-ux'},
618
616
  {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
619
617
  {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'},
620
618
  {id: 'preload-fonts', weight: 1, group: 'best-practices-ux'},
@@ -218,9 +218,9 @@ class NetworkMonitor extends NetworkMonitorEventEmitter {
218
218
  if (request.protocol === 'ws' || request.protocol === 'wss') return;
219
219
 
220
220
  // convert the network timestamp to ms
221
- timeBoundaries.push({time: request.startTime * 1000, isStart: true});
221
+ timeBoundaries.push({time: request.networkRequestTime * 1000, isStart: true});
222
222
  if (request.finished) {
223
- timeBoundaries.push({time: request.endTime * 1000, isStart: false});
223
+ timeBoundaries.push({time: request.networkEndTime * 1000, isStart: false});
224
224
  }
225
225
  });
226
226
 
@@ -155,7 +155,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
155
155
  */
156
156
  static async findBlockingTags(driver, networkRecords) {
157
157
  const firstRequestEndTime = networkRecords.reduce(
158
- (min, record) => Math.min(min, record.endTime),
158
+ (min, record) => Math.min(min, record.networkEndTime),
159
159
  Infinity
160
160
  );
161
161
  const tags = await driver.executionContext.evaluate(collectTagsThatBlockFirstPaint, {args: []});
@@ -167,7 +167,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
167
167
  const request = requests.get(tag.url);
168
168
  if (!request || request.isLinkPreload) continue;
169
169
 
170
- let endTime = request.endTime;
170
+ let endTime = request.networkEndTime;
171
171
  let mediaChanges;
172
172
 
173
173
  if (tag.tagName === 'LINK') {
@@ -180,7 +180,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
180
180
  if (timesResourceBecameNonBlocking.length > 0) {
181
181
  const earliestNonBlockingTime = Math.min(...timesResourceBecameNonBlocking);
182
182
  const lastTimeResourceWasBlocking = Math.max(
183
- request.startTime,
183
+ request.networkRequestTime,
184
184
  firstRequestEndTime + earliestNonBlockingTime / 1000
185
185
  );
186
186
  endTime = Math.min(endTime, lastTimeResourceWasBlocking);
@@ -194,7 +194,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
194
194
  result.push({
195
195
  tag: {tagName, url, mediaChanges},
196
196
  transferSize: request.transferSize,
197
- startTime: request.startTime,
197
+ startTime: request.networkRequestTime,
198
198
  endTime,
199
199
  });
200
200
 
@@ -61,6 +61,11 @@ function collectElements() {
61
61
  return [...labelElToArtifact.keys()].indexOf(labelEl);
62
62
  });
63
63
 
64
+ let preventsPaste;
65
+ if (!inputEl.readOnly) {
66
+ preventsPaste = !inputEl.dispatchEvent(new ClipboardEvent('paste', {cancelable: true}));
67
+ }
68
+
64
69
  inputArtifacts.push({
65
70
  parentFormIndex,
66
71
  labelIndices,
@@ -74,6 +79,7 @@ function collectElements() {
74
79
  // Requires `--enable-features=AutofillShowTypePredictions`.
75
80
  prediction: inputEl.getAttribute('autofill-prediction'),
76
81
  },
82
+ preventsPaste,
77
83
  // @ts-expect-error - getNodeDetails put into scope via stringification
78
84
  node: getNodeDetails(inputEl),
79
85
  });
@@ -59,7 +59,6 @@ legacyDefaultConfig.passes = [{
59
59
  'dobetterweb/doctype',
60
60
  'dobetterweb/domstats',
61
61
  'dobetterweb/optimized-images',
62
- 'dobetterweb/password-inputs-with-prevented-paste',
63
62
  'dobetterweb/response-compression',
64
63
  'dobetterweb/tags-blocking-first-paint',
65
64
  'seo/font-size',
@@ -367,7 +367,7 @@ const UIStrings = {
367
367
  * @description Description text for not restored reason InjectedJavascript.
368
368
  */
369
369
  injectedJavascript:
370
- 'IPages that JavaScript is injected into by extensions are not currently eligible for back/forward cache.',
370
+ 'Pages that JavaScript is injected into by extensions are not currently eligible for back/forward cache.',
371
371
  /**
372
372
  * @description Description text for not restored reason InjectedStyleSheet.
373
373
  */
@@ -26,14 +26,14 @@ class NetworkNode extends BaseNode {
26
26
  * @return {number}
27
27
  */
28
28
  get startTime() {
29
- return this._record.startTime * 1000;
29
+ return this._record.networkRequestTime * 1000;
30
30
  }
31
31
 
32
32
  /**
33
33
  * @return {number}
34
34
  */
35
35
  get endTime() {
36
- return this._record.endTime * 1000;
36
+ return this._record.networkEndTime * 1000;
37
37
  }
38
38
 
39
39
  /**
@@ -150,7 +150,7 @@ class NetworkAnalyzer {
150
150
  if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
151
151
 
152
152
  // Compute the amount of time downloading everything after the first congestion window took
153
- const totalTime = record.endTime - record.startTime;
153
+ const totalTime = record.networkEndTime - record.networkRequestTime;
154
154
  const downloadTimeAfterFirstByte = totalTime - timing.receiveHeadersEnd;
155
155
  const numberOfRoundTrips = Math.log2(record.transferSize / INITIAL_CWD);
156
156
 
@@ -279,17 +279,19 @@ class NetworkAnalyzer {
279
279
  const groupedByOrigin = NetworkAnalyzer.groupByOrigin(records);
280
280
  for (const [_, originRecords] of groupedByOrigin.entries()) {
281
281
  const earliestReusePossible = originRecords
282
- .map(record => record.endTime)
282
+ .map(record => record.networkEndTime)
283
283
  .reduce((a, b) => Math.min(a, b), Infinity);
284
284
 
285
285
  for (const record of originRecords) {
286
286
  connectionWasReused.set(
287
287
  record.requestId,
288
- record.startTime >= earliestReusePossible || record.protocol === 'h2'
288
+ record.networkRequestTime >= earliestReusePossible || record.protocol === 'h2'
289
289
  );
290
290
  }
291
291
 
292
- const firstRecord = originRecords.reduce((a, b) => (a.startTime > b.startTime ? b : a));
292
+ const firstRecord = originRecords.reduce((a, b) => {
293
+ return a.networkRequestTime > b.networkRequestTime ? b : a;
294
+ });
293
295
  connectionWasReused.set(firstRecord.requestId, false);
294
296
  }
295
297
 
@@ -399,8 +401,8 @@ class NetworkAnalyzer {
399
401
 
400
402
  // If we've made it this far, all the times we need should be valid (i.e. not undefined/-1).
401
403
  totalBytes += record.transferSize;
402
- boundaries.push({time: record.responseReceivedTime / 1000, isStart: true});
403
- boundaries.push({time: record.endTime / 1000, isStart: false});
404
+ boundaries.push({time: record.responseHeadersEndTime / 1000, isStart: true});
405
+ boundaries.push({time: record.networkEndTime / 1000, isStart: false});
404
406
  return boundaries;
405
407
  }, /** @type {Array<{time: number, isStart: boolean}>} */([])).sort((a, b) => a.time - b.time);
406
408
 
@@ -0,0 +1,8 @@
1
+ export type ItemValueType = import('../../types/lhr/audit-details').default.ItemValueType;
2
+ /** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
3
+ /**
4
+ * Upgrades an lhr object in-place to account for changes in the data structure over major versions.
5
+ * @param {LH.Result} lhr
6
+ */
7
+ export function upgradeLhrForCompatibility(lhr: LH.Result): void;
8
+ //# sourceMappingURL=lighthouse-compatibility.d.ts.map
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ import {Util} from '../../shared/util.js';
8
+
9
+ const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
10
+
11
+ /** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
12
+
13
+ /**
14
+ * Upgrades an lhr object in-place to account for changes in the data structure over major versions.
15
+ * @param {LH.Result} lhr
16
+ */
17
+ function upgradeLhrForCompatibility(lhr) {
18
+ // If LHR is older (≤3.0.3), it has no locale setting. Set default.
19
+ if (!lhr.configSettings.locale) {
20
+ lhr.configSettings.locale = 'en';
21
+ }
22
+ if (!lhr.configSettings.formFactor) {
23
+ // @ts-expect-error fallback handling for emulatedFormFactor
24
+ lhr.configSettings.formFactor = lhr.configSettings.emulatedFormFactor;
25
+ }
26
+
27
+ lhr.finalDisplayedUrl = Util.getFinalDisplayedUrl(lhr);
28
+ lhr.mainDocumentUrl = Util.getMainDocumentUrl(lhr);
29
+
30
+ for (const audit of Object.values(lhr.audits)) {
31
+ // Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
32
+ // into 'notApplicable' (LHR ≥4.0).
33
+ // @ts-expect-error tsc rightly flags that these values shouldn't occur.
34
+ // eslint-disable-next-line max-len
35
+ if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
36
+ audit.scoreDisplayMode = 'notApplicable';
37
+ }
38
+
39
+ if (audit.details) {
40
+ // Turn `auditDetails.type` of undefined (LHR <4.2) and 'diagnostic' (LHR <5.0)
41
+ // into 'debugdata' (LHR ≥5.0).
42
+ // @ts-expect-error tsc rightly flags that these values shouldn't occur.
43
+ if (audit.details.type === undefined || audit.details.type === 'diagnostic') {
44
+ // @ts-expect-error details is of type never.
45
+ audit.details.type = 'debugdata';
46
+ }
47
+
48
+ // Add the jpg data URL prefix to filmstrip screenshots without them (LHR <5.0).
49
+ if (audit.details.type === 'filmstrip') {
50
+ for (const screenshot of audit.details.items) {
51
+ if (!screenshot.data.startsWith(SCREENSHOT_PREFIX)) {
52
+ screenshot.data = SCREENSHOT_PREFIX + screenshot.data;
53
+ }
54
+ }
55
+ }
56
+
57
+ // Circa 10.0, table items were refactored.
58
+ if (audit.details.type === 'table') {
59
+ for (const heading of audit.details.headings) {
60
+ /** @type {{itemType: ItemValueType|undefined, text: string|undefined}} */
61
+ // @ts-expect-error
62
+ const {itemType, text} = heading;
63
+ if (itemType !== undefined) {
64
+ heading.valueType = itemType;
65
+ // @ts-expect-error
66
+ delete heading.itemType;
67
+ }
68
+ if (text !== undefined) {
69
+ heading.label = text;
70
+ // @ts-expect-error
71
+ delete heading.text;
72
+ }
73
+
74
+ // @ts-expect-error
75
+ const subItemsItemType = heading.subItemsHeading?.itemType;
76
+ if (heading.subItemsHeading && subItemsItemType !== undefined) {
77
+ heading.subItemsHeading.valueType = subItemsItemType;
78
+ // @ts-expect-error
79
+ delete heading.subItemsHeading.itemType;
80
+ }
81
+ }
82
+ }
83
+
84
+ // TODO: convert printf-style displayValue.
85
+ // Added: #5099, v3
86
+ // Removed: #6767, v4
87
+ }
88
+ }
89
+
90
+ // This backcompat converts old LHRs (<9.0.0) to use the new "hidden" group.
91
+ // Old LHRs used "no group" to identify audits that should be hidden in performance instead of the "hidden" group.
92
+ // Newer LHRs use "no group" to identify opportunities and diagnostics whose groups are assigned by details type.
93
+ const [majorVersion] = lhr.lighthouseVersion.split('.').map(Number);
94
+ const perfCategory = lhr.categories['performance'];
95
+ if (majorVersion < 9 && perfCategory) {
96
+ if (!lhr.categoryGroups) lhr.categoryGroups = {};
97
+ lhr.categoryGroups['hidden'] = {title: ''};
98
+ for (const auditRef of perfCategory.auditRefs) {
99
+ if (!auditRef.group) {
100
+ auditRef.group = 'hidden';
101
+ } else if (['load-opportunities', 'diagnostics'].includes(auditRef.group)) {
102
+ delete auditRef.group;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Add some minimal stuff so older reports still work.
108
+ if (!lhr.environment) {
109
+ // @ts-expect-error
110
+ lhr.environment = {benchmarkIndex: 0};
111
+ }
112
+ if (!lhr.configSettings.screenEmulation) {
113
+ // @ts-expect-error
114
+ lhr.configSettings.screenEmulation = {};
115
+ }
116
+ if (!lhr.i18n) {
117
+ // @ts-expect-error
118
+ lhr.i18n = {};
119
+ }
120
+
121
+ // In 10.0, full-page-screenshot became a top-level property on the LHR.
122
+ if (lhr.audits['full-page-screenshot']) {
123
+ const details = /** @type {LH.Result.FullPageScreenshot=} */ (
124
+ lhr.audits['full-page-screenshot'].details);
125
+ if (details) {
126
+ lhr.fullPageScreenshot = {
127
+ screenshot: details.screenshot,
128
+ nodes: details.nodes,
129
+ };
130
+ } else {
131
+ lhr.fullPageScreenshot = null;
132
+ }
133
+ delete lhr.audits['full-page-screenshot'];
134
+ }
135
+ }
136
+
137
+ export {
138
+ upgradeLhrForCompatibility,
139
+ };