lighthouse 12.8.2-dev.20251006 → 12.8.2-dev.20251008
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/insights/dom-size-insight.js +5 -1
- package/core/audits/insights/font-display-insight.js +3 -1
- package/core/audits/insights/insight-audit.d.ts +4 -2
- package/core/audits/insights/insight-audit.js +22 -3
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/redirects.js +0 -1
- package/core/audits/seo/crawlable-anchors.js +2 -3
- package/core/computed/metrics/lcp-breakdown.d.ts +10 -5
- package/core/computed/metrics/lcp-breakdown.js +50 -23
- package/core/computed/metrics/time-to-first-byte.js +33 -10
- package/core/computed/metrics/timing-summary.js +3 -2
- package/core/config/default-config.js +0 -12
- package/core/gather/gatherers/anchor-elements.js +8 -24
- package/core/gather/gatherers/inspector-issues.js +1 -28
- package/core/gather/gatherers/trace-elements.d.ts +0 -9
- package/core/gather/gatherers/trace-elements.js +0 -36
- package/core/lib/network-request.d.ts +0 -7
- package/core/lib/network-request.js +0 -16
- package/package.json +2 -4
- package/shared/localization/locales/ar-XB.json +20 -38
- package/shared/localization/locales/ar.json +20 -38
- package/shared/localization/locales/bg.json +9 -27
- package/shared/localization/locales/ca.json +9 -27
- package/shared/localization/locales/cs.json +9 -27
- package/shared/localization/locales/da.json +9 -27
- package/shared/localization/locales/de.json +9 -27
- package/shared/localization/locales/el.json +9 -27
- package/shared/localization/locales/en-GB.json +9 -27
- package/shared/localization/locales/en-US.json +18 -45
- package/shared/localization/locales/en-XA.json +0 -27
- package/shared/localization/locales/en-XL.json +18 -45
- package/shared/localization/locales/es-419.json +9 -27
- package/shared/localization/locales/es.json +9 -27
- package/shared/localization/locales/fi.json +9 -27
- package/shared/localization/locales/fil.json +9 -27
- package/shared/localization/locales/fr.json +9 -27
- package/shared/localization/locales/he.json +31 -49
- package/shared/localization/locales/hi.json +9 -27
- package/shared/localization/locales/hr.json +9 -27
- package/shared/localization/locales/hu.json +9 -27
- package/shared/localization/locales/id.json +9 -27
- package/shared/localization/locales/it.json +9 -27
- package/shared/localization/locales/ja.json +9 -27
- package/shared/localization/locales/ko.json +10 -28
- package/shared/localization/locales/lt.json +9 -27
- package/shared/localization/locales/lv.json +10 -28
- package/shared/localization/locales/nl.json +9 -27
- package/shared/localization/locales/no.json +9 -27
- package/shared/localization/locales/pl.json +9 -27
- package/shared/localization/locales/pt-PT.json +9 -27
- package/shared/localization/locales/pt.json +9 -27
- package/shared/localization/locales/ro.json +10 -28
- package/shared/localization/locales/ru.json +9 -27
- package/shared/localization/locales/sk.json +9 -27
- package/shared/localization/locales/sl.json +9 -27
- package/shared/localization/locales/sr-Latn.json +9 -27
- package/shared/localization/locales/sr.json +9 -27
- package/shared/localization/locales/sv.json +9 -27
- package/shared/localization/locales/ta.json +9 -27
- package/shared/localization/locales/te.json +10 -28
- package/shared/localization/locales/th.json +9 -27
- package/shared/localization/locales/tr.json +9 -27
- package/shared/localization/locales/uk.json +9 -27
- package/shared/localization/locales/vi.json +9 -27
- package/shared/localization/locales/zh-HK.json +9 -27
- package/shared/localization/locales/zh-TW.json +10 -28
- package/shared/localization/locales/zh.json +9 -27
- package/tsconfig.json +0 -3
- package/types/artifacts.d.ts +4 -43
- package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -63
- package/core/audits/byte-efficiency/offscreen-images.js +0 -240
- package/core/audits/dobetterweb/no-document-write.d.ts +0 -16
- package/core/audits/dobetterweb/no-document-write.js +0 -86
- package/core/audits/dobetterweb/uses-passive-event-listeners.d.ts +0 -16
- package/core/audits/dobetterweb/uses-passive-event-listeners.js +0 -69
- package/core/audits/metrics/first-meaningful-paint.d.ts +0 -12
- package/core/audits/metrics/first-meaningful-paint.js +0 -47
- package/core/computed/critical-request-chains.d.ts +0 -42
- package/core/computed/critical-request-chains.js +0 -143
- package/core/computed/viewport-meta.d.ts +0 -37
- package/core/computed/viewport-meta.js +0 -71
- package/core/gather/gatherers/cache-contents.d.ts +0 -11
- package/core/gather/gatherers/cache-contents.js +0 -56
- package/core/gather/gatherers/dobetterweb/domstats.d.ts +0 -10
- package/core/gather/gatherers/dobetterweb/domstats.js +0 -102
- package/core/gather/gatherers/dobetterweb/optimized-images.d.ts +0 -48
- package/core/gather/gatherers/dobetterweb/optimized-images.js +0 -169
- package/core/gather/gatherers/dobetterweb/response-compression.d.ts +0 -23
- package/core/gather/gatherers/dobetterweb/response-compression.js +0 -136
- package/types/internal/metaviewport-parser.d.ts +0 -13
- package/types/internal/parse-cache-control.d.ts +0 -20
|
@@ -46,7 +46,9 @@ class FontDisplayInsight extends Audit {
|
|
|
46
46
|
url: font.request.args.data.url,
|
|
47
47
|
wastedMs: font.wastedTime,
|
|
48
48
|
}));
|
|
49
|
-
|
|
49
|
+
const details = Audit.makeTableDetails(headings, items);
|
|
50
|
+
details.skipSumming = ['wastedMs'];
|
|
51
|
+
return details;
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -11,13 +11,15 @@ export type CreateDetailsExtras = {
|
|
|
11
11
|
* @param {LH.Artifacts} artifacts
|
|
12
12
|
* @param {LH.Audit.Context} context
|
|
13
13
|
* @param {T} insightName
|
|
14
|
-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings
|
|
14
|
+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings?: Array<string | LH.IcuMessage>, numericValue?: number, numericUnit?: LH.Audit.NumericProduct['numericUnit']}|LH.Audit.Details|undefined} createDetails
|
|
15
15
|
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
16
16
|
* @return {Promise<LH.Audit.Product>}
|
|
17
17
|
*/
|
|
18
18
|
export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModelsType>(artifacts: LH.Artifacts, context: LH.Audit.Context, insightName: T, createDetails: (insight: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModels[T], extras: CreateDetailsExtras) => {
|
|
19
19
|
details: LH.Audit.Details;
|
|
20
|
-
warnings
|
|
20
|
+
warnings?: Array<string | LH.IcuMessage>;
|
|
21
|
+
numericValue?: number;
|
|
22
|
+
numericUnit?: LH.Audit.NumericProduct["numericUnit"];
|
|
21
23
|
} | LH.Audit.Details | undefined): Promise<LH.Audit.Product>;
|
|
22
24
|
/**
|
|
23
25
|
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
@@ -42,7 +42,7 @@ async function getInsightSet(artifacts, context) {
|
|
|
42
42
|
* @param {LH.Artifacts} artifacts
|
|
43
43
|
* @param {LH.Audit.Context} context
|
|
44
44
|
* @param {T} insightName
|
|
45
|
-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings
|
|
45
|
+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings?: Array<string | LH.IcuMessage>, numericValue?: number, numericUnit?: LH.Audit.NumericProduct['numericUnit']}|LH.Audit.Details|undefined} createDetails
|
|
46
46
|
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
47
47
|
* @return {Promise<LH.Audit.Product>}
|
|
48
48
|
*/
|
|
@@ -70,11 +70,17 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
const warnings = [...insight.warnings ?? []];
|
|
73
|
+
/** @type {number|undefined} */
|
|
74
|
+
let numericValue;
|
|
75
|
+
/** @type {LH.Audit.NumericProduct['numericUnit']|undefined} */
|
|
76
|
+
let numericUnit;
|
|
73
77
|
|
|
74
78
|
let details;
|
|
75
|
-
if (cbResult && '
|
|
79
|
+
if (cbResult && 'details' in cbResult) {
|
|
76
80
|
details = cbResult.details;
|
|
77
|
-
warnings.push(...cbResult.warnings);
|
|
81
|
+
if (cbResult.warnings) warnings.push(...cbResult.warnings);
|
|
82
|
+
numericValue = cbResult.numericValue;
|
|
83
|
+
numericUnit = cbResult.numericUnit;
|
|
78
84
|
} else {
|
|
79
85
|
details = cbResult;
|
|
80
86
|
}
|
|
@@ -163,6 +169,19 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
163
169
|
scoreDisplayMode = Audit.SCORING_MODES.INFORMATIVE;
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
if (numericValue !== undefined && numericUnit !== undefined) {
|
|
173
|
+
return {
|
|
174
|
+
scoreDisplayMode,
|
|
175
|
+
score,
|
|
176
|
+
numericValue,
|
|
177
|
+
numericUnit,
|
|
178
|
+
metricSavings,
|
|
179
|
+
warnings: warnings.length ? warnings : undefined,
|
|
180
|
+
displayValue,
|
|
181
|
+
details,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
166
185
|
return {
|
|
167
186
|
scoreDisplayMode,
|
|
168
187
|
score,
|
|
@@ -76,8 +76,8 @@ class PredictivePerf extends Audit {
|
|
|
76
76
|
pessimisticLCP: lcp.pessimisticEstimate.timeInMs,
|
|
77
77
|
|
|
78
78
|
roughEstimateOfTTFB: timingSummary.metrics.timeToFirstByte,
|
|
79
|
-
roughEstimateOfLCPLoadStart: timingSummary.metrics.
|
|
80
|
-
roughEstimateOfLCPLoadEnd: timingSummary.metrics.
|
|
79
|
+
roughEstimateOfLCPLoadStart: timingSummary.metrics.lcpLoadDelay,
|
|
80
|
+
roughEstimateOfLCPLoadEnd: timingSummary.metrics.lcpLoadDuration,
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const score = Audit.computeLogNormalScore(
|
package/core/audits/redirects.js
CHANGED
|
@@ -89,7 +89,6 @@ class Redirects extends Audit {
|
|
|
89
89
|
const gatherContext = artifacts.GatherContext;
|
|
90
90
|
const {URL, SourceMaps} = artifacts;
|
|
91
91
|
|
|
92
|
-
// TODO(v13): use trace processor / network tree insight.
|
|
93
92
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
94
93
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
95
94
|
const documentRequests = Redirects.getDocumentRequestChain(networkRecords, processedTrace);
|
|
@@ -57,12 +57,10 @@ class CrawlableAnchors extends Audit {
|
|
|
57
57
|
href,
|
|
58
58
|
attributeNames = [],
|
|
59
59
|
listeners = [],
|
|
60
|
-
ancestorListeners = [],
|
|
61
60
|
}) => {
|
|
62
61
|
rawHref = rawHref.replace( /\s/g, '');
|
|
63
62
|
name = name.trim();
|
|
64
63
|
role = role.trim();
|
|
65
|
-
const hasListener = Boolean(listeners.length || ancestorListeners.length);
|
|
66
64
|
|
|
67
65
|
if (role.length > 0) return;
|
|
68
66
|
// Ignore mailto links even if they use one of the failing patterns. See https://github.com/GoogleChrome/lighthouse/issues/11443#issuecomment-694898412
|
|
@@ -86,7 +84,8 @@ class CrawlableAnchors extends Audit {
|
|
|
86
84
|
!attributeNames.includes('href') &&
|
|
87
85
|
hrefAssociatedAttributes.every(attribute => !attributeNames.includes(attribute))
|
|
88
86
|
) {
|
|
89
|
-
|
|
87
|
+
// If it has an even listener (e.g. onclick) then we can't assume it's a placeholder. Therefore we consider it failing.
|
|
88
|
+
return Boolean(listeners.length);
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
if (href === '') return true;
|
|
@@ -2,20 +2,25 @@ export { LCPBreakdownComputed as LCPBreakdown };
|
|
|
2
2
|
declare const LCPBreakdownComputed: typeof LCPBreakdown & {
|
|
3
3
|
request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext) => Promise<{
|
|
4
4
|
ttfb: number;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
loadDelay?: number;
|
|
6
|
+
loadDuration?: number;
|
|
7
|
+
renderDelay?: number;
|
|
7
8
|
}>;
|
|
8
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Note: this omits renderDelay for simulated throttling.
|
|
12
|
+
*/
|
|
9
13
|
declare class LCPBreakdown {
|
|
10
14
|
/**
|
|
11
15
|
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
12
16
|
* @param {LH.Artifacts.ComputedContext} context
|
|
13
|
-
* @return {Promise<{ttfb: number,
|
|
17
|
+
* @return {Promise<{ttfb: number, loadDelay?: number, loadDuration?: number, renderDelay?: number}>}
|
|
14
18
|
*/
|
|
15
19
|
static compute_(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<{
|
|
16
20
|
ttfb: number;
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
loadDelay?: number;
|
|
22
|
+
loadDuration?: number;
|
|
23
|
+
renderDelay?: number;
|
|
19
24
|
}>;
|
|
20
25
|
}
|
|
21
26
|
//# sourceMappingURL=lcp-breakdown.d.ts.map
|
|
@@ -10,43 +10,70 @@ import {LargestContentfulPaint} from './largest-contentful-paint.js';
|
|
|
10
10
|
import {ProcessedNavigation} from '../processed-navigation.js';
|
|
11
11
|
import {TimeToFirstByte} from './time-to-first-byte.js';
|
|
12
12
|
import {LCPImageRecord} from '../lcp-image-record.js';
|
|
13
|
+
import {NavigationInsights} from '../navigation-insights.js';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Note: this omits renderDelay for simulated throttling.
|
|
17
|
+
*/
|
|
15
18
|
class LCPBreakdown {
|
|
16
19
|
/**
|
|
17
20
|
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
18
21
|
* @param {LH.Artifacts.ComputedContext} context
|
|
19
|
-
* @return {Promise<{ttfb: number,
|
|
22
|
+
* @return {Promise<{ttfb: number, loadDelay?: number, loadDuration?: number, renderDelay?: number}>}
|
|
20
23
|
*/
|
|
21
24
|
static async compute_(data, context) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
if (data.settings.throttlingMethod === 'simulate') {
|
|
26
|
+
const processedNavigation = await ProcessedNavigation.request(data.trace, context);
|
|
27
|
+
const observedLcp = processedNavigation.timings.largestContentfulPaint;
|
|
28
|
+
if (observedLcp === undefined) {
|
|
29
|
+
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
30
|
+
}
|
|
31
|
+
const timeOrigin = processedNavigation.timestamps.timeOrigin / 1000;
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
const {timing: ttfb} = await TimeToFirstByte.request(data, context);
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const lcpRecord = await LCPImageRecord.request(data, context);
|
|
36
|
+
if (!lcpRecord) {
|
|
37
|
+
return {ttfb};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Official LCP^tm. Will be lantern result if simulated, otherwise same as observedLcp.
|
|
41
|
+
const {timing: metricLcp} = await LargestContentfulPaint.request(data, context);
|
|
42
|
+
const throttleRatio = metricLcp / observedLcp;
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const throttleRatio = metricLcp / observedLcp;
|
|
44
|
+
const unclampedLoadStart = (lcpRecord.networkRequestTime - timeOrigin) * throttleRatio;
|
|
45
|
+
const loadDelay = Math.max(ttfb, Math.min(unclampedLoadStart, metricLcp));
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
const unclampedLoadEnd = (lcpRecord.networkEndTime - timeOrigin) * throttleRatio;
|
|
48
|
+
const loadDuration = Math.max(loadDelay, Math.min(unclampedLoadEnd, metricLcp));
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
return {
|
|
51
|
+
ttfb,
|
|
52
|
+
loadDelay,
|
|
53
|
+
loadDuration,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const {trace, settings, SourceMaps} = data;
|
|
58
|
+
const navInsights = await NavigationInsights.request({trace, settings, SourceMaps}, context);
|
|
59
|
+
const lcpBreakdown = navInsights.model.LCPBreakdown;
|
|
60
|
+
if (lcpBreakdown instanceof Error) {
|
|
61
|
+
throw new LighthouseError(LighthouseError.errors.NO_LCP, {}, {cause: lcpBreakdown});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!lcpBreakdown.subparts) {
|
|
65
|
+
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
66
|
+
}
|
|
45
67
|
|
|
46
68
|
return {
|
|
47
|
-
ttfb,
|
|
48
|
-
|
|
49
|
-
|
|
69
|
+
ttfb: lcpBreakdown.subparts.ttfb.range / 1000,
|
|
70
|
+
loadDelay: lcpBreakdown.subparts.loadDelay !== undefined ?
|
|
71
|
+
lcpBreakdown.subparts.loadDelay.range / 1000 :
|
|
72
|
+
undefined,
|
|
73
|
+
loadDuration: lcpBreakdown.subparts.loadDuration !== undefined ?
|
|
74
|
+
lcpBreakdown.subparts.loadDuration.range / 1000 :
|
|
75
|
+
undefined,
|
|
76
|
+
renderDelay: lcpBreakdown.subparts.renderDelay.range / 1000,
|
|
50
77
|
};
|
|
51
78
|
}
|
|
52
79
|
}
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import {calculateDocFirstByteTs} from '@paulirish/trace_engine/models/trace/insights/Common.js';
|
|
8
|
+
|
|
7
9
|
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
8
10
|
import {NavigationMetric} from './navigation-metric.js';
|
|
9
11
|
import {MainResource} from '../main-resource.js';
|
|
10
12
|
import {NetworkAnalysis} from '../network-analysis.js';
|
|
13
|
+
import {NavigationInsights} from '../navigation-insights.js';
|
|
14
|
+
import {TraceEngineResult} from '../trace-engine-result.js';
|
|
11
15
|
|
|
12
16
|
class TimeToFirstByte extends NavigationMetric {
|
|
13
17
|
/**
|
|
@@ -41,18 +45,37 @@ class TimeToFirstByte extends NavigationMetric {
|
|
|
41
45
|
* @return {Promise<LH.Artifacts.Metric>}
|
|
42
46
|
*/
|
|
43
47
|
static async computeObservedMetric(data, context) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
const {trace, settings, SourceMaps} = data;
|
|
49
|
+
const traceEngineResult =
|
|
50
|
+
await TraceEngineResult.request({trace, settings, SourceMaps}, context);
|
|
51
|
+
const navInsights = await NavigationInsights.request({trace, settings, SourceMaps}, context);
|
|
52
|
+
const lcpBreakdown = navInsights.model.LCPBreakdown;
|
|
53
|
+
|
|
54
|
+
// Defer to LCP breakdown, but if there's no LCP fallback to manual calculation.
|
|
55
|
+
if (!(lcpBreakdown instanceof Error) && lcpBreakdown.subparts) {
|
|
56
|
+
return {
|
|
57
|
+
timing: lcpBreakdown.subparts.ttfb.range / 1000,
|
|
58
|
+
timestamp: lcpBreakdown.subparts.ttfb.max,
|
|
59
|
+
};
|
|
60
|
+
} else if (navInsights.navigation?.args.data?.navigationId) {
|
|
61
|
+
const request = traceEngineResult.data.NetworkRequests.byId.get(
|
|
62
|
+
navInsights.navigation.args.data.navigationId);
|
|
63
|
+
if (!request) {
|
|
64
|
+
throw new Error();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const timestamp = calculateDocFirstByteTs(request);
|
|
68
|
+
if (timestamp === null) {
|
|
69
|
+
throw new Error('cannot calculate ttfb');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
timing: (timestamp - navInsights.navigation.ts) / 1000,
|
|
74
|
+
timestamp,
|
|
75
|
+
};
|
|
47
76
|
}
|
|
48
77
|
|
|
49
|
-
|
|
50
|
-
const timeOriginTs = processedNavigation.timestamps.timeOrigin;
|
|
51
|
-
const timestampMs =
|
|
52
|
-
mainResource.timing.requestTime * 1000 + mainResource.timing.receiveHeadersStart;
|
|
53
|
-
const timestamp = timestampMs * 1000;
|
|
54
|
-
const timing = (timestamp - timeOriginTs) / 1000;
|
|
55
|
-
return {timing, timestamp};
|
|
78
|
+
throw new Error('cannot determine ttfb');
|
|
56
79
|
}
|
|
57
80
|
}
|
|
58
81
|
|
|
@@ -96,8 +96,9 @@ class TimingSummary {
|
|
|
96
96
|
cumulativeLayoutShift,
|
|
97
97
|
cumulativeLayoutShiftMainFrame,
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
lcpLoadDelay: lcpBreakdown?.loadDelay,
|
|
100
|
+
lcpLoadDuration: lcpBreakdown?.loadDuration,
|
|
101
|
+
lcpRenderDelay: lcpBreakdown?.renderDelay,
|
|
101
102
|
|
|
102
103
|
timeToFirstByte: ttfb?.timing,
|
|
103
104
|
timeToFirstByteTs: ttfb?.timestamp,
|
|
@@ -114,11 +114,9 @@ const defaultConfig = {
|
|
|
114
114
|
|
|
115
115
|
{id: 'Accessibility', gatherer: 'accessibility'},
|
|
116
116
|
{id: 'AnchorElements', gatherer: 'anchor-elements'},
|
|
117
|
-
{id: 'CacheContents', gatherer: 'cache-contents'},
|
|
118
117
|
{id: 'ConsoleMessages', gatherer: 'console-messages'},
|
|
119
118
|
{id: 'CSSUsage', gatherer: 'css-usage'},
|
|
120
119
|
{id: 'Doctype', gatherer: 'dobetterweb/doctype'},
|
|
121
|
-
{id: 'DOMStats', gatherer: 'dobetterweb/domstats'},
|
|
122
120
|
{id: 'Inputs', gatherer: 'inputs'},
|
|
123
121
|
{id: 'IFrameElements', gatherer: 'iframe-elements'},
|
|
124
122
|
{id: 'ImageElements', gatherer: 'image-elements'},
|
|
@@ -128,8 +126,6 @@ const defaultConfig = {
|
|
|
128
126
|
{id: 'MainDocumentContent', gatherer: 'main-document-content'},
|
|
129
127
|
{id: 'MetaElements', gatherer: 'meta-elements'},
|
|
130
128
|
{id: 'NetworkUserAgent', gatherer: 'network-user-agent'},
|
|
131
|
-
{id: 'OptimizedImages', gatherer: 'dobetterweb/optimized-images'},
|
|
132
|
-
{id: 'ResponseCompression', gatherer: 'dobetterweb/response-compression'},
|
|
133
129
|
{id: 'RobotsTxt', gatherer: 'seo/robots-txt'},
|
|
134
130
|
{id: 'Scripts', gatherer: 'scripts'},
|
|
135
131
|
{id: 'SourceMaps', gatherer: 'source-maps'},
|
|
@@ -149,7 +145,6 @@ const defaultConfig = {
|
|
|
149
145
|
'redirects-http',
|
|
150
146
|
'metrics/first-contentful-paint',
|
|
151
147
|
'metrics/largest-contentful-paint',
|
|
152
|
-
'metrics/first-meaningful-paint',
|
|
153
148
|
'metrics/speed-index',
|
|
154
149
|
'screenshot-thumbnails',
|
|
155
150
|
'final-screenshot',
|
|
@@ -260,7 +255,6 @@ const defaultConfig = {
|
|
|
260
255
|
'accessibility/manual/use-landmarks',
|
|
261
256
|
'accessibility/manual/visual-order-follows-dom',
|
|
262
257
|
'byte-efficiency/total-byte-weight',
|
|
263
|
-
'byte-efficiency/offscreen-images',
|
|
264
258
|
'byte-efficiency/unminified-css',
|
|
265
259
|
'byte-efficiency/unminified-javascript',
|
|
266
260
|
'byte-efficiency/unused-css-rules',
|
|
@@ -269,11 +263,9 @@ const defaultConfig = {
|
|
|
269
263
|
'dobetterweb/charset',
|
|
270
264
|
'dobetterweb/geolocation-on-start',
|
|
271
265
|
'dobetterweb/inspector-issues',
|
|
272
|
-
'dobetterweb/no-document-write',
|
|
273
266
|
'dobetterweb/js-libraries',
|
|
274
267
|
'dobetterweb/notification-on-start',
|
|
275
268
|
'dobetterweb/paste-preventing-inputs',
|
|
276
|
-
'dobetterweb/uses-passive-event-listeners',
|
|
277
269
|
'seo/meta-description',
|
|
278
270
|
'seo/http-status-code',
|
|
279
271
|
'seo/link-text',
|
|
@@ -407,9 +399,7 @@ const defaultConfig = {
|
|
|
407
399
|
// These are our "invisible" metrics. Not displayed, but still in the LHR.
|
|
408
400
|
{id: 'interactive', weight: 0, group: 'hidden', acronym: 'TTI'},
|
|
409
401
|
{id: 'max-potential-fid', weight: 0, group: 'hidden'},
|
|
410
|
-
{id: 'first-meaningful-paint', weight: 0, acronym: 'FMP', group: 'hidden'},
|
|
411
402
|
|
|
412
|
-
{id: 'offscreen-images', weight: 0, group: 'diagnostics'},
|
|
413
403
|
{id: 'unminified-css', weight: 0, group: 'diagnostics'},
|
|
414
404
|
{id: 'unminified-javascript', weight: 0, group: 'diagnostics'},
|
|
415
405
|
{id: 'unused-css-rules', weight: 0, group: 'diagnostics'},
|
|
@@ -418,8 +408,6 @@ const defaultConfig = {
|
|
|
418
408
|
{id: 'user-timings', weight: 0, group: 'diagnostics'},
|
|
419
409
|
{id: 'bootup-time', weight: 0, group: 'diagnostics'},
|
|
420
410
|
{id: 'mainthread-work-breakdown', weight: 0, group: 'diagnostics'},
|
|
421
|
-
{id: 'uses-passive-event-listeners', weight: 0, group: 'diagnostics'},
|
|
422
|
-
{id: 'no-document-write', weight: 0, group: 'diagnostics'},
|
|
423
411
|
{id: 'long-tasks', weight: 0, group: 'diagnostics'},
|
|
424
412
|
{id: 'non-composited-animations', weight: 0, group: 'diagnostics'},
|
|
425
413
|
{id: 'unsized-images', weight: 0, group: 'diagnostics'},
|
|
@@ -157,33 +157,17 @@ class AnchorElements extends BaseGatherer {
|
|
|
157
157
|
|
|
158
158
|
// DOM.getDocument is necessary for pushNodesByBackendIdsToFrontend to properly retrieve nodeIds if the `DOM` domain was enabled before this gatherer, invoke it to be safe.
|
|
159
159
|
await session.sendCommand('DOM.getDocument', {depth: -1, pierce: true});
|
|
160
|
-
const anchorsWithEventListeners = anchors.map(async anchor => {
|
|
161
|
-
const listeners = await getEventListeners(session, anchor.node.devtoolsNodePath);
|
|
162
|
-
|
|
163
|
-
/** @type {Set<{type: string}>} */
|
|
164
|
-
const ancestorListeners = new Set();
|
|
165
|
-
const splitPath = anchor.node.devtoolsNodePath.split(',');
|
|
166
|
-
const ancestorListenerPromises = [];
|
|
167
|
-
while (splitPath.length >= 2) {
|
|
168
|
-
splitPath.length -= 2;
|
|
169
|
-
const path = splitPath.join(',');
|
|
170
|
-
const promise = getEventListeners(session, path).then(listeners => {
|
|
171
|
-
for (const listener of listeners) {
|
|
172
|
-
ancestorListeners.add(listener);
|
|
173
|
-
}
|
|
174
|
-
}).catch(() => {});
|
|
175
|
-
ancestorListenerPromises.push(promise);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
await Promise.all(ancestorListenerPromises);
|
|
179
160
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
161
|
+
const anchorsWithEventListeners = anchors.map( anchor => {
|
|
162
|
+
return getEventListeners(session, anchor.node.devtoolsNodePath).then(listeners => {
|
|
163
|
+
return {
|
|
164
|
+
...anchor,
|
|
165
|
+
listeners,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
185
168
|
});
|
|
186
169
|
|
|
170
|
+
|
|
187
171
|
const result = await Promise.all(anchorsWithEventListeners);
|
|
188
172
|
await session.sendCommand('DOM.disable');
|
|
189
173
|
return result;
|
|
@@ -61,34 +61,7 @@ class InspectorIssues extends BaseGatherer {
|
|
|
61
61
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
62
62
|
|
|
63
63
|
/** @type {LH.Artifacts.InspectorIssues} */
|
|
64
|
-
const artifact = {
|
|
65
|
-
// TODO(v13): remove empty arrays.
|
|
66
|
-
attributionReportingIssue: [],
|
|
67
|
-
blockedByResponseIssue: [],
|
|
68
|
-
bounceTrackingIssue: [],
|
|
69
|
-
clientHintIssue: [],
|
|
70
|
-
contentSecurityPolicyIssue: [],
|
|
71
|
-
cookieDeprecationMetadataIssue: [],
|
|
72
|
-
corsIssue: [],
|
|
73
|
-
deprecationIssue: [],
|
|
74
|
-
federatedAuthRequestIssue: [],
|
|
75
|
-
genericIssue: [],
|
|
76
|
-
heavyAdIssue: [],
|
|
77
|
-
lowTextContrastIssue: [],
|
|
78
|
-
mixedContentIssue: [],
|
|
79
|
-
navigatorUserAgentIssue: [],
|
|
80
|
-
partitioningBlobURLIssue: [],
|
|
81
|
-
propertyRuleIssue: [],
|
|
82
|
-
quirksModeIssue: [],
|
|
83
|
-
cookieIssue: [],
|
|
84
|
-
elementAccessibilityIssue: [],
|
|
85
|
-
sharedArrayBufferIssue: [],
|
|
86
|
-
sharedDictionaryIssue: [],
|
|
87
|
-
stylesheetLoadingIssue: [],
|
|
88
|
-
sriMessageSignatureIssue: [],
|
|
89
|
-
federatedAuthUserInfoRequestIssue: [],
|
|
90
|
-
userReidentificationIssue: [],
|
|
91
|
-
};
|
|
64
|
+
const artifact = {};
|
|
92
65
|
|
|
93
66
|
for (const issue of this._issues) {
|
|
94
67
|
const detailsKey = /** @type {keyof LH.Crdp.Audits.InspectorIssueDetails} */(
|
|
@@ -45,15 +45,6 @@ declare class TraceElements extends BaseGatherer {
|
|
|
45
45
|
* @return {Promise<TraceElementData|undefined>}
|
|
46
46
|
*/
|
|
47
47
|
static getResponsivenessElement(trace: LH.Trace, context: LH.Gatherer.Context): Promise<TraceElementData | undefined>;
|
|
48
|
-
/**
|
|
49
|
-
* @param {LH.Trace} trace
|
|
50
|
-
* @param {LH.Gatherer.Context} context
|
|
51
|
-
* @return {Promise<{nodeId: number, type: string} | undefined>}
|
|
52
|
-
*/
|
|
53
|
-
static getLcpElement(trace: LH.Trace, context: LH.Gatherer.Context): Promise<{
|
|
54
|
-
nodeId: number;
|
|
55
|
-
type: string;
|
|
56
|
-
} | undefined>;
|
|
57
48
|
/** @type {LH.Gatherer.GathererMeta<'Trace'|'SourceMaps'>} */
|
|
58
49
|
meta: LH.Gatherer.GathererMeta<"Trace" | "SourceMaps">;
|
|
59
50
|
/** @type {Map<string, string>} */
|
|
@@ -18,8 +18,6 @@ import {pageFunctions} from '../../lib/page-functions.js';
|
|
|
18
18
|
import {Sentry} from '../../lib/sentry.js';
|
|
19
19
|
import Trace from './trace.js';
|
|
20
20
|
import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
21
|
-
import {ProcessedNavigation} from '../../computed/processed-navigation.js';
|
|
22
|
-
import {LighthouseError} from '../../lib/lh-error.js';
|
|
23
21
|
import {Responsiveness} from '../../computed/metrics/responsiveness.js';
|
|
24
22
|
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
25
23
|
import {ExecutionContext} from '../driver/execution-context.js';
|
|
@@ -276,35 +274,6 @@ class TraceElements extends BaseGatherer {
|
|
|
276
274
|
return animatedElementData;
|
|
277
275
|
}
|
|
278
276
|
|
|
279
|
-
/**
|
|
280
|
-
* @param {LH.Trace} trace
|
|
281
|
-
* @param {LH.Gatherer.Context} context
|
|
282
|
-
* @return {Promise<{nodeId: number, type: string} | undefined>}
|
|
283
|
-
*/
|
|
284
|
-
static async getLcpElement(trace, context) {
|
|
285
|
-
let processedNavigation;
|
|
286
|
-
try {
|
|
287
|
-
processedNavigation = await ProcessedNavigation.request(trace, context);
|
|
288
|
-
} catch (err) {
|
|
289
|
-
// If we were running in timespan mode and there was no paint, treat LCP as missing.
|
|
290
|
-
if (context.gatherMode === 'timespan' && err.code === LighthouseError.errors.NO_FCP.code) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
throw err;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Use main-frame-only LCP to match the metric value.
|
|
298
|
-
const lcpData = processedNavigation.largestContentfulPaintEvt?.args?.data;
|
|
299
|
-
// These should exist, but trace types are loose.
|
|
300
|
-
if (lcpData?.nodeId === undefined || !lcpData.type) return;
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
nodeId: lcpData.nodeId,
|
|
304
|
-
type: lcpData.type,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
277
|
/**
|
|
309
278
|
* @param {LH.Gatherer.Context} context
|
|
310
279
|
*/
|
|
@@ -372,20 +341,15 @@ class TraceElements extends BaseGatherer {
|
|
|
372
341
|
|
|
373
342
|
const traceEngineData = await TraceElements.getTraceEngineElements(
|
|
374
343
|
traceEngineResult, navigationId);
|
|
375
|
-
const lcpNodeData = await TraceElements.getLcpElement(trace, context);
|
|
376
344
|
const shiftsData = await TraceElements.getTopLayoutShifts(
|
|
377
345
|
trace, traceEngineResult, context);
|
|
378
346
|
const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
|
|
379
|
-
const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
|
|
380
347
|
|
|
381
|
-
// TODO(v13): remove unnecessary entries.
|
|
382
348
|
/** @type {Map<string, TraceElementData[]>} */
|
|
383
349
|
const backendNodeDataMap = new Map([
|
|
384
350
|
['trace-engine', traceEngineData],
|
|
385
|
-
['largest-contentful-paint', lcpNodeData ? [lcpNodeData] : []],
|
|
386
351
|
['layout-shift', shiftsData],
|
|
387
352
|
['animation', animatedElementData],
|
|
388
|
-
['responsiveness', responsivenessElementData ? [responsivenessElementData] : []],
|
|
389
353
|
]);
|
|
390
354
|
|
|
391
355
|
/** @type {Map<number, LH.Crdp.Runtime.CallFunctionOnResponse | null>} */
|
|
@@ -241,13 +241,6 @@ export class NetworkRequest {
|
|
|
241
241
|
* LR loses protocol information.
|
|
242
242
|
*/
|
|
243
243
|
_updateProtocolForLightrider(): void;
|
|
244
|
-
/**
|
|
245
|
-
* TODO(compat): remove M116.
|
|
246
|
-
* `timing.receiveHeadersStart` was added recently, and will be in M116. Until then,
|
|
247
|
-
* set it to receiveHeadersEnd, which is close enough, to allow consumers of NetworkRequest
|
|
248
|
-
* to use the new field without accounting for this backcompat.
|
|
249
|
-
*/
|
|
250
|
-
_backfillReceiveHeaderStartTiming(): void;
|
|
251
244
|
/**
|
|
252
245
|
* LR gets additional, accurate timing information from its underlying fetch infrastructure. This
|
|
253
246
|
* is passed in via X-Headers similar to 'X-TotalFetchedSize'.
|
|
@@ -286,7 +286,6 @@ class NetworkRequest {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
this._updateResponseHeadersEndTimeIfNecessary();
|
|
289
|
-
this._backfillReceiveHeaderStartTiming();
|
|
290
289
|
this._updateTransferSizeForLightrider();
|
|
291
290
|
this._updateTimingsForLightrider();
|
|
292
291
|
}
|
|
@@ -306,7 +305,6 @@ class NetworkRequest {
|
|
|
306
305
|
this.localizedFailDescription = data.errorText;
|
|
307
306
|
|
|
308
307
|
this._updateResponseHeadersEndTimeIfNecessary();
|
|
309
|
-
this._backfillReceiveHeaderStartTiming();
|
|
310
308
|
this._updateTransferSizeForLightrider();
|
|
311
309
|
this._updateTimingsForLightrider();
|
|
312
310
|
}
|
|
@@ -329,7 +327,6 @@ class NetworkRequest {
|
|
|
329
327
|
this.networkEndTime = data.timestamp * 1000;
|
|
330
328
|
|
|
331
329
|
this._updateResponseHeadersEndTimeIfNecessary();
|
|
332
|
-
this._backfillReceiveHeaderStartTiming();
|
|
333
330
|
}
|
|
334
331
|
|
|
335
332
|
/**
|
|
@@ -463,19 +460,6 @@ class NetworkRequest {
|
|
|
463
460
|
}
|
|
464
461
|
}
|
|
465
462
|
|
|
466
|
-
/**
|
|
467
|
-
* TODO(compat): remove M116.
|
|
468
|
-
* `timing.receiveHeadersStart` was added recently, and will be in M116. Until then,
|
|
469
|
-
* set it to receiveHeadersEnd, which is close enough, to allow consumers of NetworkRequest
|
|
470
|
-
* to use the new field without accounting for this backcompat.
|
|
471
|
-
*/
|
|
472
|
-
_backfillReceiveHeaderStartTiming() {
|
|
473
|
-
// Do nothing if a value is already present!
|
|
474
|
-
if (!this.timing || this.timing.receiveHeadersStart !== undefined) return;
|
|
475
|
-
|
|
476
|
-
this.timing.receiveHeadersStart = this.timing.receiveHeadersEnd;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
463
|
/**
|
|
480
464
|
* LR gets additional, accurate timing information from its underlying fetch infrastructure. This
|
|
481
465
|
* is passed in via X-Headers similar to 'X-TotalFetchedSize'.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "12.8.2-dev.
|
|
4
|
+
"version": "12.8.2-dev.20251008",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"webtreemap-cdt": "^3.2.1"
|
|
182
182
|
},
|
|
183
183
|
"dependencies": {
|
|
184
|
-
"@paulirish/trace_engine": "0.0.
|
|
184
|
+
"@paulirish/trace_engine": "0.0.61",
|
|
185
185
|
"@sentry/node": "^9.28.1",
|
|
186
186
|
"axe-core": "^4.10.3",
|
|
187
187
|
"chrome-launcher": "^1.2.1",
|
|
@@ -197,9 +197,7 @@
|
|
|
197
197
|
"lighthouse-stack-packs": "1.12.3",
|
|
198
198
|
"lodash-es": "^4.17.21",
|
|
199
199
|
"lookup-closest-locale": "6.2.0",
|
|
200
|
-
"metaviewport-parser": "0.3.0",
|
|
201
200
|
"open": "^8.4.0",
|
|
202
|
-
"parse-cache-control": "1.0.1",
|
|
203
201
|
"puppeteer-core": "^24.23.0",
|
|
204
202
|
"robots-parser": "^3.0.1",
|
|
205
203
|
"speedline-core": "^1.4.3",
|