lighthouse 9.2.0 → 9.3.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/changelog.md +67 -0
- package/dist/report/bundle.esm.js +23 -11
- package/dist/report/flow.js +11 -11
- package/dist/report/standalone.js +8 -8
- package/flow-report/src/common.tsx +1 -1
- package/flow-report/src/sidebar/flow.tsx +1 -1
- package/flow-report/src/util.ts +1 -2
- package/lighthouse-cli/bin.js +0 -4
- package/lighthouse-cli/cli-flags.js +22 -8
- package/lighthouse-cli/run.js +5 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -2
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +103 -0
- package/lighthouse-cli/test/smokehouse/readme.md +10 -8
- package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
- package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -5
- package/lighthouse-core/audits/accessibility/axe-audit.js +4 -4
- package/lighthouse-core/audits/accessibility/object-alt.js +3 -3
- package/lighthouse-core/audits/audit.js +19 -4
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
- package/lighthouse-core/audits/deprecations.js +2 -2
- package/lighthouse-core/audits/dobetterweb/inspector-issues.js +11 -11
- package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -1
- package/lighthouse-core/audits/font-display.js +1 -1
- package/lighthouse-core/audits/installable-manifest.js +1 -1
- package/lighthouse-core/audits/is-on-https.js +1 -1
- package/lighthouse-core/audits/maskable-icon.js +1 -1
- package/lighthouse-core/audits/multi-check-audit.js +1 -1
- package/lighthouse-core/audits/network-requests.js +4 -4
- package/lighthouse-core/audits/preload-fonts.js +2 -3
- package/lighthouse-core/audits/preload-lcp-image.js +2 -12
- package/lighthouse-core/audits/seo/is-crawlable.js +2 -1
- package/lighthouse-core/audits/themed-omnibox.js +1 -1
- package/lighthouse-core/audits/uses-rel-preload.js +2 -3
- package/lighthouse-core/audits/valid-source-maps.js +2 -2
- package/lighthouse-core/computed/image-records.js +1 -1
- package/lighthouse-core/computed/js-bundles.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-metric.js +1 -1
- package/lighthouse-core/computed/metrics/timing-summary.js +35 -35
- package/lighthouse-core/computed/page-dependency-graph.js +2 -2
- package/lighthouse-core/computed/resource-summary.js +1 -1
- package/lighthouse-core/config/config-helpers.js +2 -2
- package/lighthouse-core/config/config.js +1 -1
- package/lighthouse-core/fraggle-rock/config/filters.js +23 -1
- package/lighthouse-core/fraggle-rock/config/validation.js +2 -2
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +5 -4
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +0 -1
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +0 -1
- package/lighthouse-core/fraggle-rock/user-flow.js +4 -4
- package/lighthouse-core/gather/connections/connection.js +1 -1
- package/lighthouse-core/gather/driver/execution-context.js +1 -1
- package/lighthouse-core/gather/driver/network-monitor.js +2 -2
- package/lighthouse-core/gather/driver/target-manager.js +1 -1
- package/lighthouse-core/gather/driver.js +1 -1
- package/lighthouse-core/gather/gatherers/console-messages.js +2 -2
- package/lighthouse-core/gather/gatherers/inspector-issues.js +34 -46
- package/lighthouse-core/gather/gatherers/js-usage.js +1 -1
- package/lighthouse-core/gather/gatherers/seo/font-size.js +1 -1
- package/lighthouse-core/gather/gatherers/trace-elements.js +8 -43
- package/lighthouse-core/index.js +4 -3
- package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
- package/lighthouse-core/lib/asset-saver.js +6 -4
- package/lighthouse-core/lib/dependency-graph/simulator/connection-pool.js +1 -1
- package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +1 -1
- package/lighthouse-core/lib/emulation.js +1 -1
- package/lighthouse-core/lib/icons.js +1 -2
- package/lighthouse-core/lib/median-run.js +1 -2
- package/lighthouse-core/lib/network-recorder.js +2 -2
- package/lighthouse-core/lib/network-request.js +1 -1
- package/lighthouse-core/lib/stack-packs.js +5 -0
- package/lighthouse-core/lib/tracehouse/cpu-profile-model.js +4 -6
- package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +1 -1
- package/lighthouse-core/lib/tracehouse/trace-processor.js +14 -17
- package/lighthouse-core/lib/url-shim.js +15 -0
- package/lighthouse-core/runner.js +62 -49
- package/lighthouse-core/scripts/download-chrome.sh +15 -2
- package/package.json +21 -25
- package/report/assets/templates.html +2 -1
- package/report/clients/standalone.js +5 -1
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/components.js +2 -2
- package/report/renderer/performance-category-renderer.js +4 -4
- package/report/renderer/report-renderer.js +1 -1
- package/report/renderer/report-ui-features.js +15 -3
- package/report/test/generator/report-generator-test.js +1 -1
- package/report/test/renderer/report-ui-features-test.js +22 -4
- package/report/test-assets/faux-psi.js +2 -3
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +40 -4
- package/shared/localization/locales/ar.json +59 -23
- package/shared/localization/locales/bg.json +40 -4
- package/shared/localization/locales/ca.json +45 -9
- package/shared/localization/locales/cs.json +39 -3
- package/shared/localization/locales/da.json +43 -7
- package/shared/localization/locales/de.json +42 -6
- package/shared/localization/locales/el.json +59 -23
- package/shared/localization/locales/en-GB.json +43 -7
- package/shared/localization/locales/en-US.json +48 -6
- package/shared/localization/locales/en-XA.json +40 -4
- package/shared/localization/locales/en-XL.json +48 -6
- package/shared/localization/locales/es-419.json +39 -3
- package/shared/localization/locales/es.json +46 -10
- package/shared/localization/locales/fi.json +57 -21
- package/shared/localization/locales/fil.json +40 -4
- package/shared/localization/locales/fr.json +45 -9
- package/shared/localization/locales/he.json +41 -5
- package/shared/localization/locales/hi.json +59 -23
- package/shared/localization/locales/hr.json +57 -21
- package/shared/localization/locales/hu.json +39 -3
- package/shared/localization/locales/id.json +40 -4
- package/shared/localization/locales/it.json +40 -4
- package/shared/localization/locales/ja.json +39 -3
- package/shared/localization/locales/ko.json +40 -4
- package/shared/localization/locales/lt.json +40 -4
- package/shared/localization/locales/lv.json +56 -20
- package/shared/localization/locales/nl.json +40 -4
- package/shared/localization/locales/no.json +56 -20
- package/shared/localization/locales/pl.json +56 -20
- package/shared/localization/locales/pt-PT.json +57 -21
- package/shared/localization/locales/pt.json +41 -5
- package/shared/localization/locales/ro.json +39 -3
- package/shared/localization/locales/ru.json +40 -4
- package/shared/localization/locales/sk.json +39 -3
- package/shared/localization/locales/sl.json +39 -3
- package/shared/localization/locales/sr-Latn.json +40 -4
- package/shared/localization/locales/sr.json +40 -4
- package/shared/localization/locales/sv.json +39 -3
- package/shared/localization/locales/ta.json +47 -11
- package/shared/localization/locales/te.json +61 -25
- package/shared/localization/locales/th.json +39 -3
- package/shared/localization/locales/tr.json +40 -4
- package/shared/localization/locales/uk.json +40 -4
- package/shared/localization/locales/vi.json +63 -27
- package/shared/localization/locales/zh-HK.json +40 -4
- package/shared/localization/locales/zh-TW.json +45 -9
- package/shared/localization/locales/zh.json +41 -5
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/snyk/snapshot.json +4 -1
- package/types/artifacts.d.ts +16 -6
- package/types/robots-parser/index.d.ts +0 -20
|
@@ -176,7 +176,7 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
176
176
|
const node = modifiedNodesById.get(nodeId);
|
|
177
177
|
if (!node) throw new Error('Impossible - node should never be undefined');
|
|
178
178
|
const timings = simulationAfterChanges.nodeTimings.get(node);
|
|
179
|
-
const endTime = timings
|
|
179
|
+
const endTime = timings?.endTime || 0;
|
|
180
180
|
maxDependencyEndTime = Math.max(maxDependencyEndTime, endTime);
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -198,7 +198,7 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
198
198
|
* @param {LH.Audit.Context} context
|
|
199
199
|
* @return {Promise<LH.Audit.Product>}
|
|
200
200
|
*/
|
|
201
|
-
static async
|
|
201
|
+
static async audit(artifacts, context) {
|
|
202
202
|
const gatherContext = artifacts.GatherContext;
|
|
203
203
|
const trace = artifacts.traces[PreloadLCPImageAudit.DEFAULT_PASS];
|
|
204
204
|
const devtoolsLog = artifacts.devtoolsLogs[PreloadLCPImageAudit.DEFAULT_PASS];
|
|
@@ -236,16 +236,6 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
236
236
|
details,
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* @return {Promise<LH.Audit.Product>}
|
|
242
|
-
*/
|
|
243
|
-
static async audit() {
|
|
244
|
-
// Preload advice is dangerous until https://bugs.chromium.org/p/chromium/issues/detail?id=788757
|
|
245
|
-
// has been fixed and validated. All preload audits are on hold until then.
|
|
246
|
-
// See https://github.com/GoogleChrome/lighthouse/issues/11960 for more discussion.
|
|
247
|
-
return {score: 1, notApplicable: true, details: Audit.makeOpportunityDetails([], [], 0)};
|
|
248
|
-
}
|
|
249
239
|
}
|
|
250
240
|
|
|
251
241
|
module.exports = PreloadLCPImageAudit;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const Audit = require('../audit.js');
|
|
9
|
-
|
|
9
|
+
// TODO(esmodules): cast can be removed when this switches to import.
|
|
10
|
+
const robotsParser = /** @type {typeof import('robots-parser').default} */ (/** @type {unknown} */(require('robots-parser'))); // eslint-disable-line max-len
|
|
10
11
|
const URL = require('../../lib/url-shim.js');
|
|
11
12
|
const MainResource = require('../../computed/main-resource.js');
|
|
12
13
|
const BLOCKLIST = new Set([
|
|
@@ -257,9 +257,8 @@ class UsesRelPreloadAudit extends Audit {
|
|
|
257
257
|
* @return {Promise<LH.Audit.Product>}
|
|
258
258
|
*/
|
|
259
259
|
static async audit() {
|
|
260
|
-
// Preload advice is
|
|
261
|
-
//
|
|
262
|
-
// See https://github.com/GoogleChrome/lighthouse/issues/11960 for more discussion.
|
|
260
|
+
// Preload advice is on hold until https://github.com/GoogleChrome/lighthouse/issues/11960
|
|
261
|
+
// is resolved.
|
|
263
262
|
return {score: 1, notApplicable: true, details: Audit.makeOpportunityDetails([], [], 0)};
|
|
264
263
|
}
|
|
265
264
|
}
|
|
@@ -93,7 +93,7 @@ class ValidSourceMaps extends Audit {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// Sources content errors.
|
|
96
|
-
if (sourceMap
|
|
96
|
+
if (sourceMap?.map) {
|
|
97
97
|
const sourcesContent = sourceMap.map.sourcesContent || [];
|
|
98
98
|
let missingSourcesContentCount = 0;
|
|
99
99
|
for (let i = 0; i < sourceMap.map.sources.length; i++) {
|
|
@@ -108,7 +108,7 @@ class ValidSourceMaps extends Audit {
|
|
|
108
108
|
if (sourceMap || errors.length) {
|
|
109
109
|
results.push({
|
|
110
110
|
scriptUrl: scriptElement.src,
|
|
111
|
-
sourceMapUrl: sourceMap
|
|
111
|
+
sourceMapUrl: sourceMap?.sourceMapUrl,
|
|
112
112
|
subItems: {
|
|
113
113
|
type: /** @type {const} */ ('subitems'),
|
|
114
114
|
items: errors,
|
|
@@ -39,7 +39,7 @@ class ImageRecords {
|
|
|
39
39
|
|
|
40
40
|
for (const element of data.ImageElements) {
|
|
41
41
|
const networkRecord = indexedNetworkRecords[element.src];
|
|
42
|
-
const mimeType = networkRecord
|
|
42
|
+
const mimeType = networkRecord?.mimeType;
|
|
43
43
|
|
|
44
44
|
// Don't change the guessed mime type if no mime type was found.
|
|
45
45
|
imageRecords.push({
|
|
@@ -100,7 +100,7 @@ class JSBundles {
|
|
|
100
100
|
const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
|
|
101
101
|
const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap);
|
|
102
102
|
|
|
103
|
-
const content = scriptElement
|
|
103
|
+
const content = scriptElement?.content ? scriptElement.content : '';
|
|
104
104
|
const sizes = computeGeneratedFileSizes(map, content);
|
|
105
105
|
|
|
106
106
|
const bundle = {
|
|
@@ -111,7 +111,7 @@ class LanternMetricArtifact {
|
|
|
111
111
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
112
112
|
const processedNavigation = await ProcessedNavigation.request(processedTrace, context);
|
|
113
113
|
const simulator = data.simulator ||
|
|
114
|
-
await LoadSimulator.request({devtoolsLog, settings}, context);
|
|
114
|
+
(await LoadSimulator.request({devtoolsLog, settings}, context));
|
|
115
115
|
|
|
116
116
|
const optimisticGraph = this.getOptimisticGraph(graph, processedNavigation);
|
|
117
117
|
const pessimisticGraph = this.getPessimisticGraph(graph, processedNavigation);
|
|
@@ -67,22 +67,22 @@ class TimingSummary {
|
|
|
67
67
|
/** @type {LH.Artifacts.TimingSummary} */
|
|
68
68
|
const metrics = {
|
|
69
69
|
// Include the simulated/observed performance metrics
|
|
70
|
-
firstContentfulPaint: firstContentfulPaint
|
|
71
|
-
firstContentfulPaintTs: firstContentfulPaint
|
|
72
|
-
firstContentfulPaintAllFrames: firstContentfulPaintAllFrames
|
|
73
|
-
firstContentfulPaintAllFramesTs: firstContentfulPaintAllFrames
|
|
74
|
-
firstMeaningfulPaint: firstMeaningfulPaint
|
|
75
|
-
firstMeaningfulPaintTs: firstMeaningfulPaint
|
|
76
|
-
largestContentfulPaint: largestContentfulPaint
|
|
77
|
-
largestContentfulPaintTs: largestContentfulPaint
|
|
78
|
-
largestContentfulPaintAllFrames: largestContentfulPaintAllFrames
|
|
79
|
-
largestContentfulPaintAllFramesTs: largestContentfulPaintAllFrames
|
|
80
|
-
interactive: interactive
|
|
81
|
-
interactiveTs: interactive
|
|
82
|
-
speedIndex: speedIndex
|
|
83
|
-
speedIndexTs: speedIndex
|
|
84
|
-
totalBlockingTime: totalBlockingTime
|
|
85
|
-
maxPotentialFID: maxPotentialFID
|
|
70
|
+
firstContentfulPaint: firstContentfulPaint?.timing,
|
|
71
|
+
firstContentfulPaintTs: firstContentfulPaint?.timestamp,
|
|
72
|
+
firstContentfulPaintAllFrames: firstContentfulPaintAllFrames?.timing,
|
|
73
|
+
firstContentfulPaintAllFramesTs: firstContentfulPaintAllFrames?.timestamp,
|
|
74
|
+
firstMeaningfulPaint: firstMeaningfulPaint?.timing,
|
|
75
|
+
firstMeaningfulPaintTs: firstMeaningfulPaint?.timestamp,
|
|
76
|
+
largestContentfulPaint: largestContentfulPaint?.timing,
|
|
77
|
+
largestContentfulPaintTs: largestContentfulPaint?.timestamp,
|
|
78
|
+
largestContentfulPaintAllFrames: largestContentfulPaintAllFrames?.timing,
|
|
79
|
+
largestContentfulPaintAllFramesTs: largestContentfulPaintAllFrames?.timestamp,
|
|
80
|
+
interactive: interactive?.timing,
|
|
81
|
+
interactiveTs: interactive?.timestamp,
|
|
82
|
+
speedIndex: speedIndex?.timing,
|
|
83
|
+
speedIndexTs: speedIndex?.timestamp,
|
|
84
|
+
totalBlockingTime: totalBlockingTime?.timing,
|
|
85
|
+
maxPotentialFID: maxPotentialFID?.timing,
|
|
86
86
|
cumulativeLayoutShift,
|
|
87
87
|
cumulativeLayoutShiftMainFrame,
|
|
88
88
|
totalCumulativeLayoutShift,
|
|
@@ -91,26 +91,26 @@ class TimingSummary {
|
|
|
91
91
|
observedTimeOrigin: processedTrace.timings.timeOrigin,
|
|
92
92
|
observedTimeOriginTs: processedTrace.timestamps.timeOrigin,
|
|
93
93
|
// For now, navigationStart is always timeOrigin.
|
|
94
|
-
observedNavigationStart: processedNavigation
|
|
95
|
-
observedNavigationStartTs: processedNavigation
|
|
96
|
-
observedFirstPaint: processedNavigation
|
|
97
|
-
observedFirstPaintTs: processedNavigation
|
|
98
|
-
observedFirstContentfulPaint: processedNavigation
|
|
99
|
-
observedFirstContentfulPaintTs: processedNavigation
|
|
100
|
-
observedFirstContentfulPaintAllFrames: processedNavigation
|
|
101
|
-
observedFirstContentfulPaintAllFramesTs: processedNavigation
|
|
102
|
-
observedFirstMeaningfulPaint: processedNavigation
|
|
103
|
-
observedFirstMeaningfulPaintTs: processedNavigation
|
|
104
|
-
observedLargestContentfulPaint: processedNavigation
|
|
105
|
-
observedLargestContentfulPaintTs: processedNavigation
|
|
106
|
-
observedLargestContentfulPaintAllFrames: processedNavigation
|
|
107
|
-
observedLargestContentfulPaintAllFramesTs: processedNavigation
|
|
94
|
+
observedNavigationStart: processedNavigation?.timings.timeOrigin,
|
|
95
|
+
observedNavigationStartTs: processedNavigation?.timestamps.timeOrigin,
|
|
96
|
+
observedFirstPaint: processedNavigation?.timings.firstPaint,
|
|
97
|
+
observedFirstPaintTs: processedNavigation?.timestamps.firstPaint,
|
|
98
|
+
observedFirstContentfulPaint: processedNavigation?.timings.firstContentfulPaint,
|
|
99
|
+
observedFirstContentfulPaintTs: processedNavigation?.timestamps.firstContentfulPaint,
|
|
100
|
+
observedFirstContentfulPaintAllFrames: processedNavigation?.timings.firstContentfulPaintAllFrames,
|
|
101
|
+
observedFirstContentfulPaintAllFramesTs: processedNavigation?.timestamps.firstContentfulPaintAllFrames,
|
|
102
|
+
observedFirstMeaningfulPaint: processedNavigation?.timings.firstMeaningfulPaint,
|
|
103
|
+
observedFirstMeaningfulPaintTs: processedNavigation?.timestamps.firstMeaningfulPaint,
|
|
104
|
+
observedLargestContentfulPaint: processedNavigation?.timings.largestContentfulPaint,
|
|
105
|
+
observedLargestContentfulPaintTs: processedNavigation?.timestamps.largestContentfulPaint,
|
|
106
|
+
observedLargestContentfulPaintAllFrames: processedNavigation?.timings.largestContentfulPaintAllFrames,
|
|
107
|
+
observedLargestContentfulPaintAllFramesTs: processedNavigation?.timestamps.largestContentfulPaintAllFrames,
|
|
108
108
|
observedTraceEnd: processedTrace.timings.traceEnd,
|
|
109
109
|
observedTraceEndTs: processedTrace.timestamps.traceEnd,
|
|
110
|
-
observedLoad: processedNavigation
|
|
111
|
-
observedLoadTs: processedNavigation
|
|
112
|
-
observedDomContentLoaded: processedNavigation
|
|
113
|
-
observedDomContentLoadedTs: processedNavigation
|
|
110
|
+
observedLoad: processedNavigation?.timings.load,
|
|
111
|
+
observedLoadTs: processedNavigation?.timestamps.load,
|
|
112
|
+
observedDomContentLoaded: processedNavigation?.timings.domContentLoaded,
|
|
113
|
+
observedDomContentLoadedTs: processedNavigation?.timestamps.domContentLoaded,
|
|
114
114
|
observedCumulativeLayoutShift: cumulativeLayoutShift,
|
|
115
115
|
observedCumulativeLayoutShiftMainFrame: cumulativeLayoutShiftMainFrame,
|
|
116
116
|
observedTotalCumulativeLayoutShift: totalCumulativeLayoutShift,
|
|
@@ -128,7 +128,7 @@ class TimingSummary {
|
|
|
128
128
|
|
|
129
129
|
/** @type {Record<string,boolean>} */
|
|
130
130
|
const debugInfo = {
|
|
131
|
-
lcpInvalidated: !!
|
|
131
|
+
lcpInvalidated: !!processedNavigation?.lcpInvalidated,
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
return {metrics, debugInfo};
|
|
@@ -205,7 +205,7 @@ class PageDependencyGraph {
|
|
|
205
205
|
networkNode.startTime <= cpuNode.startTime) return;
|
|
206
206
|
const {record} = networkNode;
|
|
207
207
|
const resourceType = record.resourceType ||
|
|
208
|
-
record.redirectDestination
|
|
208
|
+
record.redirectDestination?.resourceType;
|
|
209
209
|
if (!linkableResourceTypes.has(resourceType)) {
|
|
210
210
|
// We only link some resources to CPU nodes because we observe LCP simulation
|
|
211
211
|
// regressions when including images, etc.
|
|
@@ -405,7 +405,7 @@ class PageDependencyGraph {
|
|
|
405
405
|
|
|
406
406
|
// The root request is the earliest request in the main document redirect chain.
|
|
407
407
|
// Will be undefined if there were no redirects.
|
|
408
|
-
const rootRequest = mainDocumentNode.record.redirects
|
|
408
|
+
const rootRequest = mainDocumentNode.record.redirects?.[0];
|
|
409
409
|
|
|
410
410
|
let rootNode;
|
|
411
411
|
if (rootRequest) {
|
|
@@ -55,7 +55,7 @@ class ResourceSummary {
|
|
|
55
55
|
const budget = Budget.getMatchingBudget(budgets, mainResourceURL);
|
|
56
56
|
/** @type {ReadonlyArray<string>} */
|
|
57
57
|
let firstPartyHosts = [];
|
|
58
|
-
if (budget
|
|
58
|
+
if (budget?.options?.firstPartyHostnames) {
|
|
59
59
|
firstPartyHosts = budget.options.firstPartyHostnames;
|
|
60
60
|
} else {
|
|
61
61
|
const rootDomain = Util.getRootDomain(mainResourceURL);
|
|
@@ -295,7 +295,7 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
|
|
|
295
295
|
// however `lookupLocale` will always determine which of our supported locales to use (falling
|
|
296
296
|
// back if necessary).
|
|
297
297
|
// TODO: could do more work to sniff out the user's locale
|
|
298
|
-
const locale = i18n.lookupLocale(
|
|
298
|
+
const locale = i18n.lookupLocale(overrides?.locale || settingsJson.locale);
|
|
299
299
|
|
|
300
300
|
// Fill in missing settings with defaults
|
|
301
301
|
const {defaultSettings} = constants;
|
|
@@ -332,7 +332,7 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
|
|
|
332
332
|
*/
|
|
333
333
|
function mergePlugins(configJSON, configDir, flags) {
|
|
334
334
|
const configPlugins = configJSON.plugins || [];
|
|
335
|
-
const flagPlugins =
|
|
335
|
+
const flagPlugins = flags?.plugins || [];
|
|
336
336
|
const pluginNames = new Set([...configPlugins, ...flagPlugins]);
|
|
337
337
|
|
|
338
338
|
for (const pluginName of pluginNames) {
|
|
@@ -161,7 +161,7 @@ class Config {
|
|
|
161
161
|
constructor(configJSON, flags) {
|
|
162
162
|
const status = {msg: 'Create config', id: 'lh:init:config'};
|
|
163
163
|
log.time(status, 'verbose');
|
|
164
|
-
let configPath = flags
|
|
164
|
+
let configPath = flags?.configPath;
|
|
165
165
|
|
|
166
166
|
if (!configJSON) {
|
|
167
167
|
configJSON = defaultConfig;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const log = require('lighthouse-logger');
|
|
9
|
+
|
|
8
10
|
const Audit = require('../../audits/audit.js');
|
|
9
11
|
|
|
10
12
|
/** @type {Record<keyof LH.FRBaseArtifacts, string>} */
|
|
@@ -45,7 +47,7 @@ function getAuditIdsInCategories(allCategories, onlyCategories) {
|
|
|
45
47
|
|
|
46
48
|
onlyCategories = onlyCategories || Object.keys(allCategories);
|
|
47
49
|
const categories = onlyCategories.map(categoryId => allCategories[categoryId]);
|
|
48
|
-
const auditRefs = categories.flatMap(category => category
|
|
50
|
+
const auditRefs = categories.flatMap(category => category?.auditRefs || []);
|
|
49
51
|
return new Set(auditRefs.map(auditRef => auditRef.id));
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -194,6 +196,24 @@ function filterCategoriesByExplicitFilters(categories, onlyCategories) {
|
|
|
194
196
|
return Object.fromEntries(categoriesToKeep);
|
|
195
197
|
}
|
|
196
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Logs a warning if any specified onlyCategory is not a known category that can
|
|
201
|
+
* be included.
|
|
202
|
+
*
|
|
203
|
+
* @param {LH.Config.Config['categories']} allCategories
|
|
204
|
+
* @param {string[] | null} onlyCategories
|
|
205
|
+
* @return {void}
|
|
206
|
+
*/
|
|
207
|
+
function warnOnUnknownOnlyCategories(allCategories, onlyCategories) {
|
|
208
|
+
if (!onlyCategories) return;
|
|
209
|
+
|
|
210
|
+
for (const onlyCategoryId of onlyCategories) {
|
|
211
|
+
if (!allCategories?.[onlyCategoryId]) {
|
|
212
|
+
log.warn('config', `unrecognized category in 'onlyCategories': ${onlyCategoryId}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
197
217
|
/**
|
|
198
218
|
* Filters a categories object and their auditRefs down to the set that can be computed using
|
|
199
219
|
* only the specified audits.
|
|
@@ -266,6 +286,8 @@ function filterConfigByGatherMode(config, mode) {
|
|
|
266
286
|
function filterConfigByExplicitFilters(config, filters) {
|
|
267
287
|
const {onlyAudits, onlyCategories, skipAudits} = filters;
|
|
268
288
|
|
|
289
|
+
warnOnUnknownOnlyCategories(config.categories, onlyCategories);
|
|
290
|
+
|
|
269
291
|
let baseAuditIds = getAuditIdsInCategories(config.categories, undefined);
|
|
270
292
|
if (onlyCategories) {
|
|
271
293
|
baseAuditIds = getAuditIdsInCategories(config.categories, onlyCategories);
|
|
@@ -49,7 +49,7 @@ function assertValidPluginName(configJSON, pluginName) {
|
|
|
49
49
|
throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (configJSON.categories
|
|
52
|
+
if (configJSON.categories?.[pluginName]) {
|
|
53
53
|
throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -123,7 +123,7 @@ function assertValidFRNavigations(navigationsDefn) {
|
|
|
123
123
|
function assertValidAudit(auditDefinition) {
|
|
124
124
|
const {implementation, path: auditPath} = auditDefinition;
|
|
125
125
|
const auditName = auditPath ||
|
|
126
|
-
|
|
126
|
+
implementation?.meta?.id ||
|
|
127
127
|
'Unknown audit';
|
|
128
128
|
|
|
129
129
|
if (typeof implementation.audit !== 'function' || implementation.audit === Audit.audit) {
|
|
@@ -22,6 +22,7 @@ const {initializeConfig} = require('../config/config.js');
|
|
|
22
22
|
const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
|
|
23
23
|
const format = require('../../../shared/localization/format.js');
|
|
24
24
|
const LighthouseError = require('../../lib/lh-error.js');
|
|
25
|
+
const URL = require('../../lib/url-shim.js');
|
|
25
26
|
const {getPageLoadError} = require('../../lib/navigation-error.js');
|
|
26
27
|
const Trace = require('../../gather/gatherers/trace.js');
|
|
27
28
|
const DevtoolsLog = require('../../gather/gatherers/devtools-log.js');
|
|
@@ -136,11 +137,11 @@ async function _collectDebugData(navigationContext, phaseState) {
|
|
|
136
137
|
await collectPhaseArtifacts({...phaseState, phase: 'getArtifact', artifactDefinitions});
|
|
137
138
|
const getArtifactState = phaseState.artifactState.getArtifact;
|
|
138
139
|
|
|
139
|
-
const devtoolsLogArtifactId = devtoolsLogArtifactDefn
|
|
140
|
+
const devtoolsLogArtifactId = devtoolsLogArtifactDefn?.id;
|
|
140
141
|
const devtoolsLog = devtoolsLogArtifactId && await getArtifactState[devtoolsLogArtifactId];
|
|
141
142
|
const records = devtoolsLog && await NetworkRecords.request(devtoolsLog, navigationContext);
|
|
142
143
|
|
|
143
|
-
const traceArtifactId = traceArtifactDefn
|
|
144
|
+
const traceArtifactId = traceArtifactDefn?.id;
|
|
144
145
|
const trace = traceArtifactId && await getArtifactState[traceArtifactId];
|
|
145
146
|
|
|
146
147
|
return {devtoolsLog, records, trace};
|
|
@@ -279,7 +280,7 @@ async function _cleanup({requestedUrl, driver, config}) {
|
|
|
279
280
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
280
281
|
*/
|
|
281
282
|
async function navigation(options) {
|
|
282
|
-
const {url
|
|
283
|
+
const {url, page, configContext = {}} = options;
|
|
283
284
|
const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
|
|
284
285
|
const computedCache = new Map();
|
|
285
286
|
const internalOptions = {
|
|
@@ -289,6 +290,7 @@ async function navigation(options) {
|
|
|
289
290
|
return Runner.run(
|
|
290
291
|
async () => {
|
|
291
292
|
const driver = new Driver(page);
|
|
293
|
+
const requestedUrl = URL.normalizeUrl(url);
|
|
292
294
|
const context = {driver, config, requestedUrl, options: internalOptions};
|
|
293
295
|
const {baseArtifacts} = await _setup(context);
|
|
294
296
|
const {artifacts} = await _navigations({...context, baseArtifacts, computedCache});
|
|
@@ -297,7 +299,6 @@ async function navigation(options) {
|
|
|
297
299
|
return finalizeArtifacts(baseArtifacts, artifacts);
|
|
298
300
|
},
|
|
299
301
|
{
|
|
300
|
-
url: requestedUrl,
|
|
301
302
|
config,
|
|
302
303
|
computedCache: new Map(),
|
|
303
304
|
}
|
|
@@ -23,7 +23,7 @@ class UserFlow {
|
|
|
23
23
|
/** @type {FrOptions} */
|
|
24
24
|
this.options = {page, ...options};
|
|
25
25
|
/** @type {string|undefined} */
|
|
26
|
-
this.name = options
|
|
26
|
+
this.name = options?.name;
|
|
27
27
|
/** @type {LH.FlowResult.Step[]} */
|
|
28
28
|
this.steps = [];
|
|
29
29
|
}
|
|
@@ -90,7 +90,7 @@ class UserFlow {
|
|
|
90
90
|
const result = await navigation(this._getNextNavigationOptions(url, stepOptions));
|
|
91
91
|
if (!result) throw Error('Navigation returned undefined');
|
|
92
92
|
|
|
93
|
-
const providedName = stepOptions
|
|
93
|
+
const providedName = stepOptions?.stepName;
|
|
94
94
|
this.steps.push({
|
|
95
95
|
lhr: result.lhr,
|
|
96
96
|
name: providedName || this._getDefaultStepName(result.lhr),
|
|
@@ -118,7 +118,7 @@ class UserFlow {
|
|
|
118
118
|
this.currentTimespan = undefined;
|
|
119
119
|
if (!result) throw Error('Timespan returned undefined');
|
|
120
120
|
|
|
121
|
-
const providedName = options
|
|
121
|
+
const providedName = options?.stepName;
|
|
122
122
|
this.steps.push({
|
|
123
123
|
lhr: result.lhr,
|
|
124
124
|
name: providedName || this._getDefaultStepName(result.lhr),
|
|
@@ -137,7 +137,7 @@ class UserFlow {
|
|
|
137
137
|
const result = await snapshot(options);
|
|
138
138
|
if (!result) throw Error('Snapshot returned undefined');
|
|
139
139
|
|
|
140
|
-
const providedName = stepOptions
|
|
140
|
+
const providedName = stepOptions?.stepName;
|
|
141
141
|
this.steps.push({
|
|
142
142
|
lhr: result.lhr,
|
|
143
143
|
name: providedName || this._getDefaultStepName(result.lhr),
|
|
@@ -148,7 +148,7 @@ class Connection {
|
|
|
148
148
|
} else {
|
|
149
149
|
// In DevTools we receive responses to commands we did not send which we cannot act on, so we
|
|
150
150
|
// just log these occurrences.
|
|
151
|
-
const error = object.error
|
|
151
|
+
const error = object.error?.message;
|
|
152
152
|
log.formatProtocol(`disowned method <= browser ${error ? 'ERR' : 'OK'}`,
|
|
153
153
|
{method: 'UNKNOWN', params: error || object.result}, 'verbose');
|
|
154
154
|
}
|
|
@@ -121,7 +121,7 @@ class ExecutionContext {
|
|
|
121
121
|
new Error('Runtime.evaluate response did not contain a "result" object'));
|
|
122
122
|
}
|
|
123
123
|
const value = response.result.value;
|
|
124
|
-
if (value
|
|
124
|
+
if (value?.__failedInBrowser) {
|
|
125
125
|
return Promise.reject(Object.assign(new Error(), value));
|
|
126
126
|
} else {
|
|
127
127
|
return value;
|
|
@@ -144,7 +144,7 @@ class NetworkMonitor {
|
|
|
144
144
|
const finalNavigation = mainFrameNavigations[mainFrameNavigations.length - 1];
|
|
145
145
|
if (!finalNavigation) log.warn('NetworkMonitor', 'No detected navigations');
|
|
146
146
|
|
|
147
|
-
return finalNavigation
|
|
147
|
+
return finalNavigation?.url;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
@@ -172,7 +172,7 @@ class NetworkMonitor {
|
|
|
172
172
|
if (!this._networkRecorder) return false;
|
|
173
173
|
const requests = this._networkRecorder.getRawRecords();
|
|
174
174
|
const rootFrameRequest = requests.find(r => r.resourceType === 'Document');
|
|
175
|
-
const rootFrameId = rootFrameRequest
|
|
175
|
+
const rootFrameId = rootFrameRequest?.frameId;
|
|
176
176
|
|
|
177
177
|
return this._isActiveIdlePeriod(
|
|
178
178
|
0,
|
|
@@ -49,7 +49,7 @@ class TargetManager {
|
|
|
49
49
|
async _onSessionAttached(session) {
|
|
50
50
|
try {
|
|
51
51
|
const target = await session.sendCommand('Target.getTargetInfo').catch(() => null);
|
|
52
|
-
const targetType = target
|
|
52
|
+
const targetType = target?.targetInfo?.type;
|
|
53
53
|
const hasValidTargetType = targetType === 'page' || targetType === 'iframe';
|
|
54
54
|
if (!target || !hasValidTargetType) return;
|
|
55
55
|
|
|
@@ -425,7 +425,7 @@ class Driver {
|
|
|
425
425
|
* @return {Promise<void>}
|
|
426
426
|
*/
|
|
427
427
|
async beginTrace(settings) {
|
|
428
|
-
const additionalCategories = (settings
|
|
428
|
+
const additionalCategories = (settings?.additionalTraceCategories &&
|
|
429
429
|
settings.additionalTraceCategories.split(',')) || [];
|
|
430
430
|
const traceCategories = TraceGatherer.getDefaultTraceCategories().concat(additionalCategories);
|
|
431
431
|
|
|
@@ -66,7 +66,7 @@ class ConsoleMessages extends FRGatherer {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const {url, lineNumber, columnNumber} =
|
|
69
|
-
event.stackTrace
|
|
69
|
+
event.stackTrace?.callFrames[0] || {};
|
|
70
70
|
/** @type {LH.Artifacts.ConsoleMessage} */
|
|
71
71
|
const consoleMessage = {
|
|
72
72
|
eventType: 'consoleAPI',
|
|
@@ -117,7 +117,7 @@ class ConsoleMessages extends FRGatherer {
|
|
|
117
117
|
|
|
118
118
|
// JS events have a stack trace, which we use to get the column.
|
|
119
119
|
// CSS/HTML events only expose a line number.
|
|
120
|
-
const {columnNumber} = event.entry.stackTrace
|
|
120
|
+
const {columnNumber} = event.entry.stackTrace?.callFrames[0] || {};
|
|
121
121
|
|
|
122
122
|
this._logEntries.push({
|
|
123
123
|
eventType: 'protocolLog',
|
|
@@ -58,58 +58,46 @@ class InspectorIssues extends FRGatherer {
|
|
|
58
58
|
* @return {Promise<LH.Artifacts['InspectorIssues']>}
|
|
59
59
|
*/
|
|
60
60
|
async _getArtifact(networkRecords) {
|
|
61
|
+
/** @type {LH.Artifacts.InspectorIssues} */
|
|
61
62
|
const artifact = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
attributionReportingIssue: [],
|
|
64
|
+
blockedByResponseIssue: [],
|
|
65
|
+
clientHintIssue: [],
|
|
66
|
+
contentSecurityPolicyIssue: [],
|
|
67
|
+
corsIssue: [],
|
|
68
|
+
deprecationIssue: [],
|
|
69
|
+
federatedAuthRequestIssue: [],
|
|
70
|
+
genericIssue: [],
|
|
71
|
+
heavyAdIssue: [],
|
|
72
|
+
lowTextContrastIssue: [],
|
|
73
|
+
mixedContentIssue: [],
|
|
74
|
+
navigatorUserAgentIssue: [],
|
|
75
|
+
quirksModeIssue: [],
|
|
76
|
+
sameSiteCookieIssue: [],
|
|
77
|
+
sharedArrayBufferIssue: [],
|
|
78
|
+
twaQualityEnforcement: [],
|
|
74
79
|
};
|
|
75
|
-
|
|
76
|
-
for (const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
artifact.mixedContent.push(issueDetails);
|
|
80
|
+
const keys = /** @type {Array<keyof LH.Artifacts['InspectorIssues']>} */(Object.keys(artifact));
|
|
81
|
+
for (const key of keys) {
|
|
82
|
+
/** @type {`${key}Details`} */
|
|
83
|
+
const detailsKey = `${key}Details`;
|
|
84
|
+
const allDetails = this._issues.map(issue => issue.details[detailsKey]);
|
|
85
|
+
for (const detail of allDetails) {
|
|
86
|
+
if (!detail) {
|
|
87
|
+
continue;
|
|
84
88
|
}
|
|
85
|
-
}
|
|
86
|
-
if (issue.details.sameSiteCookieIssueDetails) {
|
|
87
|
-
const issueDetails = issue.details.sameSiteCookieIssueDetails;
|
|
88
|
-
const issueReqId = issueDetails.request && issueDetails.request.requestId;
|
|
89
89
|
// Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const requestId = 'request' in detail && detail.request && detail.request.requestId;
|
|
91
|
+
if (requestId) {
|
|
92
|
+
if (networkRecords.find(req => req.requestId === requestId)) {
|
|
93
|
+
// @ts-expect-error - detail types are not all compatible
|
|
94
|
+
artifact[key].push(detail);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
// @ts-expect-error - detail types are not all compatible
|
|
98
|
+
artifact[key].push(detail);
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
|
-
if (issue.details.blockedByResponseIssueDetails) {
|
|
96
|
-
const issueDetails = issue.details.blockedByResponseIssueDetails;
|
|
97
|
-
const issueReqId = issueDetails.request && issueDetails.request.requestId;
|
|
98
|
-
// Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
|
|
99
|
-
if (issueReqId &&
|
|
100
|
-
networkRecords.find(req => req.requestId === issueReqId)) {
|
|
101
|
-
artifact.blockedByResponse.push(issueDetails);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (issue.details.heavyAdIssueDetails) {
|
|
105
|
-
artifact.heavyAds.push(issue.details.heavyAdIssueDetails);
|
|
106
|
-
}
|
|
107
|
-
if (issue.details.contentSecurityPolicyIssueDetails) {
|
|
108
|
-
artifact.contentSecurityPolicy.push(issue.details.contentSecurityPolicyIssueDetails);
|
|
109
|
-
}
|
|
110
|
-
if (issue.details.deprecationIssueDetails) {
|
|
111
|
-
artifact.deprecations.push(issue.details.deprecationIssueDetails);
|
|
112
|
-
}
|
|
113
101
|
}
|
|
114
102
|
|
|
115
103
|
return artifact;
|
|
@@ -116,7 +116,7 @@ class JsUsage extends FRGatherer {
|
|
|
116
116
|
let url = scriptUsage.url;
|
|
117
117
|
const scriptParsedEvent =
|
|
118
118
|
this._scriptParsedEvents.find(e => e.scriptId === scriptUsage.scriptId);
|
|
119
|
-
if (scriptParsedEvent
|
|
119
|
+
if (scriptParsedEvent?.embedderName) {
|
|
120
120
|
url = scriptParsedEvent.embedderName;
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -355,7 +355,7 @@ class FontSize extends FRGatherer {
|
|
|
355
355
|
// For the nodes whose computed style we could attribute to a stylesheet, assign
|
|
356
356
|
// the stylsheet to the data.
|
|
357
357
|
analyzedFailingNodesData
|
|
358
|
-
.filter(data => data.cssRule
|
|
358
|
+
.filter(data => data.cssRule?.styleSheetId)
|
|
359
359
|
// @ts-expect-error - guaranteed to exist from the filter immediately above
|
|
360
360
|
.forEach(data => (data.cssRule.stylesheet = stylesheets.get(data.cssRule.styleSheetId)));
|
|
361
361
|
|