lighthouse 12.3.0 → 12.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/test/smokehouse/core-tests.js +4 -0
- package/core/audits/audit.d.ts +5 -0
- package/core/audits/audit.js +12 -0
- package/core/audits/bootup-time.js +0 -2
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +4 -5
- package/core/audits/byte-efficiency/duplicated-javascript.js +9 -5
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +2 -2
- package/core/audits/byte-efficiency/legacy-javascript.js +17 -5
- package/core/audits/byte-efficiency/polyfill-graph-data.json +48 -49
- package/core/audits/byte-efficiency/total-byte-weight.js +0 -2
- package/core/audits/clickjacking-mitigation.d.ts +42 -0
- package/core/audits/clickjacking-mitigation.js +139 -0
- package/core/audits/dobetterweb/dom-size.js +0 -2
- package/core/audits/insights/README.md +3 -0
- package/core/audits/insights/cls-culprits-insight.d.ts +25 -0
- package/core/audits/insights/cls-culprits-insight.js +137 -0
- package/core/audits/insights/document-latency-insight.d.ts +11 -0
- package/core/audits/insights/document-latency-insight.js +48 -0
- package/core/audits/insights/dom-size-insight.d.ts +11 -0
- package/core/audits/insights/dom-size-insight.js +85 -0
- package/core/audits/insights/font-display-insight.d.ts +11 -0
- package/core/audits/insights/font-display-insight.js +53 -0
- package/core/audits/insights/forced-reflow-insight.d.ts +11 -0
- package/core/audits/insights/forced-reflow-insight.js +52 -0
- package/core/audits/insights/image-delivery-insight.d.ts +11 -0
- package/core/audits/insights/image-delivery-insight.js +83 -0
- package/core/audits/insights/insight-audit.d.ts +23 -0
- package/core/audits/insights/insight-audit.js +133 -0
- package/core/audits/insights/interaction-to-next-paint-insight.d.ts +11 -0
- package/core/audits/insights/interaction-to-next-paint-insight.js +71 -0
- package/core/audits/insights/lcp-discovery-insight.d.ts +11 -0
- package/core/audits/insights/lcp-discovery-insight.js +48 -0
- package/core/audits/insights/lcp-phases-insight.d.ts +16 -0
- package/core/audits/insights/lcp-phases-insight.js +87 -0
- package/core/audits/insights/long-critical-network-tree-insight.d.ts +11 -0
- package/core/audits/insights/long-critical-network-tree-insight.js +53 -0
- package/core/audits/insights/render-blocking-insight.d.ts +11 -0
- package/core/audits/insights/render-blocking-insight.js +57 -0
- package/core/audits/insights/slow-css-selector-insight.d.ts +11 -0
- package/core/audits/insights/slow-css-selector-insight.js +52 -0
- package/core/audits/insights/third-parties-insight.d.ts +28 -0
- package/core/audits/insights/third-parties-insight.js +90 -0
- package/core/audits/insights/viewport-insight.d.ts +11 -0
- package/core/audits/insights/viewport-insight.js +54 -0
- package/core/audits/layout-shifts.d.ts +0 -1
- package/core/audits/layout-shifts.js +18 -21
- package/core/audits/mainthread-work-breakdown.js +0 -2
- package/core/audits/seo/is-crawlable.d.ts +1 -0
- package/core/audits/server-response-time.js +0 -1
- package/core/computed/metrics/lantern-metric.js +5 -1
- package/core/computed/trace-engine-result.js +71 -17
- package/core/config/default-config.js +37 -1
- package/core/gather/gatherers/inspector-issues.js +3 -0
- package/core/gather/gatherers/trace-elements.d.ts +10 -2
- package/core/gather/gatherers/trace-elements.js +89 -12
- package/core/lib/bf-cache-strings.d.ts +7 -4
- package/core/lib/bf-cache-strings.js +174 -140
- package/core/lib/cdt/generated/ParsedURL.d.ts +1 -0
- package/core/lib/cdt/generated/ParsedURL.js +16 -4
- package/core/lib/cdt/generated/SourceMap.d.ts +32 -5
- package/core/lib/cdt/generated/SourceMap.js +192 -100
- package/core/lib/deprecations-strings.d.ts +78 -98
- package/core/lib/deprecations-strings.js +23 -41
- package/core/lib/i18n/i18n.d.ts +1 -0
- package/core/lib/i18n/i18n.js +2 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +2 -0
- package/dist/report/bundle.esm.js +196 -9
- package/dist/report/flow.js +197 -10
- package/dist/report/standalone.js +197 -10
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/package.json +15 -13
- package/readme.md +3 -0
- package/report/assets/styles.css +179 -5
- package/report/assets/templates.html +14 -0
- package/report/renderer/components.js +9 -3
- package/report/renderer/details-renderer.d.ts +5 -0
- package/report/renderer/details-renderer.js +24 -0
- package/report/renderer/dom.d.ts +12 -1
- package/report/renderer/dom.js +26 -1
- package/report/renderer/i18n-formatter.d.ts +1 -1
- package/report/renderer/performance-category-renderer.d.ts +10 -0
- package/report/renderer/performance-category-renderer.js +81 -20
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/topbar-features.js +7 -0
- package/shared/localization/locales/ar-XB.json +74 -26
- package/shared/localization/locales/ar.json +76 -28
- package/shared/localization/locales/bg.json +74 -26
- package/shared/localization/locales/ca.json +74 -26
- package/shared/localization/locales/cs.json +74 -26
- package/shared/localization/locales/da.json +74 -26
- package/shared/localization/locales/de.json +75 -27
- package/shared/localization/locales/el.json +74 -26
- package/shared/localization/locales/en-GB.json +74 -26
- package/shared/localization/locales/en-US.json +288 -30
- package/shared/localization/locales/en-XA.json +48 -24
- package/shared/localization/locales/en-XL.json +288 -30
- package/shared/localization/locales/es-419.json +74 -26
- package/shared/localization/locales/es.json +74 -26
- package/shared/localization/locales/fi.json +74 -26
- package/shared/localization/locales/fil.json +75 -27
- package/shared/localization/locales/fr.json +74 -26
- package/shared/localization/locales/he.json +82 -34
- package/shared/localization/locales/hi.json +74 -26
- package/shared/localization/locales/hr.json +75 -27
- package/shared/localization/locales/hu.json +74 -26
- package/shared/localization/locales/id.json +74 -26
- package/shared/localization/locales/it.json +85 -37
- package/shared/localization/locales/ja.json +75 -27
- package/shared/localization/locales/ko.json +75 -27
- package/shared/localization/locales/lt.json +75 -27
- package/shared/localization/locales/lv.json +74 -26
- package/shared/localization/locales/nl.json +74 -26
- package/shared/localization/locales/no.json +75 -27
- package/shared/localization/locales/pl.json +74 -26
- package/shared/localization/locales/pt-PT.json +74 -26
- package/shared/localization/locales/pt.json +74 -26
- package/shared/localization/locales/ro.json +74 -26
- package/shared/localization/locales/ru.json +74 -26
- package/shared/localization/locales/sk.json +74 -26
- package/shared/localization/locales/sl.json +74 -26
- package/shared/localization/locales/sr-Latn.json +74 -26
- package/shared/localization/locales/sr.json +74 -26
- package/shared/localization/locales/sv.json +74 -26
- package/shared/localization/locales/ta.json +74 -26
- package/shared/localization/locales/te.json +74 -26
- package/shared/localization/locales/th.json +76 -28
- package/shared/localization/locales/tr.json +74 -26
- package/shared/localization/locales/uk.json +74 -26
- package/shared/localization/locales/vi.json +74 -26
- package/shared/localization/locales/zh-HK.json +75 -27
- package/shared/localization/locales/zh-TW.json +74 -26
- package/shared/localization/locales/zh.json +74 -26
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +3 -0
- package/types/artifacts.d.ts +5 -3
- package/types/audit.d.ts +2 -0
- package/types/lhr/audit-details.d.ts +13 -1
- package/types/lhr/audit-result.d.ts +2 -0
- package/core/gather/gatherers/root-causes.d.ts +0 -19
- package/core/gather/gatherers/root-causes.js +0 -144
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {UIStrings as InsightUIStrings} from '@paulirish/trace_engine/models/trace/insights/CLSCulprits.js';
|
|
8
|
+
|
|
9
|
+
import {Audit} from '../audit.js';
|
|
10
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
11
|
+
import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js';
|
|
12
|
+
import TraceElements from '../../gather/gatherers/trace-elements.js';
|
|
13
|
+
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
14
|
+
|
|
15
|
+
const MAX_LAYOUT_SHIFTS_PER_CLUSTER = 5;
|
|
16
|
+
|
|
17
|
+
/** @typedef {{extra?: LH.Audit.Details.NodeValue | LH.Audit.Details.UrlValue, cause: LH.IcuMessage}} SubItem */
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line max-len
|
|
20
|
+
const insightStr_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js', InsightUIStrings);
|
|
21
|
+
|
|
22
|
+
/* eslint-disable max-len */
|
|
23
|
+
const UIStrings = {
|
|
24
|
+
/** Label for a column in a data table; entries in this column will be a number representing how large the layout shift was. */
|
|
25
|
+
columnScore: 'Layout shift score',
|
|
26
|
+
};
|
|
27
|
+
/* eslint-enable max-len */
|
|
28
|
+
|
|
29
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
30
|
+
|
|
31
|
+
class CLSCulpritsInsight extends Audit {
|
|
32
|
+
/**
|
|
33
|
+
* @return {LH.Audit.Meta}
|
|
34
|
+
*/
|
|
35
|
+
static get meta() {
|
|
36
|
+
return {
|
|
37
|
+
id: 'cls-culprits-insight',
|
|
38
|
+
title: insightStr_(InsightUIStrings.title),
|
|
39
|
+
failureTitle: insightStr_(InsightUIStrings.title),
|
|
40
|
+
description: insightStr_(InsightUIStrings.description),
|
|
41
|
+
guidanceLevel: 3,
|
|
42
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
43
|
+
replacesAudits: ['layout-shifts', 'non-composited-animations', 'unsized-images'],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {import('@paulirish/trace_engine/models/trace/insights/CLSCulprits.js').CLSCulpritsInsightModel} insight
|
|
49
|
+
* @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event
|
|
50
|
+
* @param {LH.Artifacts.TraceElement[]} TraceElements
|
|
51
|
+
* @return {LH.Audit.Details.TableSubItems|undefined}
|
|
52
|
+
*/
|
|
53
|
+
static getCulpritSubItems(insight, event, TraceElements) {
|
|
54
|
+
const culprits = insight.shifts.get(event);
|
|
55
|
+
if (!culprits) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @type {SubItem[]} */
|
|
60
|
+
const subItems = [];
|
|
61
|
+
for (const backendNodeId of culprits.unsizedImages) {
|
|
62
|
+
subItems.push({
|
|
63
|
+
extra: makeNodeItemForNodeId(TraceElements, backendNodeId),
|
|
64
|
+
cause: insightStr_(InsightUIStrings.unsizedImages),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
for (const request of culprits.fontRequests) {
|
|
68
|
+
const url = request.args.data.url;
|
|
69
|
+
subItems.push({
|
|
70
|
+
extra: {type: 'url', value: url},
|
|
71
|
+
cause: insightStr_(InsightUIStrings.fontRequest),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (culprits.iframeIds.length) {
|
|
75
|
+
subItems.push({
|
|
76
|
+
cause: insightStr_(InsightUIStrings.injectedIframe),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (subItems.length) {
|
|
81
|
+
return {type: 'subitems', items: subItems};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {LH.Artifacts} artifacts
|
|
87
|
+
* @param {LH.Audit.Context} context
|
|
88
|
+
* @return {Promise<LH.Audit.Product>}
|
|
89
|
+
*/
|
|
90
|
+
static async audit(artifacts, context) {
|
|
91
|
+
return adaptInsightToAuditProduct(artifacts, context, 'CLSCulprits', (insight) => {
|
|
92
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
93
|
+
const headings = [
|
|
94
|
+
/* eslint-disable max-len */
|
|
95
|
+
{key: 'node', valueType: 'node', subItemsHeading: {key: 'extra'}, label: insightStr_(i18n.UIStrings.columnElement)},
|
|
96
|
+
{key: 'score', valueType: 'numeric', subItemsHeading: {key: 'cause', valueType: 'text'}, granularity: 0.001, label: str_(UIStrings.columnScore)},
|
|
97
|
+
/* eslint-enable max-len */
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const tables = insight.clusters.map(cluster => {
|
|
101
|
+
const events =
|
|
102
|
+
/** @type {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift[]} */ (
|
|
103
|
+
cluster.events.filter(e => !!e.args.data)
|
|
104
|
+
).sort((a, b) => b.args.data.weighted_score_delta - a.args.data.weighted_score_delta)
|
|
105
|
+
.slice(0, MAX_LAYOUT_SHIFTS_PER_CLUSTER);
|
|
106
|
+
const impactByNodeId = CumulativeLayoutShift.getImpactByNodeId(events.map(e => ({
|
|
107
|
+
impactedNodes: e.args.data.impacted_nodes,
|
|
108
|
+
ts: e.ts,
|
|
109
|
+
isMainFrame: e.args.data.is_main_frame,
|
|
110
|
+
weightedScore: e.args.data.weighted_score_delta,
|
|
111
|
+
event: /** @type {any} */ (e),
|
|
112
|
+
})));
|
|
113
|
+
|
|
114
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
115
|
+
const items = events.map(event => {
|
|
116
|
+
const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
|
|
117
|
+
event.args.data.impacted_nodes || [], impactByNodeId, event);
|
|
118
|
+
return {
|
|
119
|
+
node: makeNodeItemForNodeId(artifacts.TraceElements, biggestImpactNodeId),
|
|
120
|
+
score: event.args.data?.weighted_score_delta,
|
|
121
|
+
subItems: this.getCulpritSubItems(insight, event, artifacts.TraceElements),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
items.unshift({
|
|
125
|
+
node: {type: 'text', value: insightStr_(i18n.UIStrings.total)},
|
|
126
|
+
score: cluster.clusterCumulativeScore,
|
|
127
|
+
});
|
|
128
|
+
return Audit.makeTableDetails(headings, items);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return Audit.makeListDetails(tables);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default CLSCulpritsInsight;
|
|
137
|
+
export {UIStrings};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default DocumentLatencyInsight;
|
|
2
|
+
declare class DocumentLatencyInsight extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from '../audit.js';
|
|
11
|
+
//# sourceMappingURL=document-latency-insight.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/DocumentLatency.js';
|
|
8
|
+
|
|
9
|
+
import {Audit} from '../audit.js';
|
|
10
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
11
|
+
import {adaptInsightToAuditProduct} from './insight-audit.js';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js', UIStrings);
|
|
15
|
+
|
|
16
|
+
class DocumentLatencyInsight extends Audit {
|
|
17
|
+
/**
|
|
18
|
+
* @return {LH.Audit.Meta}
|
|
19
|
+
*/
|
|
20
|
+
static get meta() {
|
|
21
|
+
return {
|
|
22
|
+
id: 'document-latency-insight',
|
|
23
|
+
title: str_(UIStrings.title),
|
|
24
|
+
failureTitle: str_(UIStrings.title),
|
|
25
|
+
description: str_(UIStrings.description),
|
|
26
|
+
guidanceLevel: 3,
|
|
27
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
28
|
+
replacesAudits: ['redirects', 'server-response-time', 'uses-text-compression'],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {LH.Artifacts} artifacts
|
|
34
|
+
* @param {LH.Audit.Context} context
|
|
35
|
+
* @return {Promise<LH.Audit.Product>}
|
|
36
|
+
*/
|
|
37
|
+
static async audit(artifacts, context) {
|
|
38
|
+
return adaptInsightToAuditProduct(artifacts, context, 'DocumentLatency', (insight) => {
|
|
39
|
+
if (!insight.data) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Audit.makeChecklistDetails(insight.data.checklist);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default DocumentLatencyInsight;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default DOMSizeInsight;
|
|
2
|
+
declare class DOMSizeInsight extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from '../audit.js';
|
|
11
|
+
//# sourceMappingURL=dom-size-insight.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/DOMSize.js';
|
|
8
|
+
|
|
9
|
+
import {Audit} from '../audit.js';
|
|
10
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
11
|
+
import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js', UIStrings);
|
|
15
|
+
|
|
16
|
+
class DOMSizeInsight extends Audit {
|
|
17
|
+
/**
|
|
18
|
+
* @return {LH.Audit.Meta}
|
|
19
|
+
*/
|
|
20
|
+
static get meta() {
|
|
21
|
+
return {
|
|
22
|
+
id: 'dom-size-insight',
|
|
23
|
+
title: str_(UIStrings.title),
|
|
24
|
+
failureTitle: str_(UIStrings.title),
|
|
25
|
+
description: str_(UIStrings.description),
|
|
26
|
+
guidanceLevel: 3,
|
|
27
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
28
|
+
replacesAudits: ['dom-size'],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {LH.Artifacts} artifacts
|
|
34
|
+
* @param {LH.Audit.Context} context
|
|
35
|
+
* @return {Promise<LH.Audit.Product>}
|
|
36
|
+
*/
|
|
37
|
+
static async audit(artifacts, context) {
|
|
38
|
+
return adaptInsightToAuditProduct(artifacts, context, 'DOMSize', (insight) => {
|
|
39
|
+
if (!insight.maxDOMStats?.args.data.maxChildren || !insight.maxDOMStats?.args.data.maxDepth) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const {totalElements, maxChildren, maxDepth} = insight.maxDOMStats.args.data;
|
|
44
|
+
|
|
45
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
46
|
+
const headings = [
|
|
47
|
+
{key: 'statistic', valueType: 'text', label: str_(UIStrings.statistic)},
|
|
48
|
+
{key: 'node', valueType: 'node', label: str_(UIStrings.element)},
|
|
49
|
+
{key: 'value', valueType: 'numeric', label: str_(UIStrings.value)},
|
|
50
|
+
];
|
|
51
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
52
|
+
const items = [
|
|
53
|
+
{
|
|
54
|
+
statistic: str_(UIStrings.totalElements),
|
|
55
|
+
value: {
|
|
56
|
+
type: 'numeric',
|
|
57
|
+
granularity: 1,
|
|
58
|
+
value: totalElements,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
statistic: str_(UIStrings.maxChildren),
|
|
63
|
+
node: makeNodeItemForNodeId(artifacts.TraceElements, maxChildren.nodeId),
|
|
64
|
+
value: {
|
|
65
|
+
type: 'numeric',
|
|
66
|
+
granularity: 1,
|
|
67
|
+
value: maxChildren.numChildren,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
statistic: str_(UIStrings.maxDOMDepth),
|
|
72
|
+
node: makeNodeItemForNodeId(artifacts.TraceElements, maxDepth.nodeId),
|
|
73
|
+
value: {
|
|
74
|
+
type: 'numeric',
|
|
75
|
+
granularity: 1,
|
|
76
|
+
value: maxDepth.depth,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
return Audit.makeTableDetails(headings, items);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default DOMSizeInsight;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default FontDisplayInsight;
|
|
2
|
+
declare class FontDisplayInsight extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from '../audit.js';
|
|
11
|
+
//# sourceMappingURL=font-display-insight.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */ // TODO: remove once implemented.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2025 Google LLC
|
|
6
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/FontDisplay.js';
|
|
10
|
+
|
|
11
|
+
import {Audit} from '../audit.js';
|
|
12
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
13
|
+
import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js';
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line max-len
|
|
16
|
+
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js', UIStrings);
|
|
17
|
+
|
|
18
|
+
class FontDisplayInsight extends Audit {
|
|
19
|
+
/**
|
|
20
|
+
* @return {LH.Audit.Meta}
|
|
21
|
+
*/
|
|
22
|
+
static get meta() {
|
|
23
|
+
return {
|
|
24
|
+
id: 'font-display-insight',
|
|
25
|
+
title: str_(UIStrings.title),
|
|
26
|
+
failureTitle: str_(UIStrings.title),
|
|
27
|
+
description: str_(UIStrings.description),
|
|
28
|
+
guidanceLevel: 3,
|
|
29
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
30
|
+
replacesAudits: ['font-display'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {LH.Artifacts} artifacts
|
|
36
|
+
* @param {LH.Audit.Context} context
|
|
37
|
+
* @return {Promise<LH.Audit.Product>}
|
|
38
|
+
*/
|
|
39
|
+
static async audit(artifacts, context) {
|
|
40
|
+
// TODO: implement.
|
|
41
|
+
return adaptInsightToAuditProduct(artifacts, context, 'FontDisplay', (insight) => {
|
|
42
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
43
|
+
const headings = [
|
|
44
|
+
];
|
|
45
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
46
|
+
const items = [
|
|
47
|
+
];
|
|
48
|
+
return Audit.makeTableDetails(headings, items);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default FontDisplayInsight;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default ForcedReflowInsight;
|
|
2
|
+
declare class ForcedReflowInsight extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from '../audit.js';
|
|
11
|
+
//# sourceMappingURL=forced-reflow-insight.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */ // TODO: remove once implemented.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2025 Google LLC
|
|
6
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ForcedReflow.js';
|
|
10
|
+
|
|
11
|
+
import {Audit} from '../audit.js';
|
|
12
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
13
|
+
import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js';
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line max-len
|
|
16
|
+
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js', UIStrings);
|
|
17
|
+
|
|
18
|
+
class ForcedReflowInsight extends Audit {
|
|
19
|
+
/**
|
|
20
|
+
* @return {LH.Audit.Meta}
|
|
21
|
+
*/
|
|
22
|
+
static get meta() {
|
|
23
|
+
return {
|
|
24
|
+
id: 'forced-reflow-insight',
|
|
25
|
+
title: str_(UIStrings.title),
|
|
26
|
+
failureTitle: str_(UIStrings.title),
|
|
27
|
+
description: str_(UIStrings.description),
|
|
28
|
+
guidanceLevel: 3,
|
|
29
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {LH.Artifacts} artifacts
|
|
35
|
+
* @param {LH.Audit.Context} context
|
|
36
|
+
* @return {Promise<LH.Audit.Product>}
|
|
37
|
+
*/
|
|
38
|
+
static async audit(artifacts, context) {
|
|
39
|
+
// TODO: implement.
|
|
40
|
+
return adaptInsightToAuditProduct(artifacts, context, 'ForcedReflow', (insight) => {
|
|
41
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
42
|
+
const headings = [
|
|
43
|
+
];
|
|
44
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
45
|
+
const items = [
|
|
46
|
+
];
|
|
47
|
+
return Audit.makeTableDetails(headings, items);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default ForcedReflowInsight;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default ImageDeliveryInsight;
|
|
2
|
+
declare class ImageDeliveryInsight extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from '../audit.js';
|
|
11
|
+
//# sourceMappingURL=image-delivery-insight.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ImageDelivery.js';
|
|
8
|
+
|
|
9
|
+
import {Audit} from '../audit.js';
|
|
10
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
11
|
+
import {adaptInsightToAuditProduct} from './insight-audit.js';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js', UIStrings);
|
|
15
|
+
|
|
16
|
+
class ImageDeliveryInsight extends Audit {
|
|
17
|
+
/**
|
|
18
|
+
* @return {LH.Audit.Meta}
|
|
19
|
+
*/
|
|
20
|
+
static get meta() {
|
|
21
|
+
return {
|
|
22
|
+
id: 'image-delivery-insight',
|
|
23
|
+
title: str_(UIStrings.title),
|
|
24
|
+
failureTitle: str_(UIStrings.title),
|
|
25
|
+
description: str_(UIStrings.description),
|
|
26
|
+
guidanceLevel: 3,
|
|
27
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
28
|
+
replacesAudits: [
|
|
29
|
+
'modern-image-formats',
|
|
30
|
+
'uses-optimized-images',
|
|
31
|
+
'efficient-animated-content',
|
|
32
|
+
'uses-responsive-images',
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {LH.Artifacts} artifacts
|
|
39
|
+
* @param {LH.Audit.Context} context
|
|
40
|
+
* @return {Promise<LH.Audit.Product>}
|
|
41
|
+
*/
|
|
42
|
+
static async audit(artifacts, context) {
|
|
43
|
+
return adaptInsightToAuditProduct(artifacts, context, 'ImageDelivery', (insight) => {
|
|
44
|
+
if (!insight.optimizableImages.length) {
|
|
45
|
+
// TODO: show UIStrings.noOptimizableImages?
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const relatedEventsMap = insight.relatedEvents && !Array.isArray(insight.relatedEvents) ?
|
|
50
|
+
insight.relatedEvents :
|
|
51
|
+
null;
|
|
52
|
+
|
|
53
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
54
|
+
const headings = [
|
|
55
|
+
/* eslint-disable max-len */
|
|
56
|
+
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL), subItemsHeading: {key: 'reason', valueType: 'text'}},
|
|
57
|
+
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
|
|
58
|
+
{key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes), subItemsHeading: {key: 'wastedBytes', valueType: 'bytes'}},
|
|
59
|
+
/* eslint-enable max-len */
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
63
|
+
const items = insight.optimizableImages.map(image => ({
|
|
64
|
+
url: image.request.args.data.url,
|
|
65
|
+
totalBytes: image.request.args.data.decodedBodyLength,
|
|
66
|
+
wastedBytes: image.byteSavings,
|
|
67
|
+
subItems: {
|
|
68
|
+
type: /** @type {const} */ ('subitems'),
|
|
69
|
+
// TODO: when strings update to remove number from "reason" uistrings, update this
|
|
70
|
+
// to use `image.optimizations.map(...)` and construct strings from the type.
|
|
71
|
+
items: (relatedEventsMap?.get(image.request) ?? []).map((reason, i) => ({
|
|
72
|
+
reason,
|
|
73
|
+
wastedBytes: image.optimizations[i].byteSavings,
|
|
74
|
+
})),
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
return Audit.makeTableDetails(headings, items);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default ImageDeliveryInsight;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {LH.Artifacts} artifacts
|
|
3
|
+
* @param {LH.Audit.Context} context
|
|
4
|
+
* @param {T} insightName
|
|
5
|
+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]) => LH.Audit.Details|undefined} createDetails
|
|
6
|
+
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
7
|
+
* @return {Promise<LH.Audit.Product>}
|
|
8
|
+
*/
|
|
9
|
+
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]) => LH.Audit.Details | undefined): Promise<LH.Audit.Product>;
|
|
10
|
+
/**
|
|
11
|
+
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
12
|
+
* @param {number|null|undefined} nodeId
|
|
13
|
+
* @return {LH.Audit.Details.NodeValue|undefined}
|
|
14
|
+
*/
|
|
15
|
+
export function makeNodeItemForNodeId(traceElements: LH.Artifacts.TraceElement[], nodeId: number | null | undefined): LH.Audit.Details.NodeValue | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
18
|
+
* @param {number|null|undefined} nodeId
|
|
19
|
+
* @param {LH.IcuMessage|string} label
|
|
20
|
+
* @return {LH.Audit.Details.Table|undefined}
|
|
21
|
+
*/
|
|
22
|
+
export function maybeMakeNodeElementTable(traceElements: LH.Artifacts.TraceElement[], nodeId: number | null | undefined, label: LH.IcuMessage | string): LH.Audit.Details.Table | undefined;
|
|
23
|
+
//# sourceMappingURL=insight-audit.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {NO_NAVIGATION} from '@paulirish/trace_engine/models/trace/types/TraceEvents.js';
|
|
8
|
+
|
|
9
|
+
import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
10
|
+
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
11
|
+
import {Audit} from '../audit.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {LH.Artifacts} artifacts
|
|
15
|
+
* @param {LH.Audit.Context} context
|
|
16
|
+
* @return {Promise<import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet|undefined>}
|
|
17
|
+
*/
|
|
18
|
+
async function getInsightSet(artifacts, context) {
|
|
19
|
+
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
20
|
+
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
21
|
+
const traceEngineResult = await TraceEngineResult.request({trace}, context);
|
|
22
|
+
|
|
23
|
+
const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
|
|
24
|
+
const key = navigationId ?? NO_NAVIGATION;
|
|
25
|
+
|
|
26
|
+
return traceEngineResult.insights.get(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {LH.Artifacts} artifacts
|
|
31
|
+
* @param {LH.Audit.Context} context
|
|
32
|
+
* @param {T} insightName
|
|
33
|
+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]) => LH.Audit.Details|undefined} createDetails
|
|
34
|
+
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
35
|
+
* @return {Promise<LH.Audit.Product>}
|
|
36
|
+
*/
|
|
37
|
+
async function adaptInsightToAuditProduct(artifacts, context, insightName, createDetails) {
|
|
38
|
+
const insights = await getInsightSet(artifacts, context);
|
|
39
|
+
if (!insights) {
|
|
40
|
+
return {
|
|
41
|
+
scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
|
|
42
|
+
score: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const insight = insights.model[insightName];
|
|
47
|
+
if (insight instanceof Error) {
|
|
48
|
+
return {
|
|
49
|
+
errorMessage: insight.message,
|
|
50
|
+
errorStack: insight.stack,
|
|
51
|
+
score: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const details = createDetails(insight);
|
|
56
|
+
if (!details || (details.type === 'table' && details.headings.length === 0)) {
|
|
57
|
+
return {
|
|
58
|
+
scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
|
|
59
|
+
score: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// This hack is to add metric adorners if an insight category links it to a metric,
|
|
64
|
+
// but doesn't output a metric savings for that metric.
|
|
65
|
+
let metricSavings = insight.metricSavings;
|
|
66
|
+
if (insight.category === 'INP' && !metricSavings?.INP) {
|
|
67
|
+
metricSavings = {...metricSavings, INP: /** @type {any} */ (0)};
|
|
68
|
+
} else if (insight.category === 'CLS' && !metricSavings?.CLS) {
|
|
69
|
+
metricSavings = {...metricSavings, CLS: /** @type {any} */ (0)};
|
|
70
|
+
} else if (insight.category === 'LCP' && !metricSavings?.LCP) {
|
|
71
|
+
metricSavings = {...metricSavings, LCP: /** @type {any} */ (0)};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let score = insight.shouldShow ? 0 : 1;
|
|
75
|
+
// TODO: change insight model to denote passing/failing/informative. Until then... hack it.
|
|
76
|
+
if (insightName === 'LCPPhases') {
|
|
77
|
+
score = metricSavings?.LCP ?? 0 >= 1000 ? 0 : 1;
|
|
78
|
+
} else if (insightName === 'InteractionToNextPaint') {
|
|
79
|
+
score = metricSavings?.INP ?? 0 >= 500 ? 0 : 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
scoreDisplayMode:
|
|
84
|
+
insight.metricSavings ? Audit.SCORING_MODES.METRIC_SAVINGS : Audit.SCORING_MODES.NUMERIC,
|
|
85
|
+
score,
|
|
86
|
+
metricSavings,
|
|
87
|
+
warnings: insight.warnings,
|
|
88
|
+
details,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
94
|
+
* @param {number|null|undefined} nodeId
|
|
95
|
+
* @return {LH.Audit.Details.NodeValue|undefined}
|
|
96
|
+
*/
|
|
97
|
+
function makeNodeItemForNodeId(traceElements, nodeId) {
|
|
98
|
+
if (typeof nodeId !== 'number') {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const traceElement =
|
|
103
|
+
traceElements.find(te => te.traceEventType === 'trace-engine' && te.nodeId === nodeId);
|
|
104
|
+
const node = traceElement?.node;
|
|
105
|
+
if (!node) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Audit.makeNodeItem(node);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
114
|
+
* @param {number|null|undefined} nodeId
|
|
115
|
+
* @param {LH.IcuMessage|string} label
|
|
116
|
+
* @return {LH.Audit.Details.Table|undefined}
|
|
117
|
+
*/
|
|
118
|
+
function maybeMakeNodeElementTable(traceElements, nodeId, label) {
|
|
119
|
+
const node = makeNodeItemForNodeId(traceElements, nodeId);
|
|
120
|
+
if (!node) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Audit.makeTableDetails([
|
|
125
|
+
{key: 'node', valueType: 'node', label},
|
|
126
|
+
], [{node}]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
adaptInsightToAuditProduct,
|
|
131
|
+
makeNodeItemForNodeId,
|
|
132
|
+
maybeMakeNodeElementTable,
|
|
133
|
+
};
|