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.
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/bf-cache.d.ts +1 -0
- package/core/audits/bf-cache.js +12 -2
- package/core/audits/bootup-time.js +3 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -10
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -46
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
- package/core/audits/byte-efficiency/duplicated-javascript.js +9 -38
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -11
- package/core/audits/byte-efficiency/legacy-javascript.js +5 -36
- package/core/audits/byte-efficiency/unminified-css.js +2 -2
- package/core/audits/byte-efficiency/unminified-javascript.js +2 -3
- package/core/audits/byte-efficiency/unused-javascript.js +10 -11
- package/core/audits/dobetterweb/uses-http2.d.ts +1 -1
- package/core/audits/dobetterweb/uses-http2.js +8 -5
- package/core/audits/layout-shift-elements.js +32 -17
- package/core/audits/metrics/cumulative-layout-shift.js +5 -1
- package/core/audits/metrics/max-potential-fid.d.ts +20 -0
- package/core/audits/metrics/max-potential-fid.js +69 -0
- package/core/audits/resource-summary.d.ts +11 -0
- package/core/audits/resource-summary.js +86 -0
- package/core/audits/third-party-cookies.d.ts +22 -0
- package/core/audits/third-party-cookies.js +112 -0
- package/core/audits/third-party-summary.d.ts +20 -2
- package/core/audits/third-party-summary.js +3 -48
- package/core/computed/entity-classification.js +1 -1
- package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
- package/core/computed/metrics/cumulative-layout-shift.js +47 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
- package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
- package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
- package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
- package/core/computed/metrics/lantern-metric.d.ts +2 -2
- package/core/computed/metrics/lantern-metric.js +6 -5
- package/core/computed/resource-summary.d.ts +0 -1
- package/core/computed/resource-summary.js +2 -2
- package/core/computed/unused-css.js +2 -2
- package/core/config/constants.d.ts +2 -2
- package/core/config/constants.js +2 -2
- package/core/config/default-config.js +4 -0
- package/core/config/filters.js +1 -0
- package/core/gather/base-artifacts.js +2 -1
- package/core/gather/gatherers/css-usage.js +5 -0
- package/core/gather/gatherers/trace-elements.d.ts +6 -15
- package/core/gather/gatherers/trace-elements.js +15 -71
- package/core/gather/navigation-runner.js +1 -1
- package/core/lib/i18n/i18n.d.ts +2 -2
- package/core/lib/i18n/i18n.js +2 -2
- package/core/lib/minification-estimator.js +1 -1
- package/core/lib/network-recorder.d.ts +8 -0
- package/core/lib/network-recorder.js +13 -0
- package/core/lib/network-request.d.ts +11 -0
- package/core/lib/network-request.js +21 -1
- package/core/lib/rect-helpers.d.ts +5 -0
- package/core/lib/rect-helpers.js +15 -0
- package/core/lib/script-helpers.d.ts +34 -5
- package/core/lib/script-helpers.js +136 -0
- package/core/lib/url-utils.d.ts +6 -0
- package/core/lib/url-utils.js +14 -2
- package/core/runner.js +5 -1
- package/core/scripts/download-chrome.sh +56 -13
- package/dist/report/bundle.esm.js +30 -13
- package/dist/report/flow.js +32 -15
- package/dist/report/standalone.js +32 -15
- package/flow-report/src/i18n/i18n.d.ts +4 -0
- package/package.json +5 -4
- package/report/assets/styles.css +25 -8
- package/report/assets/templates.html +1 -1
- package/report/clients/standalone.js +1 -0
- package/report/renderer/components.js +2 -2
- package/report/renderer/explodey-gauge.js +0 -1
- package/report/renderer/performance-category-renderer.js +4 -2
- package/report/renderer/report-renderer.js +12 -1
- package/report/renderer/report-ui-features.js +3 -2
- package/report/renderer/report-utils.d.ts +2 -0
- package/report/renderer/report-utils.js +8 -4
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +34 -25
- package/shared/localization/locales/ar.json +35 -26
- package/shared/localization/locales/bg.json +34 -25
- package/shared/localization/locales/ca.json +35 -26
- package/shared/localization/locales/cs.json +34 -25
- package/shared/localization/locales/da.json +51 -42
- package/shared/localization/locales/de.json +38 -29
- package/shared/localization/locales/el.json +34 -25
- package/shared/localization/locales/en-GB.json +35 -26
- package/shared/localization/locales/en-US.json +26 -5
- package/shared/localization/locales/en-XA.json +34 -25
- package/shared/localization/locales/en-XL.json +26 -5
- package/shared/localization/locales/es-419.json +34 -25
- package/shared/localization/locales/es.json +34 -25
- package/shared/localization/locales/fi.json +34 -25
- package/shared/localization/locales/fil.json +35 -26
- package/shared/localization/locales/fr.json +34 -25
- package/shared/localization/locales/he.json +35 -26
- package/shared/localization/locales/hi.json +35 -26
- package/shared/localization/locales/hr.json +35 -26
- package/shared/localization/locales/hu.json +34 -25
- package/shared/localization/locales/id.json +34 -25
- package/shared/localization/locales/it.json +34 -25
- package/shared/localization/locales/ja.json +34 -25
- package/shared/localization/locales/ko.json +35 -26
- package/shared/localization/locales/lt.json +34 -25
- package/shared/localization/locales/lv.json +35 -26
- package/shared/localization/locales/nl.json +35 -26
- package/shared/localization/locales/no.json +34 -25
- package/shared/localization/locales/pl.json +34 -25
- package/shared/localization/locales/pt-PT.json +35 -26
- package/shared/localization/locales/pt.json +35 -26
- package/shared/localization/locales/ro.json +34 -25
- package/shared/localization/locales/ru.json +35 -26
- package/shared/localization/locales/sk.json +35 -26
- package/shared/localization/locales/sl.json +35 -26
- package/shared/localization/locales/sr-Latn.json +34 -25
- package/shared/localization/locales/sr.json +34 -25
- package/shared/localization/locales/sv.json +35 -26
- package/shared/localization/locales/ta.json +35 -26
- package/shared/localization/locales/te.json +35 -26
- package/shared/localization/locales/th.json +34 -25
- package/shared/localization/locales/tr.json +35 -26
- package/shared/localization/locales/uk.json +34 -25
- package/shared/localization/locales/vi.json +34 -25
- package/shared/localization/locales/zh-HK.json +35 -26
- package/shared/localization/locales/zh-TW.json +38 -29
- package/shared/localization/locales/zh.json +34 -25
- package/shared/util.d.ts +14 -2
- package/shared/util.js +25 -3
- package/tsconfig.json +2 -0
- 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 {
|
|
35
|
-
*
|
|
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
|
|
48
|
+
static getRenderBlockingNodeData(
|
|
43
49
|
graph,
|
|
44
|
-
|
|
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
|
|
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 <=
|
|
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
|
|
75
|
+
// A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
|
|
72
76
|
const possiblyRenderBlockingScriptUrls = LanternMetric.getScriptUrls(graph, node => {
|
|
73
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
109
|
+
if (firstLayout) renderBlockingCpuNodeIds.add(firstLayout.id);
|
|
103
110
|
const firstPaint = cpuNodes.find(node => node.childEvents.some(e => e.name === 'Paint'));
|
|
104
|
-
if (firstPaint)
|
|
111
|
+
if (firstPaint) renderBlockingCpuNodeIds.add(firstPaint.id);
|
|
105
112
|
const firstParse = cpuNodes.find(node => node.childEvents.some(e => e.name === 'ParseHTML'));
|
|
106
|
-
if (firstParse)
|
|
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 (
|
|
116
|
+
if (additionalCpuNodesToTreatAsRenderBlocking) {
|
|
110
117
|
cpuNodes
|
|
111
|
-
.filter(
|
|
112
|
-
.forEach(node =>
|
|
118
|
+
.filter(additionalCpuNodesToTreatAsRenderBlocking)
|
|
119
|
+
.forEach(node => renderBlockingCpuNodeIds.add(node.id));
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
return {
|
|
116
123
|
definitelyNotRenderBlockingScriptUrls,
|
|
117
|
-
|
|
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 {
|
|
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
|
-
|
|
136
|
-
blockingResourcesFilter,
|
|
137
|
-
extraBlockingCpuNodesToIncludeFilter
|
|
137
|
+
{cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
|
|
138
138
|
) {
|
|
139
|
-
const {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
152
|
-
// endTime is negative if request does not finish, make sure startTime isn't after
|
|
153
|
-
const endedAfterPaint = node.endTime >
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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=}
|
|
27
|
+
* @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
|
|
28
28
|
* @return {Set<string>}
|
|
29
29
|
*/
|
|
30
|
-
static getScriptUrls(dependencyGraph: Node,
|
|
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=}
|
|
29
|
+
* @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
|
|
30
30
|
* @return {Set<string>}
|
|
31
31
|
*/
|
|
32
|
-
static getScriptUrls(dependencyGraph,
|
|
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
|
|
37
|
+
if (node.type !== BaseNode.TYPES.NETWORK) return;
|
|
38
38
|
if (node.record.resourceType !== NetworkRequest.TYPES.Script) return;
|
|
39
|
-
if (
|
|
40
|
-
|
|
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
|
|
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
|
-
[`*.${
|
|
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 =
|
|
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/
|
|
80
|
-
declare const DESKTOP_USERAGENT: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
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
|
package/core/config/constants.js
CHANGED
|
@@ -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/
|
|
84
|
-
const DESKTOP_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
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'},
|
package/core/config/filters.js
CHANGED
|
@@ -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: '',
|
|
@@ -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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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,
|
|
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
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
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,
|