lighthouse 9.5.0 → 9.6.3
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/dist/report/bundle.esm.js +67 -384
- package/dist/report/flow.js +20 -25
- package/dist/report/standalone.js +18 -23
- package/lighthouse-cli/test/smokehouse/core-tests.js +2 -0
- package/lighthouse-cli/test/smokehouse/frontends/lib.js +1 -3
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +1 -3
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -0
- package/lighthouse-cli/test/smokehouse/readme.md +5 -4
- package/lighthouse-cli/test/smokehouse/report-assert.js +16 -13
- package/lighthouse-cli/test/smokehouse/version-check-test.js +46 -0
- package/lighthouse-cli/test/smokehouse/version-check.js +49 -0
- package/lighthouse-core/audits/bootup-time.js +2 -70
- package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +3 -5
- package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +2 -5
- package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +4 -2
- package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +3 -1
- package/lighthouse-core/audits/deprecations.js +603 -28
- package/lighthouse-core/audits/installable-manifest.js +27 -6
- package/lighthouse-core/audits/long-tasks.js +3 -3
- package/lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js +88 -0
- package/lighthouse-core/audits/network-requests.js +79 -62
- package/lighthouse-core/audits/preload-lcp-image.js +33 -7
- package/lighthouse-core/audits/third-party-summary.js +3 -3
- package/lighthouse-core/audits/work-during-interaction.js +280 -0
- package/lighthouse-core/computed/metrics/responsiveness.js +157 -0
- package/lighthouse-core/config/config-helpers.js +1 -1
- package/lighthouse-core/config/metrics-to-audits.js +5 -0
- package/lighthouse-core/fraggle-rock/config/default-config.js +9 -1
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +2 -2
- package/lighthouse-core/fraggle-rock/gather/session.js +8 -2
- package/lighthouse-core/gather/driver.js +21 -14
- package/lighthouse-core/gather/gather-runner.js +2 -4
- package/lighthouse-core/gather/gatherers/css-usage.js +59 -34
- package/lighthouse-core/gather/gatherers/trace-elements.js +21 -2
- package/lighthouse-core/lib/arbitrary-equality-map.js +2 -2
- package/lighthouse-core/lib/dependency-graph/network-node.js +1 -1
- package/lighthouse-core/lib/i18n/i18n.js +2 -0
- package/lighthouse-core/lib/minify-trace.js +2 -0
- package/lighthouse-core/lib/script-helpers.js +24 -0
- package/lighthouse-core/lib/stack-packs.js +8 -5
- package/lighthouse-core/lib/tracehouse/task-groups.js +1 -0
- package/lighthouse-core/lib/tracehouse/task-summary.js +87 -0
- package/lighthouse-core/lib/tracehouse/trace-processor.js +39 -14
- package/lighthouse-core/runner.js +1 -1
- package/lighthouse-core/util-commonjs.js +20 -18
- package/package.json +10 -8
- package/report/assets/styles.css +5 -2
- package/report/clients/bundle.js +1 -0
- package/report/renderer/components.js +1 -1
- package/report/renderer/details-renderer.js +4 -4
- package/report/renderer/performance-category-renderer.js +15 -11
- package/report/renderer/util.js +20 -18
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +6 -98
- package/report/test/renderer/details-renderer-test.js +9 -9
- package/report/test/renderer/performance-category-renderer-test.js +31 -0
- package/report/test/renderer/report-renderer-axe-test.js +3 -15
- package/report/test-assets/faux-psi-template.html +3 -2
- package/report/test-assets/faux-psi.js +22 -1
- package/shared/localization/locales/en-US.json +244 -4
- package/shared/localization/locales/en-XL.json +244 -4
- package/shared/localization/swap-locale.js +2 -1
- package/third-party/chromium-synchronization/installability-errors-test.js +1 -0
- package/third-party/snyk/snapshot.json +2 -3
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +40 -1
- package/types/lhr/audit-details.d.ts +3 -1
- package/types/smokehouse.d.ts +1 -1
- package/changelog.md +0 -5902
- package/lighthouse-core/scripts/package.json +0 -4
|
@@ -97,6 +97,11 @@ const UIStrings = {
|
|
|
97
97
|
'protocol-timeout': `Lighthouse could not determine if there was a service worker. Please try with a newer version of Chrome.`,
|
|
98
98
|
/** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
|
|
99
99
|
'pipeline-restarted': 'PWA has been uninstalled and installability checks resetting.',
|
|
100
|
+
/**
|
|
101
|
+
* @description Error message explaining that the URL of the manifest uses a scheme that is not supported on Android.
|
|
102
|
+
* @example {data:} scheme
|
|
103
|
+
*/
|
|
104
|
+
'scheme-not-supported-for-webapk': 'The manifest URL scheme ({scheme}) is not supported on Android.',
|
|
100
105
|
};
|
|
101
106
|
/* eslint-enable max-len */
|
|
102
107
|
|
|
@@ -125,7 +130,7 @@ class InstallableManifest extends Audit {
|
|
|
125
130
|
failureTitle: str_(UIStrings.failureTitle),
|
|
126
131
|
description: str_(UIStrings.description),
|
|
127
132
|
supportedModes: ['navigation'],
|
|
128
|
-
requiredArtifacts: ['
|
|
133
|
+
requiredArtifacts: ['WebAppManifest', 'InstallabilityErrors'],
|
|
129
134
|
};
|
|
130
135
|
}
|
|
131
136
|
|
|
@@ -156,6 +161,17 @@ class InstallableManifest extends Audit {
|
|
|
156
161
|
// @ts-expect-error errorIds from protocol should match up against the strings dict
|
|
157
162
|
const matchingString = UIStrings[err.errorId];
|
|
158
163
|
|
|
164
|
+
if (err.errorId === 'scheme-not-supported-for-webapk') {
|
|
165
|
+
// If there was no manifest, then there will be at lest one other installability error.
|
|
166
|
+
// We can ignore this error if that's the case.
|
|
167
|
+
const manifestUrl = artifacts.WebAppManifest?.url;
|
|
168
|
+
if (!manifestUrl) continue;
|
|
169
|
+
|
|
170
|
+
const scheme = new URL(manifestUrl).protocol;
|
|
171
|
+
i18nErrors.push(str_(matchingString, {scheme}));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
159
175
|
// Handle an errorId we don't recognize.
|
|
160
176
|
if (matchingString === undefined) {
|
|
161
177
|
i18nErrors.push(str_(UIStrings.noErrorId, {errorId: err.errorId}));
|
|
@@ -195,7 +211,6 @@ class InstallableManifest extends Audit {
|
|
|
195
211
|
*
|
|
196
212
|
*/
|
|
197
213
|
static async audit(artifacts, context) {
|
|
198
|
-
const manifestValues = await ManifestValues.request(artifacts, context);
|
|
199
214
|
const {i18nErrors, warnings} = InstallableManifest.getInstallabilityErrors(artifacts);
|
|
200
215
|
|
|
201
216
|
const manifestUrl = artifacts.WebAppManifest ? artifacts.WebAppManifest.url : null;
|
|
@@ -210,10 +225,16 @@ class InstallableManifest extends Audit {
|
|
|
210
225
|
const errorReasons = i18nErrors.map(reason => {
|
|
211
226
|
return {reason};
|
|
212
227
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
228
|
+
|
|
229
|
+
// If InstallabilityErrors is empty, double check ManifestValues to make sure nothing was missed.
|
|
230
|
+
// InstallabilityErrors can be empty erroneously in our DevTools web tests.
|
|
231
|
+
if (!errorReasons.length) {
|
|
232
|
+
const manifestValues = await ManifestValues.request(artifacts, context);
|
|
233
|
+
if (manifestValues.isParseFailure) {
|
|
234
|
+
errorReasons.push({
|
|
235
|
+
reason: manifestValues.parseFailureReason,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
217
238
|
}
|
|
218
239
|
|
|
219
240
|
// Include the detailed pass/fail checklist as a diagnostic.
|
|
@@ -9,9 +9,9 @@ const Audit = require('./audit.js');
|
|
|
9
9
|
const NetworkRecords = require('../computed/network-records.js');
|
|
10
10
|
const i18n = require('../lib/i18n/i18n.js');
|
|
11
11
|
const MainThreadTasks = require('../computed/main-thread-tasks.js');
|
|
12
|
-
const BootupTime = require('./bootup-time.js');
|
|
13
12
|
const PageDependencyGraph = require('../computed/page-dependency-graph.js');
|
|
14
13
|
const LoadSimulator = require('../computed/load-simulator.js');
|
|
14
|
+
const {getJavaScriptURLs, getAttributableURLForTask} = require('../lib/tracehouse/task-summary.js');
|
|
15
15
|
|
|
16
16
|
/** We don't always have timing data for short tasks, if we're missing timing data. Treat it as though it were 0ms. */
|
|
17
17
|
const DEFAULT_TIMING = {startTime: 0, endTime: 0, duration: 0};
|
|
@@ -77,7 +77,7 @@ class LongTasks extends Audit {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const jsURLs =
|
|
80
|
+
const jsURLs = getJavaScriptURLs(networkRecords);
|
|
81
81
|
// Only consider up to 20 long, top-level (no parent) tasks that have an explicit endTime
|
|
82
82
|
const longtasks = tasks
|
|
83
83
|
.map(t => {
|
|
@@ -90,7 +90,7 @@ class LongTasks extends Audit {
|
|
|
90
90
|
|
|
91
91
|
// TODO(beytoven): Add start time that matches with the simulated throttling
|
|
92
92
|
const results = longtasks.map(task => ({
|
|
93
|
-
url:
|
|
93
|
+
url: getAttributableURLForTask(task, jsURLs),
|
|
94
94
|
duration: task.duration,
|
|
95
95
|
startTime: task.startTime,
|
|
96
96
|
}));
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const Audit = require('../audit.js');
|
|
9
|
+
const ComputedResponsivenes = require('../../computed/metrics/responsiveness.js');
|
|
10
|
+
const i18n = require('../../lib/i18n/i18n.js');
|
|
11
|
+
|
|
12
|
+
const UIStrings = {
|
|
13
|
+
/** Description of the Interaction to Next Paint metric. This description is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
|
|
14
|
+
description: 'Interaction to Next Paint measures page responsiveness, how long it ' +
|
|
15
|
+
'takes the page to visibly respond to user input. [Learn more](https://web.dev/inp/).',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @fileoverview This metric gives a high-percentile measure of responsiveness to input.
|
|
22
|
+
*/
|
|
23
|
+
class ExperimentalInteractionToNextPaint extends Audit {
|
|
24
|
+
/**
|
|
25
|
+
* @return {LH.Audit.Meta}
|
|
26
|
+
*/
|
|
27
|
+
static get meta() {
|
|
28
|
+
return {
|
|
29
|
+
id: 'experimental-interaction-to-next-paint',
|
|
30
|
+
title: str_(i18n.UIStrings.interactionToNextPaint),
|
|
31
|
+
description: str_(UIStrings.description),
|
|
32
|
+
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
33
|
+
supportedModes: ['timespan'],
|
|
34
|
+
requiredArtifacts: ['traces'],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @return {LH.Audit.ScoreOptions}
|
|
40
|
+
*/
|
|
41
|
+
static get defaultOptions() {
|
|
42
|
+
return {
|
|
43
|
+
// https://web.dev/inp/
|
|
44
|
+
// This is using the same threshold as field tools since only supported in
|
|
45
|
+
// unsimulated user flows for now.
|
|
46
|
+
// see https://www.desmos.com/calculator/4xtrhg51th
|
|
47
|
+
p10: 200,
|
|
48
|
+
median: 500,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {LH.Artifacts} artifacts
|
|
54
|
+
* @param {LH.Audit.Context} context
|
|
55
|
+
* @return {Promise<LH.Audit.Product>}
|
|
56
|
+
*/
|
|
57
|
+
static async audit(artifacts, context) {
|
|
58
|
+
const {settings} = context;
|
|
59
|
+
// TODO: responsiveness isn't yet supported by lantern.
|
|
60
|
+
if (settings.throttlingMethod === 'simulate') {
|
|
61
|
+
return {score: null, notApplicable: true};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
65
|
+
const metricData = {trace, settings};
|
|
66
|
+
const interactionEvent = await ComputedResponsivenes.request(metricData, context);
|
|
67
|
+
|
|
68
|
+
// TODO: include the no-interaction state in the report instead of using n/a.
|
|
69
|
+
if (interactionEvent === null) {
|
|
70
|
+
return {score: null, notApplicable: true};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// TODO: remove workaround once 103.0.5052.0 is sufficiently released.
|
|
74
|
+
const timing = interactionEvent.name === 'FallbackTiming' ?
|
|
75
|
+
interactionEvent.duration : interactionEvent.args.data.duration;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
score: Audit.computeLogNormalScore({p10: context.options.p10, median: context.options.median},
|
|
79
|
+
timing),
|
|
80
|
+
numericValue: timing,
|
|
81
|
+
numericUnit: 'millisecond',
|
|
82
|
+
displayValue: str_(i18n.UIStrings.ms, {timeInMs: timing}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = ExperimentalInteractionToNextPaint;
|
|
88
|
+
module.exports.UIStrings = UIStrings;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const Audit = require('./audit.js');
|
|
9
9
|
const URL = require('../lib/url-shim.js');
|
|
10
10
|
const NetworkRecords = require('../computed/network-records.js');
|
|
11
|
+
const MainResource = require('../computed/main-resource.js');
|
|
11
12
|
|
|
12
13
|
class NetworkRequests extends Audit {
|
|
13
14
|
/**
|
|
@@ -19,7 +20,7 @@ class NetworkRequests extends Audit {
|
|
|
19
20
|
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
20
21
|
title: 'Network Requests',
|
|
21
22
|
description: 'Lists the network requests that were made during page load.',
|
|
22
|
-
requiredArtifacts: ['devtoolsLogs'],
|
|
23
|
+
requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -28,75 +29,91 @@ class NetworkRequests extends Audit {
|
|
|
28
29
|
* @param {LH.Audit.Context} context
|
|
29
30
|
* @return {Promise<LH.Audit.Product>}
|
|
30
31
|
*/
|
|
31
|
-
static audit(artifacts, context) {
|
|
32
|
+
static async audit(artifacts, context) {
|
|
32
33
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const records = await NetworkRecords.request(devtoolsLog, context);
|
|
35
|
+
const earliestStartTime = records.reduce(
|
|
36
|
+
(min, record) => Math.min(min, record.startTime),
|
|
37
|
+
Infinity
|
|
38
|
+
);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Optional mainFrameId check because the main resource is only available for
|
|
41
|
+
// navigations. TODO: https://github.com/GoogleChrome/lighthouse/issues/14157
|
|
42
|
+
// for the general solution to this.
|
|
43
|
+
/** @type {string|undefined} */
|
|
44
|
+
let mainFrameId;
|
|
45
|
+
if (artifacts.GatherContext.gatherMode === 'navigation') {
|
|
46
|
+
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
|
|
47
|
+
mainFrameId = mainResource.frameId;
|
|
48
|
+
}
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const requestMs = record.lrStatistics?.requestMs;
|
|
47
|
-
const responseMs = record.lrStatistics?.responseMs;
|
|
50
|
+
/** @param {number} time */
|
|
51
|
+
const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
|
|
52
|
+
undefined : (time - earliestStartTime) * 1000;
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
resourceType: record.resourceType,
|
|
60
|
-
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
61
|
-
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
62
|
-
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
63
|
-
lrResponseMs: responseMs, // Only exists on Lightrider runs
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
|
|
68
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
69
|
-
const headings = [
|
|
70
|
-
{key: 'url', itemType: 'url', text: 'URL'},
|
|
71
|
-
{key: 'protocol', itemType: 'text', text: 'Protocol'},
|
|
72
|
-
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
|
|
73
|
-
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
|
|
74
|
-
{
|
|
75
|
-
key: 'transferSize',
|
|
76
|
-
itemType: 'bytes',
|
|
77
|
-
displayUnit: 'kb',
|
|
78
|
-
granularity: 1,
|
|
79
|
-
text: 'Transfer Size',
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: 'resourceSize',
|
|
83
|
-
itemType: 'bytes',
|
|
84
|
-
displayUnit: 'kb',
|
|
85
|
-
granularity: 1,
|
|
86
|
-
text: 'Resource Size',
|
|
87
|
-
},
|
|
88
|
-
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
|
|
89
|
-
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
|
|
90
|
-
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
const tableDetails = Audit.makeTableDetails(headings, results);
|
|
54
|
+
const results = records.map(record => {
|
|
55
|
+
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
|
|
56
|
+
const TCPMs = record.lrStatistics?.TCPMs;
|
|
57
|
+
const requestMs = record.lrStatistics?.requestMs;
|
|
58
|
+
const responseMs = record.lrStatistics?.responseMs;
|
|
59
|
+
// Default these to undefined so omitted from JSON in the negative case.
|
|
60
|
+
const isLinkPreload = record.isLinkPreload || undefined;
|
|
61
|
+
const experimentalFromMainFrame = mainFrameId ?
|
|
62
|
+
((record.frameId === mainFrameId) || undefined) :
|
|
63
|
+
undefined;
|
|
94
64
|
|
|
95
65
|
return {
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
url: URL.elideDataURI(record.url),
|
|
67
|
+
protocol: record.protocol,
|
|
68
|
+
startTime: timeToMs(record.startTime),
|
|
69
|
+
endTime: timeToMs(record.endTime),
|
|
70
|
+
finished: record.finished,
|
|
71
|
+
transferSize: record.transferSize,
|
|
72
|
+
resourceSize: record.resourceSize,
|
|
73
|
+
statusCode: record.statusCode,
|
|
74
|
+
mimeType: record.mimeType,
|
|
75
|
+
resourceType: record.resourceType,
|
|
76
|
+
isLinkPreload,
|
|
77
|
+
experimentalFromMainFrame,
|
|
78
|
+
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
79
|
+
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
80
|
+
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
81
|
+
lrResponseMs: responseMs, // Only exists on Lightrider runs
|
|
98
82
|
};
|
|
99
83
|
});
|
|
84
|
+
|
|
85
|
+
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
|
|
86
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
87
|
+
const headings = [
|
|
88
|
+
{key: 'url', itemType: 'url', text: 'URL'},
|
|
89
|
+
{key: 'protocol', itemType: 'text', text: 'Protocol'},
|
|
90
|
+
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
|
|
91
|
+
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
|
|
92
|
+
{
|
|
93
|
+
key: 'transferSize',
|
|
94
|
+
itemType: 'bytes',
|
|
95
|
+
displayUnit: 'kb',
|
|
96
|
+
granularity: 1,
|
|
97
|
+
text: 'Transfer Size',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'resourceSize',
|
|
101
|
+
itemType: 'bytes',
|
|
102
|
+
displayUnit: 'kb',
|
|
103
|
+
granularity: 1,
|
|
104
|
+
text: 'Resource Size',
|
|
105
|
+
},
|
|
106
|
+
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
|
|
107
|
+
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
|
|
108
|
+
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const tableDetails = Audit.makeTableDetails(headings, results);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
score: 1,
|
|
115
|
+
details: tableDetails,
|
|
116
|
+
};
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
|
|
@@ -23,6 +23,10 @@ const UIStrings = {
|
|
|
23
23
|
|
|
24
24
|
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
|
|
28
|
+
*/
|
|
29
|
+
|
|
26
30
|
class PreloadLCPImageAudit extends Audit {
|
|
27
31
|
/**
|
|
28
32
|
* @return {LH.Audit.Meta}
|
|
@@ -86,22 +90,33 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
86
90
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
87
91
|
* @param {LH.Artifacts.TraceElement|undefined} lcpElement
|
|
88
92
|
* @param {Array<LH.Artifacts.ImageElement>} imageElements
|
|
89
|
-
* @return {LH.Gatherer.Simulation.GraphNetworkNode
|
|
93
|
+
* @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
|
|
90
94
|
*/
|
|
91
95
|
static getLCPNodeToPreload(mainResource, graph, lcpElement, imageElements) {
|
|
92
|
-
if (!lcpElement) return
|
|
96
|
+
if (!lcpElement) return {};
|
|
93
97
|
|
|
94
98
|
const lcpImageElement = imageElements.find(elem => {
|
|
95
99
|
return elem.node.devtoolsNodePath === lcpElement.node.devtoolsNodePath;
|
|
96
100
|
});
|
|
97
101
|
|
|
98
|
-
if (!lcpImageElement) return
|
|
102
|
+
if (!lcpImageElement) return {};
|
|
99
103
|
const lcpUrl = lcpImageElement.src;
|
|
100
104
|
const {lcpNode, path} = PreloadLCPImageAudit.findLCPNode(graph, lcpUrl);
|
|
101
|
-
if (!lcpNode || !path) return
|
|
105
|
+
if (!lcpNode || !path) return {};
|
|
106
|
+
|
|
102
107
|
// eslint-disable-next-line max-len
|
|
103
108
|
const shouldPreload = PreloadLCPImageAudit.shouldPreloadRequest(lcpNode.record, mainResource, path);
|
|
104
|
-
|
|
109
|
+
const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
|
|
110
|
+
|
|
111
|
+
const initiatorPath = [
|
|
112
|
+
{url: lcpNode.record.url, initiatorType: lcpNode.initiatorType},
|
|
113
|
+
...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
lcpNodeToPreload,
|
|
118
|
+
initiatorPath,
|
|
119
|
+
};
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
/**
|
|
@@ -215,10 +230,10 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
215
230
|
|
|
216
231
|
const graph = lanternLCP.pessimisticGraph;
|
|
217
232
|
// eslint-disable-next-line max-len
|
|
218
|
-
const
|
|
233
|
+
const {lcpNodeToPreload, initiatorPath} = PreloadLCPImageAudit.getLCPNodeToPreload(mainResource, graph, lcpElement, artifacts.ImageElements);
|
|
219
234
|
|
|
220
235
|
const {results, wastedMs} =
|
|
221
|
-
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement,
|
|
236
|
+
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
|
|
222
237
|
|
|
223
238
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
224
239
|
const headings = [
|
|
@@ -228,6 +243,17 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
228
243
|
];
|
|
229
244
|
const details = Audit.makeOpportunityDetails(headings, results, wastedMs);
|
|
230
245
|
|
|
246
|
+
// If LCP element was an image and had valid network records (regardless of
|
|
247
|
+
// if it should be preloaded), it will be found first in the `initiatorPath`.
|
|
248
|
+
// Otherwise path and length will be undefined.
|
|
249
|
+
if (initiatorPath) {
|
|
250
|
+
details.debugData = {
|
|
251
|
+
type: 'debugdata',
|
|
252
|
+
initiatorPath,
|
|
253
|
+
pathLength: initiatorPath.length,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
231
257
|
return {
|
|
232
258
|
score: UnusedBytes.scoreForWastedMs(wastedMs),
|
|
233
259
|
numericValue: wastedMs,
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const Audit = require('./audit.js');
|
|
9
|
-
const BootupTime = require('./bootup-time.js');
|
|
10
9
|
const i18n = require('../lib/i18n/i18n.js');
|
|
11
10
|
const thirdPartyWeb = require('../lib/third-party-web.js');
|
|
12
11
|
const NetworkRecords = require('../computed/network-records.js');
|
|
13
12
|
const MainThreadTasks = require('../computed/main-thread-tasks.js');
|
|
13
|
+
const {getJavaScriptURLs, getAttributableURLForTask} = require('../lib/tracehouse/task-summary.js');
|
|
14
14
|
|
|
15
15
|
const UIStrings = {
|
|
16
16
|
/** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
@@ -98,10 +98,10 @@ class ThirdPartySummary extends Audit {
|
|
|
98
98
|
byURL.set(request.url, urlSummary);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const jsURLs =
|
|
101
|
+
const jsURLs = getJavaScriptURLs(networkRecords);
|
|
102
102
|
|
|
103
103
|
for (const task of mainThreadTasks) {
|
|
104
|
-
const attributableURL =
|
|
104
|
+
const attributableURL = getAttributableURLForTask(task, jsURLs);
|
|
105
105
|
|
|
106
106
|
const urlSummary = byURL.get(attributableURL) || {...defaultSummary};
|
|
107
107
|
const taskDuration = task.selfTime * cpuMultiplier;
|