lighthouse 11.3.0 → 11.4.0

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 (129) hide show
  1. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  2. package/core/audits/bf-cache.d.ts +1 -0
  3. package/core/audits/bf-cache.js +12 -2
  4. package/core/audits/bootup-time.js +3 -0
  5. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -10
  6. package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -46
  7. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
  8. package/core/audits/byte-efficiency/duplicated-javascript.js +9 -38
  9. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -11
  10. package/core/audits/byte-efficiency/legacy-javascript.js +5 -36
  11. package/core/audits/byte-efficiency/unminified-css.js +2 -2
  12. package/core/audits/byte-efficiency/unminified-javascript.js +2 -3
  13. package/core/audits/byte-efficiency/unused-javascript.js +10 -11
  14. package/core/audits/dobetterweb/uses-http2.d.ts +1 -1
  15. package/core/audits/dobetterweb/uses-http2.js +8 -5
  16. package/core/audits/layout-shift-elements.js +32 -17
  17. package/core/audits/metrics/cumulative-layout-shift.js +5 -1
  18. package/core/audits/metrics/max-potential-fid.d.ts +20 -0
  19. package/core/audits/metrics/max-potential-fid.js +69 -0
  20. package/core/audits/resource-summary.d.ts +11 -0
  21. package/core/audits/resource-summary.js +86 -0
  22. package/core/audits/third-party-cookies.d.ts +22 -0
  23. package/core/audits/third-party-cookies.js +112 -0
  24. package/core/audits/third-party-summary.d.ts +20 -2
  25. package/core/audits/third-party-summary.js +3 -48
  26. package/core/computed/entity-classification.js +1 -1
  27. package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
  28. package/core/computed/metrics/cumulative-layout-shift.js +47 -1
  29. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
  30. package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
  31. package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
  32. package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
  33. package/core/computed/metrics/lantern-metric.d.ts +2 -2
  34. package/core/computed/metrics/lantern-metric.js +6 -5
  35. package/core/computed/resource-summary.d.ts +0 -1
  36. package/core/computed/resource-summary.js +2 -2
  37. package/core/computed/unused-css.js +2 -2
  38. package/core/config/constants.d.ts +2 -2
  39. package/core/config/constants.js +2 -2
  40. package/core/config/default-config.js +4 -0
  41. package/core/config/filters.js +1 -0
  42. package/core/gather/base-artifacts.js +2 -1
  43. package/core/gather/gatherers/css-usage.js +5 -0
  44. package/core/gather/gatherers/trace-elements.d.ts +6 -15
  45. package/core/gather/gatherers/trace-elements.js +15 -71
  46. package/core/gather/navigation-runner.js +1 -1
  47. package/core/lib/i18n/i18n.d.ts +2 -2
  48. package/core/lib/i18n/i18n.js +2 -2
  49. package/core/lib/minification-estimator.js +1 -1
  50. package/core/lib/network-recorder.d.ts +8 -0
  51. package/core/lib/network-recorder.js +13 -0
  52. package/core/lib/network-request.d.ts +11 -0
  53. package/core/lib/network-request.js +21 -1
  54. package/core/lib/rect-helpers.d.ts +5 -0
  55. package/core/lib/rect-helpers.js +15 -0
  56. package/core/lib/script-helpers.d.ts +34 -5
  57. package/core/lib/script-helpers.js +136 -0
  58. package/core/lib/url-utils.d.ts +6 -0
  59. package/core/lib/url-utils.js +14 -2
  60. package/core/runner.js +5 -1
  61. package/core/scripts/download-chrome.sh +56 -13
  62. package/dist/report/bundle.esm.js +30 -13
  63. package/dist/report/flow.js +32 -15
  64. package/dist/report/standalone.js +32 -15
  65. package/flow-report/src/i18n/i18n.d.ts +4 -0
  66. package/package.json +5 -4
  67. package/report/assets/styles.css +25 -8
  68. package/report/assets/templates.html +1 -1
  69. package/report/clients/standalone.js +1 -0
  70. package/report/renderer/components.js +2 -2
  71. package/report/renderer/explodey-gauge.js +0 -1
  72. package/report/renderer/performance-category-renderer.js +4 -2
  73. package/report/renderer/report-renderer.js +12 -1
  74. package/report/renderer/report-ui-features.js +3 -2
  75. package/report/renderer/report-utils.d.ts +2 -0
  76. package/report/renderer/report-utils.js +8 -4
  77. package/report/types/report-renderer.d.ts +2 -0
  78. package/shared/localization/locales/ar-XB.json +34 -25
  79. package/shared/localization/locales/ar.json +35 -26
  80. package/shared/localization/locales/bg.json +34 -25
  81. package/shared/localization/locales/ca.json +35 -26
  82. package/shared/localization/locales/cs.json +34 -25
  83. package/shared/localization/locales/da.json +51 -42
  84. package/shared/localization/locales/de.json +38 -29
  85. package/shared/localization/locales/el.json +34 -25
  86. package/shared/localization/locales/en-GB.json +35 -26
  87. package/shared/localization/locales/en-US.json +26 -5
  88. package/shared/localization/locales/en-XA.json +34 -25
  89. package/shared/localization/locales/en-XL.json +26 -5
  90. package/shared/localization/locales/es-419.json +34 -25
  91. package/shared/localization/locales/es.json +34 -25
  92. package/shared/localization/locales/fi.json +34 -25
  93. package/shared/localization/locales/fil.json +35 -26
  94. package/shared/localization/locales/fr.json +34 -25
  95. package/shared/localization/locales/he.json +35 -26
  96. package/shared/localization/locales/hi.json +35 -26
  97. package/shared/localization/locales/hr.json +35 -26
  98. package/shared/localization/locales/hu.json +34 -25
  99. package/shared/localization/locales/id.json +34 -25
  100. package/shared/localization/locales/it.json +34 -25
  101. package/shared/localization/locales/ja.json +34 -25
  102. package/shared/localization/locales/ko.json +35 -26
  103. package/shared/localization/locales/lt.json +34 -25
  104. package/shared/localization/locales/lv.json +35 -26
  105. package/shared/localization/locales/nl.json +35 -26
  106. package/shared/localization/locales/no.json +34 -25
  107. package/shared/localization/locales/pl.json +34 -25
  108. package/shared/localization/locales/pt-PT.json +35 -26
  109. package/shared/localization/locales/pt.json +35 -26
  110. package/shared/localization/locales/ro.json +34 -25
  111. package/shared/localization/locales/ru.json +35 -26
  112. package/shared/localization/locales/sk.json +35 -26
  113. package/shared/localization/locales/sl.json +35 -26
  114. package/shared/localization/locales/sr-Latn.json +34 -25
  115. package/shared/localization/locales/sr.json +34 -25
  116. package/shared/localization/locales/sv.json +35 -26
  117. package/shared/localization/locales/ta.json +35 -26
  118. package/shared/localization/locales/te.json +35 -26
  119. package/shared/localization/locales/th.json +34 -25
  120. package/shared/localization/locales/tr.json +35 -26
  121. package/shared/localization/locales/uk.json +34 -25
  122. package/shared/localization/locales/vi.json +34 -25
  123. package/shared/localization/locales/zh-HK.json +35 -26
  124. package/shared/localization/locales/zh-TW.json +38 -29
  125. package/shared/localization/locales/zh.json +34 -25
  126. package/shared/util.d.ts +14 -2
  127. package/shared/util.js +25 -3
  128. package/tsconfig.json +2 -0
  129. package/types/artifacts.d.ts +5 -2
@@ -24,6 +24,17 @@ class LanternFirstContentfulPaint extends LanternMetric {
24
24
  };
25
25
  }
26
26
 
27
+ /**
28
+ * @typedef FirstPaintBasedGraphOpts
29
+ * @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
30
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
31
+ * @property {function(NetworkNode):boolean} treatNodeAsRenderBlocking The function that determines
32
+ * which resources should be considered *possibly* render-blocking.
33
+ * @property {(function(CPUNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
34
+ * determines which CPU nodes should also be included in our blocking node IDs set,
35
+ * beyond what getRenderBlockingNodeData() already includes.
36
+ */
37
+
27
38
  /**
28
39
  * This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
29
40
  * *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
@@ -31,19 +42,12 @@ class LanternFirstContentfulPaint extends LanternMetric {
31
42
  * given timestamp.
32
43
  *
33
44
  * @param {Node} graph
34
- * @param {number} filterTimestamp The timestamp used to filter out tasks that occured after our
35
- * paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
36
- * @param {function(NetworkNode):boolean} blockingScriptFilter The function that determines which scripts
37
- * should be considered *possibly* render-blocking.
38
- * @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
39
- * should also be included in our blocking node IDs set.
40
- * @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, blockingCpuNodeIds: Set<string>}}
45
+ * @param {FirstPaintBasedGraphOpts} opts
46
+ * @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
41
47
  */
42
- static getBlockingNodeData(
48
+ static getRenderBlockingNodeData(
43
49
  graph,
44
- filterTimestamp,
45
- blockingScriptFilter,
46
- extraBlockingCpuNodesToIncludeFilter
50
+ {cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
47
51
  ) {
48
52
  /** @type {Map<string, CPUNode>} A map of blocking script URLs to the earliest EvaluateScript task node that executed them. */
49
53
  const scriptUrlToNodeMap = new Map();
@@ -52,9 +56,9 @@ class LanternFirstContentfulPaint extends LanternMetric {
52
56
  const cpuNodes = [];
53
57
  graph.traverse(node => {
54
58
  if (node.type === BaseNode.TYPES.CPU) {
55
- // A task is *possibly* render blocking if it *started* before filterTimestamp.
59
+ // A task is *possibly* render blocking if it *started* before cutoffTimestamp.
56
60
  // We use startTime here because the paint event can be *inside* the task that was render blocking.
57
- if (node.startTime <= filterTimestamp) cpuNodes.push(node);
61
+ if (node.startTime <= cutoffTimestamp) cpuNodes.push(node);
58
62
 
59
63
  // Build our script URL map to find the earliest EvaluateScript task node.
60
64
  const scriptUrls = node.getEvaluateScriptURLs();
@@ -68,16 +72,19 @@ class LanternFirstContentfulPaint extends LanternMetric {
68
72
 
69
73
  cpuNodes.sort((a, b) => a.startTime - b.startTime);
70
74
 
71
- // A script is *possibly* render blocking if it finished loading before filterTimestamp.
75
+ // A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
72
76
  const possiblyRenderBlockingScriptUrls = LanternMetric.getScriptUrls(graph, node => {
73
- return node.endTime <= filterTimestamp && blockingScriptFilter(node);
77
+ // The optimistic LCP treatNodeAsRenderBlocking fn wants to exclude some images in the graph,
78
+ // but here it only receives scripts to evaluate. It's a no-op in this case, but it will
79
+ // matter below in the getFirstPaintBasedGraph clone operation.
80
+ return node.endTime <= cutoffTimestamp && treatNodeAsRenderBlocking(node);
74
81
  });
75
82
 
76
- // A script is *definitely not* render blocking if its EvaluateScript task started after filterTimestamp.
83
+ // A script is *definitely not* render blocking if its EvaluateScript task started after cutoffTimestamp.
77
84
  /** @type {Set<string>} */
78
85
  const definitelyNotRenderBlockingScriptUrls = new Set();
79
86
  /** @type {Set<string>} */
80
- const blockingCpuNodeIds = new Set();
87
+ const renderBlockingCpuNodeIds = new Set();
81
88
  for (const url of possiblyRenderBlockingScriptUrls) {
82
89
  // Lookup the CPU node that had the earliest EvaluateScript for this URL.
83
90
  const cpuNodeForUrl = scriptUrlToNodeMap.get(url);
@@ -85,9 +92,9 @@ class LanternFirstContentfulPaint extends LanternMetric {
85
92
  // If we can't find it at all, we can't conclude anything, so just skip it.
86
93
  if (!cpuNodeForUrl) continue;
87
94
 
88
- // If we found it and it was in our `cpuNodes` set that means it finished before filterTimestamp, so it really is render-blocking.
95
+ // If we found it and it was in our `cpuNodes` set that means it finished before cutoffTimestamp, so it really is render-blocking.
89
96
  if (cpuNodes.includes(cpuNodeForUrl)) {
90
- blockingCpuNodeIds.add(cpuNodeForUrl.id);
97
+ renderBlockingCpuNodeIds.add(cpuNodeForUrl.id);
91
98
  continue;
92
99
  }
93
100
 
@@ -99,22 +106,22 @@ class LanternFirstContentfulPaint extends LanternMetric {
99
106
  // The first layout, first paint, and first ParseHTML are almost always necessary for first paint,
100
107
  // so we always include those CPU nodes.
101
108
  const firstLayout = cpuNodes.find(node => node.didPerformLayout());
102
- if (firstLayout) blockingCpuNodeIds.add(firstLayout.id);
109
+ if (firstLayout) renderBlockingCpuNodeIds.add(firstLayout.id);
103
110
  const firstPaint = cpuNodes.find(node => node.childEvents.some(e => e.name === 'Paint'));
104
- if (firstPaint) blockingCpuNodeIds.add(firstPaint.id);
111
+ if (firstPaint) renderBlockingCpuNodeIds.add(firstPaint.id);
105
112
  const firstParse = cpuNodes.find(node => node.childEvents.some(e => e.name === 'ParseHTML'));
106
- if (firstParse) blockingCpuNodeIds.add(firstParse.id);
113
+ if (firstParse) renderBlockingCpuNodeIds.add(firstParse.id);
107
114
 
108
115
  // If a CPU filter was passed in, we also want to include those extra nodes.
109
- if (extraBlockingCpuNodesToIncludeFilter) {
116
+ if (additionalCpuNodesToTreatAsRenderBlocking) {
110
117
  cpuNodes
111
- .filter(extraBlockingCpuNodesToIncludeFilter)
112
- .forEach(node => blockingCpuNodeIds.add(node.id));
118
+ .filter(additionalCpuNodesToTreatAsRenderBlocking)
119
+ .forEach(node => renderBlockingCpuNodeIds.add(node.id));
113
120
  }
114
121
 
115
122
  return {
116
123
  definitelyNotRenderBlockingScriptUrls,
117
- blockingCpuNodeIds,
124
+ renderBlockingCpuNodeIds,
118
125
  };
119
126
  }
120
127
 
@@ -122,35 +129,25 @@ class LanternFirstContentfulPaint extends LanternMetric {
122
129
  * This function computes the graph required for the first paint of interest.
123
130
  *
124
131
  * @param {Node} dependencyGraph
125
- * @param {number} paintTs The timestamp used to filter out tasks that occured after our
126
- * paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
127
- * @param {function(NetworkNode):boolean} blockingResourcesFilter The function that determines which resources
128
- * should be considered *possibly* render-blocking.
129
- * @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
130
- * should also be included in our blocking node IDs set.
132
+ * @param {FirstPaintBasedGraphOpts} opts
131
133
  * @return {Node}
132
134
  */
133
135
  static getFirstPaintBasedGraph(
134
136
  dependencyGraph,
135
- paintTs,
136
- blockingResourcesFilter,
137
- extraBlockingCpuNodesToIncludeFilter
137
+ {cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
138
138
  ) {
139
- const {
140
- definitelyNotRenderBlockingScriptUrls,
141
- blockingCpuNodeIds,
142
- } = this.getBlockingNodeData(
143
- dependencyGraph,
144
- paintTs,
145
- blockingResourcesFilter,
146
- extraBlockingCpuNodesToIncludeFilter
147
- );
139
+ const rbData = this.getRenderBlockingNodeData(dependencyGraph, {
140
+ cutoffTimestamp,
141
+ treatNodeAsRenderBlocking,
142
+ additionalCpuNodesToTreatAsRenderBlocking,
143
+ });
144
+ const {definitelyNotRenderBlockingScriptUrls, renderBlockingCpuNodeIds} = rbData;
148
145
 
149
146
  return dependencyGraph.cloneWithRelationships(node => {
150
147
  if (node.type === BaseNode.TYPES.NETWORK) {
151
- // Exclude all nodes that ended after paintTs (except for the main document which we always consider necessary)
152
- // endTime is negative if request does not finish, make sure startTime isn't after paintTs in this case.
153
- const endedAfterPaint = node.endTime > paintTs || node.startTime > paintTs;
148
+ // Exclude all nodes that ended after cutoffTimestamp (except for the main document which we always consider necessary)
149
+ // endTime is negative if request does not finish, make sure startTime isn't after cutoffTimestamp in this case.
150
+ const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
154
151
  if (endedAfterPaint && !node.isMainDocument()) return false;
155
152
 
156
153
  const url = node.record.url;
@@ -159,10 +156,11 @@ class LanternFirstContentfulPaint extends LanternMetric {
159
156
  return false;
160
157
  }
161
158
 
162
- return blockingResourcesFilter(node);
159
+ // Lastly, build up the FCP graph of all nodes we consider render blocking
160
+ return treatNodeAsRenderBlocking(node);
163
161
  } else {
164
162
  // If it's a CPU node, just check if it was blocking.
165
- return blockingCpuNodeIds.has(node.id);
163
+ return renderBlockingCpuNodeIds.has(node.id);
166
164
  }
167
165
  });
168
166
  }
@@ -173,14 +171,14 @@ class LanternFirstContentfulPaint extends LanternMetric {
173
171
  * @return {Node}
174
172
  */
175
173
  static getOptimisticGraph(dependencyGraph, processedNavigation) {
176
- return this.getFirstPaintBasedGraph(
177
- dependencyGraph,
178
- processedNavigation.timestamps.firstContentfulPaint,
174
+ return this.getFirstPaintBasedGraph(dependencyGraph, {
175
+ cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
179
176
  // In the optimistic graph we exclude resources that appeared to be render blocking but were
180
177
  // initiated by a script. While they typically have a very high importance and tend to have a
181
178
  // significant impact on the page's content, these resources don't technically block rendering.
182
- node => node.hasRenderBlockingPriority() && node.initiatorType !== 'script'
183
- );
179
+ treatNodeAsRenderBlocking: node =>
180
+ node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
181
+ });
184
182
  }
185
183
 
186
184
  /**
@@ -189,11 +187,10 @@ class LanternFirstContentfulPaint extends LanternMetric {
189
187
  * @return {Node}
190
188
  */
191
189
  static getPessimisticGraph(dependencyGraph, processedNavigation) {
192
- return this.getFirstPaintBasedGraph(
193
- dependencyGraph,
194
- processedNavigation.timestamps.firstContentfulPaint,
195
- node => node.hasRenderBlockingPriority()
196
- );
190
+ return this.getFirstPaintBasedGraph(dependencyGraph, {
191
+ cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
192
+ treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
193
+ });
197
194
  }
198
195
  }
199
196
 
@@ -33,14 +33,13 @@ class LanternFirstMeaningfulPaint extends LanternMetric {
33
33
  if (!fmp) {
34
34
  throw new LighthouseError(LighthouseError.errors.NO_FMP);
35
35
  }
36
-
37
- return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
38
- dependencyGraph,
39
- fmp,
36
+ return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
37
+ cutoffTimestamp: fmp,
40
38
  // See LanternFirstContentfulPaint's getOptimisticGraph implementation for a longer description
41
39
  // of why we exclude script initiated resources here.
42
- node => node.hasRenderBlockingPriority() && node.initiatorType !== 'script'
43
- );
40
+ treatNodeAsRenderBlocking: node =>
41
+ node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
42
+ });
44
43
  }
45
44
 
46
45
  /**
@@ -54,13 +53,12 @@ class LanternFirstMeaningfulPaint extends LanternMetric {
54
53
  throw new LighthouseError(LighthouseError.errors.NO_FMP);
55
54
  }
56
55
 
57
- return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
58
- dependencyGraph,
59
- fmp,
60
- node => node.hasRenderBlockingPriority(),
56
+ return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
57
+ cutoffTimestamp: fmp,
58
+ treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
61
59
  // For pessimistic FMP we'll include *all* layout nodes
62
- node => node.didPerformLayout()
63
- );
60
+ additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
61
+ });
64
62
  }
65
63
 
66
64
  /**
@@ -32,7 +32,6 @@ class LanternLargestContentfulPaint extends LanternMetric {
32
32
  */
33
33
  static isNotLowPriorityImageNode(node) {
34
34
  if (node.type !== 'network') return true;
35
-
36
35
  const isImage = node.record.resourceType === 'Image';
37
36
  const isLowPriority = node.record.priority === 'Low' || node.record.priority === 'VeryLow';
38
37
  return !isImage || !isLowPriority;
@@ -49,11 +48,10 @@ class LanternLargestContentfulPaint extends LanternMetric {
49
48
  throw new LighthouseError(LighthouseError.errors.NO_LCP);
50
49
  }
51
50
 
52
- return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
53
- dependencyGraph,
54
- lcp,
55
- LanternLargestContentfulPaint.isNotLowPriorityImageNode
56
- );
51
+ return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
52
+ cutoffTimestamp: lcp,
53
+ treatNodeAsRenderBlocking: LanternLargestContentfulPaint.isNotLowPriorityImageNode,
54
+ });
57
55
  }
58
56
 
59
57
  /**
@@ -67,13 +65,12 @@ class LanternLargestContentfulPaint extends LanternMetric {
67
65
  throw new LighthouseError(LighthouseError.errors.NO_LCP);
68
66
  }
69
67
 
70
- return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
71
- dependencyGraph,
72
- lcp,
73
- _ => true,
68
+ return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
69
+ cutoffTimestamp: lcp,
70
+ treatNodeAsRenderBlocking: _ => true,
74
71
  // For pessimistic LCP we'll include *all* layout nodes
75
- node => node.didPerformLayout()
76
- );
72
+ additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
73
+ });
77
74
  }
78
75
 
79
76
  /**
@@ -24,10 +24,10 @@ export type Extras = {
24
24
  export class LanternMetric {
25
25
  /**
26
26
  * @param {Node} dependencyGraph
27
- * @param {function(NetworkNode):boolean=} condition
27
+ * @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
28
28
  * @return {Set<string>}
29
29
  */
30
- static getScriptUrls(dependencyGraph: Node, condition?: ((arg0: NetworkNode) => boolean) | undefined): Set<string>;
30
+ static getScriptUrls(dependencyGraph: Node, treatNodeAsRenderBlocking?: ((arg0: NetworkNode) => boolean) | undefined): Set<string>;
31
31
  /**
32
32
  * @return {LH.Gatherer.Simulation.MetricCoefficients}
33
33
  */
@@ -26,18 +26,19 @@ import {LoadSimulator} from '../load-simulator.js';
26
26
  class LanternMetric {
27
27
  /**
28
28
  * @param {Node} dependencyGraph
29
- * @param {function(NetworkNode):boolean=} condition
29
+ * @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
30
30
  * @return {Set<string>}
31
31
  */
32
- static getScriptUrls(dependencyGraph, condition) {
32
+ static getScriptUrls(dependencyGraph, treatNodeAsRenderBlocking) {
33
33
  /** @type {Set<string>} */
34
34
  const scriptUrls = new Set();
35
35
 
36
36
  dependencyGraph.traverse(node => {
37
- if (node.type === BaseNode.TYPES.CPU) return;
37
+ if (node.type !== BaseNode.TYPES.NETWORK) return;
38
38
  if (node.record.resourceType !== NetworkRequest.TYPES.Script) return;
39
- if (condition && !condition(node)) return;
40
- scriptUrls.add(node.record.url);
39
+ if (treatNodeAsRenderBlocking?.(node)) {
40
+ scriptUrls.add(node.record.url);
41
+ }
41
42
  });
42
43
 
43
44
  return scriptUrls;
@@ -39,7 +39,6 @@ declare class ResourceSummary {
39
39
  budgets: LH.Util.ImmutableObject<LH.Budget[] | null>;
40
40
  }, context: LH.Artifacts.ComputedContext): Promise<Record<LH.Budget.ResourceType, ResourceEntry>>;
41
41
  }
42
- import { Util } from '../../shared/util.js';
43
42
  import { Budget } from '../config/budget.js';
44
43
  import { NetworkRequest } from '../lib/network-request.js';
45
44
  import { EntityClassification } from './entity-classification.js';
@@ -9,7 +9,7 @@ import {makeComputedArtifact} from './computed-artifact.js';
9
9
  import {NetworkRecords} from './network-records.js';
10
10
  import {NetworkRequest} from '../lib/network-request.js';
11
11
  import {Budget} from '../config/budget.js';
12
- import {Util} from '../../shared/util.js';
12
+ import UrlUtils from '../lib/url-utils.js';
13
13
 
14
14
  /** @typedef {{count: number, resourceSize: number, transferSize: number}} ResourceEntry */
15
15
 
@@ -59,7 +59,7 @@ class ResourceSummary {
59
59
  firstPartyHosts = budget.options.firstPartyHostnames;
60
60
  } else {
61
61
  firstPartyHosts = classifiedEntities.firstParty?.domains.map(domain => `*.${domain}`) ||
62
- [`*.${Util.getRootDomain(URLArtifact.finalDisplayedUrl)}`];
62
+ [`*.${UrlUtils.getRootDomain(URLArtifact.finalDisplayedUrl)}`];
63
63
  }
64
64
 
65
65
  networkRecords.filter(record => {
@@ -5,9 +5,9 @@
5
5
  */
6
6
 
7
7
  import {makeComputedArtifact} from './computed-artifact.js';
8
- import {ByteEfficiencyAudit} from '../audits/byte-efficiency/byte-efficiency-audit.js';
9
8
  import {NetworkRecords} from './network-records.js';
10
9
  import {Util} from '../../shared/util.js';
10
+ import {estimateTransferSize} from '../lib/script-helpers.js';
11
11
 
12
12
  const PREVIEW_LENGTH = 100;
13
13
 
@@ -70,7 +70,7 @@ class UnusedCSS {
70
70
  usedUncompressedBytes += usedRule.endOffset - usedRule.startOffset;
71
71
  }
72
72
 
73
- const totalTransferredBytes = ByteEfficiencyAudit.estimateTransferSize(
73
+ const totalTransferredBytes = estimateTransferSize(
74
74
  stylesheetInfo.networkRecord, totalUncompressedBytes, 'Stylesheet');
75
75
  const percentUnused = (totalUncompressedBytes - usedUncompressedBytes) / totalUncompressedBytes;
76
76
  const wastedBytes = Math.round(percentUnused * totalTransferredBytes);
@@ -76,7 +76,7 @@ declare const MOTOGPOWER_EMULATION_METRICS: Required<LH.SharedFlagsSettings['scr
76
76
  * @type {Required<LH.SharedFlagsSettings['screenEmulation']>}
77
77
  */
78
78
  declare const DESKTOP_EMULATION_METRICS: Required<LH.SharedFlagsSettings['screenEmulation']>;
79
- declare const MOTOG4_USERAGENT: "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36";
80
- declare const DESKTOP_USERAGENT: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36";
79
+ declare const MOTOG4_USERAGENT: "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36";
80
+ declare const DESKTOP_USERAGENT: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36";
81
81
  export {};
82
82
  //# sourceMappingURL=constants.d.ts.map
@@ -80,8 +80,8 @@ const screenEmulationMetrics = {
80
80
  };
81
81
 
82
82
 
83
- const MOTOG4_USERAGENT = 'Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36'; // eslint-disable-line max-len
84
- const DESKTOP_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'; // eslint-disable-line max-len
83
+ const MOTOG4_USERAGENT = 'Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36'; // eslint-disable-line max-len
84
+ const DESKTOP_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'; // eslint-disable-line max-len
85
85
 
86
86
  const userAgents = {
87
87
  mobile: MOTOG4_USERAGENT,
@@ -202,6 +202,7 @@ const defaultConfig = {
202
202
  'image-size-responsive',
203
203
  'preload-fonts',
204
204
  'deprecations',
205
+ 'third-party-cookies',
205
206
  'mainthread-work-breakdown',
206
207
  'bootup-time',
207
208
  'uses-rel-preload',
@@ -215,6 +216,7 @@ const defaultConfig = {
215
216
  'metrics',
216
217
  'performance-budget',
217
218
  'timing-budget',
219
+ 'resource-summary',
218
220
  'third-party-summary',
219
221
  'third-party-facades',
220
222
  'largest-contentful-paint-element',
@@ -499,6 +501,7 @@ const defaultConfig = {
499
501
  {id: 'screenshot-thumbnails', weight: 0, group: 'hidden'},
500
502
  {id: 'final-screenshot', weight: 0, group: 'hidden'},
501
503
  {id: 'script-treemap-data', weight: 0, group: 'hidden'},
504
+ {id: 'resource-summary', weight: 0, group: 'hidden'},
502
505
  ],
503
506
  },
504
507
  'accessibility': {
@@ -607,6 +610,7 @@ const defaultConfig = {
607
610
  {id: 'no-unload-listeners', weight: 1, group: 'best-practices-general'},
608
611
  {id: 'js-libraries', weight: 0, group: 'best-practices-general'},
609
612
  {id: 'deprecations', weight: 5, group: 'best-practices-general'},
613
+ {id: 'third-party-cookies', weight: 5, group: 'best-practices-general'},
610
614
  {id: 'errors-in-console', weight: 1, group: 'best-practices-general'},
611
615
  {id: 'valid-source-maps', weight: 0, group: 'best-practices-general'},
612
616
  {id: 'inspector-issues', weight: 1, group: 'best-practices-general'},
@@ -20,6 +20,7 @@ const baseArtifactKeySource = {
20
20
  PageLoadError: '',
21
21
  HostFormFactor: '',
22
22
  HostUserAgent: '',
23
+ HostProduct: '',
23
24
  GatherContext: '',
24
25
  };
25
26
 
@@ -19,7 +19,7 @@ import {
19
19
  */
20
20
  async function getBaseArtifacts(resolvedConfig, driver, context) {
21
21
  const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
22
- const {userAgent} = await getBrowserVersion(driver.defaultSession);
22
+ const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
23
23
 
24
24
  return {
25
25
  // Meta artifacts.
@@ -32,6 +32,7 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
32
32
  HostUserAgent: userAgent,
33
33
  HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
34
34
  'mobile' : 'desktop',
35
+ HostProduct: product,
35
36
  // Contextual artifacts whose collection changes based on gather mode.
36
37
  URL: {
37
38
  finalDisplayedUrl: '',
@@ -132,6 +132,11 @@ class CSSUsage extends BaseGatherer {
132
132
  continue;
133
133
  }
134
134
 
135
+ // Exclude empty stylesheets.
136
+ if (sheet.header.length === 0) {
137
+ continue;
138
+ }
139
+
135
140
  dedupedStylesheets.set(sheet.content, sheet);
136
141
  }
137
142
 
@@ -1,7 +1,6 @@
1
1
  export default TraceElements;
2
2
  export type TraceElementData = {
3
3
  nodeId: number;
4
- score?: number;
5
4
  animations?: {
6
5
  name?: string;
7
6
  failureReasonsMask?: number;
@@ -11,20 +10,13 @@ export type TraceElementData = {
11
10
  };
12
11
  declare class TraceElements extends BaseGatherer {
13
12
  /**
14
- * @param {Array<number>} rect
15
- * @return {LH.Artifacts.Rect}
16
- */
17
- static traceRectToLHRect(rect: Array<number>): LH.Artifacts.Rect;
18
- /**
19
- * This function finds the top (up to 5) elements that contribute to the CLS score of the page.
20
- * Each layout shift event has a 'score' which is the amount added to the CLS as a result of the given shift(s).
21
- * We calculate the score per element by taking the 'score' of each layout shift event and
22
- * distributing it between all the nodes that were shifted, proportianal to the impact region of
23
- * each shifted element.
24
- * @param {LH.Artifacts.ProcessedTrace} processedTrace
25
- * @return {Array<TraceElementData>}
13
+ * This function finds the top (up to 15) elements that contribute to the CLS score of the page.
14
+ *
15
+ * @param {LH.Trace} trace
16
+ * @param {LH.Gatherer.Context} context
17
+ * @return {Promise<Array<TraceElementData>>}
26
18
  */
27
- static getTopLayoutShiftElements(processedTrace: LH.Artifacts.ProcessedTrace): Array<TraceElementData>;
19
+ static getTopLayoutShiftElements(trace: LH.Trace, context: LH.Gatherer.Context): Promise<Array<TraceElementData>>;
28
20
  /**
29
21
  * @param {LH.Trace} trace
30
22
  * @param {LH.Gatherer.Context} context
@@ -67,6 +59,5 @@ declare class TraceElements extends BaseGatherer {
67
59
  getArtifact(context: LH.Gatherer.Context<'Trace'>): Promise<LH.Artifacts.TraceElement[]>;
68
60
  }
69
61
  import BaseGatherer from '../base-gatherer.js';
70
- import { ProcessedTrace } from '../../computed/processed-trace.js';
71
62
  import Trace from './trace.js';
72
63
  //# sourceMappingURL=trace-elements.d.ts.map
@@ -15,7 +15,6 @@
15
15
  import BaseGatherer from '../base-gatherer.js';
16
16
  import {resolveNodeIdToObjectId} from '../driver/dom.js';
17
17
  import {pageFunctions} from '../../lib/page-functions.js';
18
- import * as RectHelpers from '../../lib/rect-helpers.js';
19
18
  import {Sentry} from '../../lib/sentry.js';
20
19
  import Trace from './trace.js';
21
20
  import {ProcessedTrace} from '../../computed/processed-trace.js';
@@ -25,7 +24,9 @@ import {Responsiveness} from '../../computed/metrics/responsiveness.js';
25
24
  import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
26
25
  import {ExecutionContext} from '../driver/execution-context.js';
27
26
 
28
- /** @typedef {{nodeId: number, score?: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
27
+ /** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
28
+
29
+ const MAX_LAYOUT_SHIFT_ELEMENTS = 15;
29
30
 
30
31
  /**
31
32
  * @this {HTMLElement}
@@ -63,75 +64,19 @@ class TraceElements extends BaseGatherer {
63
64
  }
64
65
 
65
66
  /**
66
- * @param {Array<number>} rect
67
- * @return {LH.Artifacts.Rect}
68
- */
69
- static traceRectToLHRect(rect) {
70
- const rectArgs = {
71
- x: rect[0],
72
- y: rect[1],
73
- width: rect[2],
74
- height: rect[3],
75
- };
76
- return RectHelpers.addRectTopAndBottom(rectArgs);
77
- }
78
-
79
- /**
80
- * This function finds the top (up to 5) elements that contribute to the CLS score of the page.
81
- * Each layout shift event has a 'score' which is the amount added to the CLS as a result of the given shift(s).
82
- * We calculate the score per element by taking the 'score' of each layout shift event and
83
- * distributing it between all the nodes that were shifted, proportianal to the impact region of
84
- * each shifted element.
85
- * @param {LH.Artifacts.ProcessedTrace} processedTrace
86
- * @return {Array<TraceElementData>}
67
+ * This function finds the top (up to 15) elements that contribute to the CLS score of the page.
68
+ *
69
+ * @param {LH.Trace} trace
70
+ * @param {LH.Gatherer.Context} context
71
+ * @return {Promise<Array<TraceElementData>>}
87
72
  */
88
- static getTopLayoutShiftElements(processedTrace) {
89
- /** @type {Map<number, number>} */
90
- const clsPerNode = new Map();
91
- const shiftEvents = CumulativeLayoutShift.getLayoutShiftEvents(processedTrace);
73
+ static async getTopLayoutShiftElements(trace, context) {
74
+ const {impactByNodeId} = await CumulativeLayoutShift.request(trace, context);
92
75
 
93
- shiftEvents.forEach((event) => {
94
- if (!event || !event.impactedNodes) {
95
- return;
96
- }
97
-
98
- let totalAreaOfImpact = 0;
99
- /** @type {Map<number, number>} */
100
- const pixelsMovedPerNode = new Map();
101
-
102
- event.impactedNodes.forEach(node => {
103
- if (!node.node_id || !node.old_rect || !node.new_rect) {
104
- return;
105
- }
106
-
107
- const oldRect = TraceElements.traceRectToLHRect(node.old_rect);
108
- const newRect = TraceElements.traceRectToLHRect(node.new_rect);
109
- const areaOfImpact = RectHelpers.getRectArea(oldRect) +
110
- RectHelpers.getRectArea(newRect) -
111
- RectHelpers.getRectOverlapArea(oldRect, newRect);
112
-
113
- pixelsMovedPerNode.set(node.node_id, areaOfImpact);
114
- totalAreaOfImpact += areaOfImpact;
115
- });
116
-
117
- for (const [nodeId, pixelsMoved] of pixelsMovedPerNode.entries()) {
118
- let clsContribution = clsPerNode.get(nodeId) || 0;
119
- clsContribution += (pixelsMoved / totalAreaOfImpact) * event.weightedScore;
120
- clsPerNode.set(nodeId, clsContribution);
121
- }
122
- });
123
-
124
- const topFive = [...clsPerNode.entries()]
125
- .sort((a, b) => b[1] - a[1])
126
- .slice(0, 5)
127
- .map(([nodeId, clsContribution]) => {
128
- return {
129
- nodeId: nodeId,
130
- score: clsContribution,
131
- };
132
- });
133
-
134
- return topFive;
76
+ return [...impactByNodeId.entries()]
77
+ .sort((a, b) => b[1] - a[1])
78
+ .slice(0, MAX_LAYOUT_SHIFT_ELEMENTS)
79
+ .map(([nodeId]) => ({nodeId}));
135
80
  }
136
81
 
137
82
  /**
@@ -265,7 +210,7 @@ class TraceElements extends BaseGatherer {
265
210
  const {mainThreadEvents} = processedTrace;
266
211
 
267
212
  const lcpNodeData = await TraceElements.getLcpElement(trace, context);
268
- const clsNodeData = TraceElements.getTopLayoutShiftElements(processedTrace);
213
+ const clsNodeData = await TraceElements.getTopLayoutShiftElements(trace, context);
269
214
  const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
270
215
  const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
271
216
 
@@ -311,7 +256,6 @@ class TraceElements extends BaseGatherer {
311
256
  traceElements.push({
312
257
  traceEventType,
313
258
  ...response.result.value,
314
- score: backendNodeData[i].score,
315
259
  animations: backendNodeData[i].animations,
316
260
  nodeId: backendNodeId,
317
261
  type: backendNodeData[i].type,