lighthouse 12.3.0 → 12.4.0-dev.20250227
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
|
@@ -29,8 +29,6 @@ const UIStrings = {
|
|
|
29
29
|
rootCauseFontChanges: 'Web font loaded',
|
|
30
30
|
/** A possible reason why that the layout shift occured. */
|
|
31
31
|
rootCauseInjectedIframe: 'Injected iframe',
|
|
32
|
-
/** A possible reason why that the layout shift occured. */
|
|
33
|
-
rootCauseRenderBlockingRequest: 'A late network request adjusted the page layout',
|
|
34
32
|
/** Label shown per-audit to show how many layout shifts are present. The `{# shifts found}` placeholder will be replaced with the number of layout shifts. */
|
|
35
33
|
displayValueShiftsFound: `{shiftCount, plural, =1 {1 layout shift found} other {# layout shifts found}}`,
|
|
36
34
|
};
|
|
@@ -49,7 +47,7 @@ class LayoutShifts extends Audit {
|
|
|
49
47
|
description: str_(UIStrings.description),
|
|
50
48
|
scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
|
|
51
49
|
guidanceLevel: 2,
|
|
52
|
-
requiredArtifacts: ['traces', '
|
|
50
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
53
51
|
};
|
|
54
52
|
}
|
|
55
53
|
|
|
@@ -67,11 +65,21 @@ class LayoutShifts extends Audit {
|
|
|
67
65
|
const traceElements = artifacts.TraceElements
|
|
68
66
|
.filter(element => element.traceEventType === 'layout-shift');
|
|
69
67
|
|
|
68
|
+
/** @type {LH.Artifacts.TraceEngineRootCauses} */
|
|
69
|
+
const allRootCauses = {
|
|
70
|
+
layoutShifts: new Map(),
|
|
71
|
+
};
|
|
72
|
+
for (const insightSet of traceEngineResult.insights.values()) {
|
|
73
|
+
for (const [shift, reasons] of insightSet.model.CLSCulprits.shifts) {
|
|
74
|
+
allRootCauses.layoutShifts.set(shift, reasons);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
/** @type {Item[]} */
|
|
71
79
|
const items = [];
|
|
72
80
|
const layoutShiftEvents =
|
|
73
81
|
/** @type {import('../lib/trace-engine.js').SaneSyntheticLayoutShift[]} */(
|
|
74
|
-
clusters.flatMap(c => c.events)
|
|
82
|
+
clusters.flatMap(c => c.events).filter(e => !!e.args.data)
|
|
75
83
|
);
|
|
76
84
|
const topLayoutShiftEvents = layoutShiftEvents
|
|
77
85
|
.sort((a, b) => b.args.data.weighted_score_delta - a.args.data.weighted_score_delta)
|
|
@@ -82,41 +90,30 @@ class LayoutShifts extends Audit {
|
|
|
82
90
|
const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
|
|
83
91
|
|
|
84
92
|
// Turn root causes into sub-items.
|
|
85
|
-
const
|
|
86
|
-
const rootCauses = artifacts.RootCauses.layoutShifts[index];
|
|
93
|
+
const rootCauses = allRootCauses.layoutShifts.get(event);
|
|
87
94
|
/** @type {SubItem[]} */
|
|
88
95
|
const subItems = [];
|
|
89
96
|
if (rootCauses) {
|
|
90
|
-
for (const
|
|
97
|
+
for (const backendNodeId of rootCauses.unsizedImages) {
|
|
91
98
|
const element = artifacts.TraceElements.find(
|
|
92
|
-
t => t.traceEventType === '
|
|
99
|
+
t => t.traceEventType === 'trace-engine' && t.nodeId === backendNodeId);
|
|
93
100
|
subItems.push({
|
|
94
101
|
extra: element ? Audit.makeNodeItem(element.node) : undefined,
|
|
95
102
|
cause: str_(UIStrings.rootCauseUnsizedMedia),
|
|
96
103
|
});
|
|
97
104
|
}
|
|
98
|
-
for (const
|
|
99
|
-
const url =
|
|
105
|
+
for (const request of rootCauses.fontRequests) {
|
|
106
|
+
const url = request.args.data.url;
|
|
100
107
|
subItems.push({
|
|
101
108
|
extra: {type: 'url', value: url},
|
|
102
109
|
cause: str_(UIStrings.rootCauseFontChanges),
|
|
103
110
|
});
|
|
104
111
|
}
|
|
105
|
-
|
|
106
|
-
const element = artifacts.TraceElements.find(
|
|
107
|
-
t => t.traceEventType === 'layout-shift' && t.nodeId === cause.iframe.backendNodeId);
|
|
112
|
+
if (rootCauses.iframeIds.length) {
|
|
108
113
|
subItems.push({
|
|
109
|
-
extra: element ? Audit.makeNodeItem(element.node) : undefined,
|
|
110
114
|
cause: str_(UIStrings.rootCauseInjectedIframe),
|
|
111
115
|
});
|
|
112
116
|
}
|
|
113
|
-
for (const cause of rootCauses.renderBlockingRequests) {
|
|
114
|
-
const url = cause.request.args.data.url;
|
|
115
|
-
subItems.push({
|
|
116
|
-
extra: {type: 'url', value: url},
|
|
117
|
-
cause: str_(UIStrings.rootCauseRenderBlockingRequest),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
items.push({
|
|
@@ -16,7 +16,6 @@ import * as i18n from '../lib/i18n/i18n.js';
|
|
|
16
16
|
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
|
|
17
17
|
import {TotalBlockingTime} from '../computed/metrics/total-blocking-time.js';
|
|
18
18
|
import {Sentry} from '../lib/sentry.js';
|
|
19
|
-
import {Util} from '../../shared/util.js';
|
|
20
19
|
|
|
21
20
|
const UIStrings = {
|
|
22
21
|
/** Title of a diagnostic audit that provides detail on the main thread work the browser did to load the page. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
@@ -142,7 +141,6 @@ class MainThreadWorkBreakdown extends Audit {
|
|
|
142
141
|
|
|
143
142
|
return {
|
|
144
143
|
score,
|
|
145
|
-
scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
|
|
146
144
|
numericValue: totalExecutionTime,
|
|
147
145
|
numericUnit: 'millisecond',
|
|
148
146
|
displayValue: str_(i18n.UIStrings.seconds, {timeInMs: totalExecutionTime}),
|
|
@@ -39,7 +39,11 @@ async function getComputationDataParamsFromTrace(data, context) {
|
|
|
39
39
|
const graph = await PageDependencyGraph.request({...data, fromTrace: true}, context);
|
|
40
40
|
const traceEngineResult = await TraceEngineResult.request(data, context);
|
|
41
41
|
const frameId = traceEngineResult.data.Meta.mainFrameId;
|
|
42
|
-
const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data
|
|
42
|
+
const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data?.navigationId;
|
|
43
|
+
if (!navigationId) {
|
|
44
|
+
throw new Error(`Lantern metrics could not be calculated due to missing navigation id`);
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
const processedNavigation = Lantern.TraceEngineComputationData.createProcessedNavigation(
|
|
44
48
|
traceEngineResult.data, frameId, navigationId);
|
|
45
49
|
const simulator = data.simulator || (await LoadSimulator.request(data, context));
|
|
@@ -20,15 +20,7 @@ class TraceEngineResult {
|
|
|
20
20
|
* @return {Promise<LH.Artifacts.TraceEngineResult>}
|
|
21
21
|
*/
|
|
22
22
|
static async runTraceEngine(traceEvents) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
// @ts-expect-error Temporarily disable this handler
|
|
26
|
-
// It's not currently used anywhere in trace engine insights or Lighthouse.
|
|
27
|
-
// TODO: Re-enable this when its memory usage is improved in the trace engine
|
|
28
|
-
// https://github.com/GoogleChrome/lighthouse/issues/16111
|
|
29
|
-
delete traceHandlers.Invalidations;
|
|
30
|
-
|
|
31
|
-
const processor = new TraceEngine.TraceProcessor(traceHandlers);
|
|
23
|
+
const processor = new TraceEngine.TraceProcessor(TraceEngine.TraceHandlers);
|
|
32
24
|
|
|
33
25
|
// eslint-disable-next-line max-len
|
|
34
26
|
await processor.parse(/** @type {import('@paulirish/trace_engine').Types.Events.Event[]} */ (
|
|
@@ -44,22 +36,84 @@ class TraceEngineResult {
|
|
|
44
36
|
* @param {import('@paulirish/trace_engine/models/trace/insights/types.js').TraceInsightSets} insightSets
|
|
45
37
|
*/
|
|
46
38
|
static localizeInsights(insightSets) {
|
|
39
|
+
/**
|
|
40
|
+
* Execute `cb(traceEngineI18nObject)` on every i18n object, recursively. The cb return
|
|
41
|
+
* value replaces traceEngineI18nObject.
|
|
42
|
+
* @param {any} obj
|
|
43
|
+
* @param {(traceEngineI18nObject: {i18nId: string, values?: {}}) => LH.IcuMessage} cb
|
|
44
|
+
* @param {Set<object>} seen
|
|
45
|
+
*/
|
|
46
|
+
function recursiveReplaceLocalizableStrings(obj, cb, seen) {
|
|
47
|
+
if (seen.has(seen)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
seen.add(obj);
|
|
52
|
+
|
|
53
|
+
if (obj instanceof Map) {
|
|
54
|
+
for (const [key, value] of obj) {
|
|
55
|
+
if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
56
|
+
obj.set(key, cb(value));
|
|
57
|
+
} else {
|
|
58
|
+
recursiveReplaceLocalizableStrings(value, cb, seen);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
62
|
+
Object.keys(obj).forEach(key => {
|
|
63
|
+
const value = obj[key];
|
|
64
|
+
if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
65
|
+
obj[key] = cb(value);
|
|
66
|
+
} else {
|
|
67
|
+
recursiveReplaceLocalizableStrings(value, cb, seen);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
} else if (Array.isArray(obj)) {
|
|
71
|
+
for (let i = 0; i < obj.length; i++) {
|
|
72
|
+
const value = obj[i];
|
|
73
|
+
if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
74
|
+
obj[i] = cb(value);
|
|
75
|
+
} else {
|
|
76
|
+
recursiveReplaceLocalizableStrings(value, cb, seen);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
47
82
|
for (const insightSet of insightSets.values()) {
|
|
48
83
|
for (const [name, model] of Object.entries(insightSet.model)) {
|
|
49
84
|
if (model instanceof Error) {
|
|
50
85
|
continue;
|
|
51
86
|
}
|
|
52
87
|
|
|
88
|
+
/** @type {Record<string, string>} */
|
|
89
|
+
let traceEngineUIStrings;
|
|
90
|
+
if (name in TraceEngine.Insights.Models) {
|
|
91
|
+
const nameAsKey = /** @type {keyof typeof TraceEngine.Insights.Models} */ (name);
|
|
92
|
+
traceEngineUIStrings = TraceEngine.Insights.Models[nameAsKey].UIStrings;
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error(`insight missing UIStrings: ${name}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
53
97
|
const key = `node_modules/@paulirish/trace_engine/models/trace/insights/${name}.js`;
|
|
54
|
-
const str_ = i18n.createIcuMessageFn(key,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
98
|
+
const str_ = i18n.createIcuMessageFn(key, traceEngineUIStrings);
|
|
99
|
+
|
|
100
|
+
// Pass `{i18nId: string, values?: {}}` through Lighthouse's i18n pipeline.
|
|
101
|
+
// This is equivalent to if we directly did `str_(UIStrings.whatever, ...)`
|
|
102
|
+
recursiveReplaceLocalizableStrings(model, (traceEngineI18nObject) => {
|
|
103
|
+
let values = traceEngineI18nObject.values;
|
|
104
|
+
if (values) {
|
|
105
|
+
values = structuredClone(values);
|
|
106
|
+
for (const [key, value] of Object.entries(values)) {
|
|
107
|
+
if (value && typeof value === 'object' && '__i18nBytes' in value) {
|
|
108
|
+
// @ts-expect-error
|
|
109
|
+
values[key] = value.__i18nBytes;
|
|
110
|
+
// TODO: use an actual byte formatter. Right now, this shows the exact number of bytes.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
58
114
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// @ts-expect-error coerce to string, should be fine
|
|
62
|
-
model.description = str_(model.description);
|
|
115
|
+
return str_(traceEngineI18nObject.i18nId, values);
|
|
116
|
+
}, new Set());
|
|
63
117
|
}
|
|
64
118
|
}
|
|
65
119
|
}
|
|
@@ -15,6 +15,8 @@ const UIStrings = {
|
|
|
15
15
|
performanceCategoryTitle: 'Performance',
|
|
16
16
|
/** Title of the speed metrics section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. */
|
|
17
17
|
metricGroupTitle: 'Metrics',
|
|
18
|
+
/** Title of the insights section of the Performance category. Within this section are various insights to give developers tips on how to improve the performance of their page. */
|
|
19
|
+
insightGroupTitle: 'Insights',
|
|
18
20
|
/** Title of an opportunity sub-section of the Performance category. Within this section are audits with imperative titles that suggest actions the user can take to improve the time of the first initial render of the webpage. */
|
|
19
21
|
firstPaintImprovementsGroupTitle: 'First Paint Improvements',
|
|
20
22
|
/** Description of an opportunity sub-section of the Performance category. Within this section are audits with imperative titles that suggest actions the user can take to improve the time of the first initial render of the webpage. */
|
|
@@ -107,7 +109,6 @@ const defaultConfig = {
|
|
|
107
109
|
// Artifacts which can be depended on come first.
|
|
108
110
|
{id: 'DevtoolsLog', gatherer: 'devtools-log'},
|
|
109
111
|
{id: 'Trace', gatherer: 'trace'},
|
|
110
|
-
{id: 'RootCauses', gatherer: 'root-causes'},
|
|
111
112
|
|
|
112
113
|
{id: 'Accessibility', gatherer: 'accessibility'},
|
|
113
114
|
{id: 'AnchorElements', gatherer: 'anchor-elements'},
|
|
@@ -194,6 +195,7 @@ const defaultConfig = {
|
|
|
194
195
|
'csp-xss',
|
|
195
196
|
'has-hsts',
|
|
196
197
|
'origin-isolation',
|
|
198
|
+
'clickjacking-mitigation',
|
|
197
199
|
'script-treemap-data',
|
|
198
200
|
'accessibility/accesskeys',
|
|
199
201
|
'accessibility/aria-allowed-attr',
|
|
@@ -307,11 +309,28 @@ const defaultConfig = {
|
|
|
307
309
|
'seo/manual/structured-data',
|
|
308
310
|
'work-during-interaction',
|
|
309
311
|
'bf-cache',
|
|
312
|
+
'insights/cls-culprits-insight',
|
|
313
|
+
'insights/document-latency-insight',
|
|
314
|
+
'insights/dom-size-insight',
|
|
315
|
+
'insights/font-display-insight',
|
|
316
|
+
'insights/forced-reflow-insight',
|
|
317
|
+
'insights/image-delivery-insight',
|
|
318
|
+
'insights/interaction-to-next-paint-insight',
|
|
319
|
+
'insights/lcp-discovery-insight',
|
|
320
|
+
'insights/lcp-phases-insight',
|
|
321
|
+
'insights/long-critical-network-tree-insight',
|
|
322
|
+
'insights/render-blocking-insight',
|
|
323
|
+
'insights/slow-css-selector-insight',
|
|
324
|
+
'insights/third-parties-insight',
|
|
325
|
+
'insights/viewport-insight',
|
|
310
326
|
],
|
|
311
327
|
groups: {
|
|
312
328
|
'metrics': {
|
|
313
329
|
title: str_(UIStrings.metricGroupTitle),
|
|
314
330
|
},
|
|
331
|
+
'insights': {
|
|
332
|
+
title: str_(UIStrings.insightGroupTitle),
|
|
333
|
+
},
|
|
315
334
|
'diagnostics': {
|
|
316
335
|
title: str_(UIStrings.diagnosticsGroupTitle),
|
|
317
336
|
description: str_(UIStrings.diagnosticsGroupDescription),
|
|
@@ -387,6 +406,22 @@ const defaultConfig = {
|
|
|
387
406
|
{id: 'speed-index', weight: 10, group: 'metrics', acronym: 'SI'},
|
|
388
407
|
{id: 'interaction-to-next-paint', weight: 0, group: 'metrics', acronym: 'INP'},
|
|
389
408
|
|
|
409
|
+
// Insight audits.
|
|
410
|
+
{id: 'cls-culprits-insight', weight: 0, group: 'insights'},
|
|
411
|
+
{id: 'document-latency-insight', weight: 0, group: 'insights'},
|
|
412
|
+
{id: 'dom-size-insight', weight: 0, group: 'insights'},
|
|
413
|
+
{id: 'font-display-insight', weight: 0, group: 'insights'},
|
|
414
|
+
{id: 'forced-reflow-insight', weight: 0, group: 'insights'},
|
|
415
|
+
{id: 'image-delivery-insight', weight: 0, group: 'insights'},
|
|
416
|
+
{id: 'interaction-to-next-paint-insight', weight: 0, group: 'insights'},
|
|
417
|
+
{id: 'lcp-discovery-insight', weight: 0, group: 'insights'},
|
|
418
|
+
{id: 'lcp-phases-insight', weight: 0, group: 'insights'},
|
|
419
|
+
{id: 'long-critical-network-tree-insight', weight: 0, group: 'insights'},
|
|
420
|
+
{id: 'render-blocking-insight', weight: 0, group: 'insights'},
|
|
421
|
+
{id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
|
|
422
|
+
{id: 'third-parties-insight', weight: 0, group: 'insights'},
|
|
423
|
+
{id: 'viewport-insight', weight: 0, group: 'insights'},
|
|
424
|
+
|
|
390
425
|
// These are our "invisible" metrics. Not displayed, but still in the LHR.
|
|
391
426
|
{id: 'interactive', weight: 0, group: 'hidden', acronym: 'TTI'},
|
|
392
427
|
{id: 'max-potential-fid', weight: 0, group: 'hidden'},
|
|
@@ -545,6 +580,7 @@ const defaultConfig = {
|
|
|
545
580
|
{id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
|
|
546
581
|
{id: 'has-hsts', weight: 0, group: 'best-practices-trust-safety'},
|
|
547
582
|
{id: 'origin-isolation', weight: 0, group: 'best-practices-trust-safety'},
|
|
583
|
+
{id: 'clickjacking-mitigation', weight: 0, group: 'best-practices-trust-safety'},
|
|
548
584
|
// User Experience
|
|
549
585
|
{id: 'paste-preventing-inputs', weight: 3, group: 'best-practices-ux'},
|
|
550
586
|
{id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
|
|
@@ -76,12 +76,15 @@ class InspectorIssues extends BaseGatherer {
|
|
|
76
76
|
lowTextContrastIssue: [],
|
|
77
77
|
mixedContentIssue: [],
|
|
78
78
|
navigatorUserAgentIssue: [],
|
|
79
|
+
partitioningBlobURLIssue: [],
|
|
79
80
|
propertyRuleIssue: [],
|
|
80
81
|
quirksModeIssue: [],
|
|
81
82
|
cookieIssue: [],
|
|
83
|
+
selectElementAccessibilityIssue: [],
|
|
82
84
|
sharedArrayBufferIssue: [],
|
|
83
85
|
sharedDictionaryIssue: [],
|
|
84
86
|
stylesheetLoadingIssue: [],
|
|
87
|
+
sriMessageSignatureIssue: [],
|
|
85
88
|
federatedAuthUserInfoRequestIssue: [],
|
|
86
89
|
};
|
|
87
90
|
const keys = /** @type {Array<keyof LH.Artifacts['InspectorIssues']>} */(Object.keys(artifact));
|
|
@@ -9,6 +9,14 @@ export type TraceElementData = {
|
|
|
9
9
|
type?: string;
|
|
10
10
|
};
|
|
11
11
|
declare class TraceElements extends BaseGatherer {
|
|
12
|
+
/**
|
|
13
|
+
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
14
|
+
* @param {string|undefined} navigationId
|
|
15
|
+
* @return {Promise<Array<{nodeId: number}>>}
|
|
16
|
+
*/
|
|
17
|
+
static getTraceEngineElements(traceEngineResult: LH.Artifacts.TraceEngineResult, navigationId: string | undefined): Promise<Array<{
|
|
18
|
+
nodeId: number;
|
|
19
|
+
}>>;
|
|
12
20
|
/**
|
|
13
21
|
* We want to a single representative node to represent the shift, so let's pick
|
|
14
22
|
* the one with the largest impact (size x distance moved).
|
|
@@ -48,8 +56,8 @@ declare class TraceElements extends BaseGatherer {
|
|
|
48
56
|
nodeId: number;
|
|
49
57
|
type: string;
|
|
50
58
|
} | undefined>;
|
|
51
|
-
/** @type {LH.Gatherer.GathererMeta<'Trace'
|
|
52
|
-
meta: LH.Gatherer.GathererMeta<"Trace"
|
|
59
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
60
|
+
meta: LH.Gatherer.GathererMeta<"Trace">;
|
|
53
61
|
/** @type {Map<string, string>} */
|
|
54
62
|
animationIdToName: Map<string, string>;
|
|
55
63
|
/** @param {LH.Crdp.Animation.AnimationStartedEvent} args */
|
|
@@ -23,7 +23,6 @@ import {LighthouseError} from '../../lib/lh-error.js';
|
|
|
23
23
|
import {Responsiveness} from '../../computed/metrics/responsiveness.js';
|
|
24
24
|
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
25
25
|
import {ExecutionContext} from '../driver/execution-context.js';
|
|
26
|
-
import RootCauses from './root-causes.js';
|
|
27
26
|
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
28
27
|
|
|
29
28
|
/** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
|
|
@@ -46,10 +45,10 @@ function getNodeDetailsData() {
|
|
|
46
45
|
/* c8 ignore stop */
|
|
47
46
|
|
|
48
47
|
class TraceElements extends BaseGatherer {
|
|
49
|
-
/** @type {LH.Gatherer.GathererMeta<'Trace'
|
|
48
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
50
49
|
meta = {
|
|
51
50
|
supportedModes: ['timespan', 'navigation'],
|
|
52
|
-
dependencies: {Trace: Trace.symbol
|
|
51
|
+
dependencies: {Trace: Trace.symbol},
|
|
53
52
|
};
|
|
54
53
|
|
|
55
54
|
/** @type {Map<string, string>} */
|
|
@@ -65,6 +64,87 @@ class TraceElements extends BaseGatherer {
|
|
|
65
64
|
if (name) this.animationIdToName.set(id, name);
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
69
|
+
* @param {string|undefined} navigationId
|
|
70
|
+
* @return {Promise<Array<{nodeId: number}>>}
|
|
71
|
+
*/
|
|
72
|
+
static async getTraceEngineElements(traceEngineResult, navigationId) {
|
|
73
|
+
// Can only resolve elements for the latest insight set, which should correspond
|
|
74
|
+
// to the current navigation id (if present). Can't resolve elements for pages
|
|
75
|
+
// that are gone.
|
|
76
|
+
const insightSet = [...traceEngineResult.insights.values()].at(-1);
|
|
77
|
+
if (!insightSet) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (navigationId) {
|
|
82
|
+
if (insightSet.navigation?.args.data?.navigationId !== navigationId) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
if (insightSet.navigation) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Execute `cb(obj, key)` on every object property (non-objects only), recursively.
|
|
93
|
+
* @param {any} obj
|
|
94
|
+
* @param {(obj: Record<string, unknown>, key: string) => void} cb
|
|
95
|
+
* @param {Set<object>} seen
|
|
96
|
+
*/
|
|
97
|
+
function recursiveObjectEnumerate(obj, cb, seen) {
|
|
98
|
+
if (seen.has(seen)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
seen.add(obj);
|
|
103
|
+
|
|
104
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
105
|
+
if (obj instanceof Map) {
|
|
106
|
+
for (const [key, val] of obj) {
|
|
107
|
+
if (typeof val === 'object') {
|
|
108
|
+
recursiveObjectEnumerate(val, cb, seen);
|
|
109
|
+
} else {
|
|
110
|
+
cb(val, key);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
Object.keys(obj).forEach(key => {
|
|
115
|
+
if (typeof obj[key] === 'object') {
|
|
116
|
+
recursiveObjectEnumerate(obj[key], cb, seen);
|
|
117
|
+
} else {
|
|
118
|
+
cb(obj[key], key);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
} else if (Array.isArray(obj)) {
|
|
123
|
+
obj.forEach(item => {
|
|
124
|
+
if (typeof item === 'object' || Array.isArray(item)) {
|
|
125
|
+
recursiveObjectEnumerate(item, cb, seen);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @type {number[]} */
|
|
132
|
+
const nodeIds = [];
|
|
133
|
+
recursiveObjectEnumerate(insightSet.model, (val, key) => {
|
|
134
|
+
const keys = ['nodeId', 'node_id'];
|
|
135
|
+
if (typeof val === 'number' && keys.includes(key)) {
|
|
136
|
+
nodeIds.push(val);
|
|
137
|
+
}
|
|
138
|
+
}, new Set());
|
|
139
|
+
|
|
140
|
+
// TODO: would be better if unsizedImages was `Array<{nodeId}>`.
|
|
141
|
+
for (const shift of insightSet.model.CLSCulprits.shifts.values()) {
|
|
142
|
+
nodeIds.push(...shift.unsizedImages);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [...new Set(nodeIds)].map(id => ({nodeId: id}));
|
|
146
|
+
}
|
|
147
|
+
|
|
68
148
|
/**
|
|
69
149
|
* We want to a single representative node to represent the shift, so let's pick
|
|
70
150
|
* the one with the largest impact (size x distance moved).
|
|
@@ -147,14 +227,6 @@ class TraceElements extends BaseGatherer {
|
|
|
147
227
|
nodeIds.push(biggestImpactedNodeId);
|
|
148
228
|
}
|
|
149
229
|
|
|
150
|
-
const index = layoutShiftEvents.indexOf(event);
|
|
151
|
-
const shiftRootCauses = rootCauses.layoutShifts[index];
|
|
152
|
-
if (shiftRootCauses) {
|
|
153
|
-
for (const cause of shiftRootCauses.unsizedMedia) {
|
|
154
|
-
nodeIds.push(cause.node.backendNodeId);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
230
|
return nodeIds.map(nodeId => ({nodeId}));
|
|
159
231
|
});
|
|
160
232
|
}
|
|
@@ -319,7 +391,10 @@ class TraceElements extends BaseGatherer {
|
|
|
319
391
|
|
|
320
392
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
321
393
|
const {mainThreadEvents} = processedTrace;
|
|
394
|
+
const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
|
|
322
395
|
|
|
396
|
+
const traceEngineData = await TraceElements.getTraceEngineElements(
|
|
397
|
+
traceEngineResult, navigationId);
|
|
323
398
|
const lcpNodeData = await TraceElements.getLcpElement(trace, context);
|
|
324
399
|
const shiftsData = await TraceElements.getTopLayoutShifts(
|
|
325
400
|
trace, traceEngineResult.data, rootCauses, context);
|
|
@@ -328,6 +403,7 @@ class TraceElements extends BaseGatherer {
|
|
|
328
403
|
|
|
329
404
|
/** @type {Map<string, TraceElementData[]>} */
|
|
330
405
|
const backendNodeDataMap = new Map([
|
|
406
|
+
['trace-engine', traceEngineData],
|
|
331
407
|
['largest-contentful-paint', lcpNodeData ? [lcpNodeData] : []],
|
|
332
408
|
['layout-shift', shiftsData],
|
|
333
409
|
['animation', animatedElementData],
|
|
@@ -336,6 +412,7 @@ class TraceElements extends BaseGatherer {
|
|
|
336
412
|
|
|
337
413
|
/** @type {Map<number, LH.Crdp.Runtime.CallFunctionOnResponse | null>} */
|
|
338
414
|
const callFunctionOnCache = new Map();
|
|
415
|
+
/** @type {LH.Artifacts.TraceElement[]} */
|
|
339
416
|
const traceElements = [];
|
|
340
417
|
for (const [traceEventType, backendNodeData] of backendNodeDataMap) {
|
|
341
418
|
for (let i = 0; i < backendNodeData.length; i++) {
|
|
@@ -348,8 +425,8 @@ class TraceElements extends BaseGatherer {
|
|
|
348
425
|
|
|
349
426
|
if (response?.result?.value) {
|
|
350
427
|
traceElements.push({
|
|
351
|
-
traceEventType,
|
|
352
428
|
...response.result.value,
|
|
429
|
+
traceEventType,
|
|
353
430
|
animations: backendNodeData[i].animations,
|
|
354
431
|
nodeId: backendNodeId,
|
|
355
432
|
type: backendNodeData[i].type,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @type {Record<string, {name: LH.IcuMessage} | undefined>} */
|
|
1
|
+
/** @type {Record<string, {name: LH.IcuMessage|string} | undefined>} */
|
|
2
2
|
export const NotRestoredReasonDescription: Record<string, {
|
|
3
|
-
name: LH.IcuMessage;
|
|
3
|
+
name: LH.IcuMessage | string;
|
|
4
4
|
} | undefined>;
|
|
5
5
|
export namespace UIStrings {
|
|
6
6
|
let notMainFrame: string;
|
|
@@ -77,7 +77,6 @@ export namespace UIStrings {
|
|
|
77
77
|
let printing: string;
|
|
78
78
|
let webDatabase: string;
|
|
79
79
|
let pictureInPicture: string;
|
|
80
|
-
let portal: string;
|
|
81
80
|
let speechRecognizer: string;
|
|
82
81
|
let idleManager: string;
|
|
83
82
|
let paymentManager: string;
|
|
@@ -87,6 +86,7 @@ export namespace UIStrings {
|
|
|
87
86
|
let outstandingNetworkRequestDirectSocket: string;
|
|
88
87
|
let injectedJavascript: string;
|
|
89
88
|
let injectedStyleSheet: string;
|
|
89
|
+
let contentDiscarded: string;
|
|
90
90
|
let contentSecurityHandler: string;
|
|
91
91
|
let contentWebAuthenticationAPI: string;
|
|
92
92
|
let contentFileChooser: string;
|
|
@@ -117,8 +117,11 @@ export namespace UIStrings {
|
|
|
117
117
|
let errorDocument: string;
|
|
118
118
|
let fencedFramesEmbedder: string;
|
|
119
119
|
let keepaliveRequest: string;
|
|
120
|
-
let
|
|
120
|
+
let jsNetworkRequestReceivedCacheControlNoStoreResource: string;
|
|
121
121
|
let indexedDBEvent: string;
|
|
122
122
|
let cookieDisabled: string;
|
|
123
|
+
let webRTCSticky: string;
|
|
124
|
+
let webTransportSticky: string;
|
|
125
|
+
let webSocketSticky: string;
|
|
123
126
|
}
|
|
124
127
|
//# sourceMappingURL=bf-cache-strings.d.ts.map
|