lighthouse 13.5.0 → 13.6.0-dev.20261028
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/.agents/skills/update-ard-port/SKILL.md +171 -0
- package/.agents/skills/update-trace-engine/SKILL.md +154 -0
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +5 -4
- package/cli/cli-flags.d.ts +16 -1
- package/cli/cli-flags.js +53 -2
- package/cli/printer.js +5 -8
- package/cli/run.d.ts +5 -5
- package/cli/run.js +97 -47
- package/cli/sentry-prompt.js +3 -4
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
- package/cli/test/smokehouse/config/exclusions.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
- package/cli/test/smokehouse/smokehouse-test.js +120 -0
- package/cli/test/smokehouse/smokehouse.js +37 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.d.ts +1 -1
- package/cli/test/smokehouse/version-check.js +1 -1
- package/commitlint.config.js +1 -1
- package/core/audits/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-conditional-attr.js +1 -1
- package/core/audits/accessibility/aria-deprecated-role.js +1 -1
- package/core/audits/accessibility/aria-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/autocomplete-valid.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/presentation-role-conflict.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/svg-img-alt.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -1
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/agentic/ard-schema.d.ts +13 -0
- package/core/audits/agentic/ard-schema.js +81 -33
- package/core/audits/agentic/llms-txt.js +5 -2
- package/core/audits/baseline.d.ts +29 -7
- package/core/audits/baseline.js +19 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/insights/insight-audit.d.ts +9 -0
- package/core/audits/insights/insight-audit.js +35 -28
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/screenshot-thumbnails.d.ts +0 -13
- package/core/audits/screenshot-thumbnails.js +0 -45
- package/core/audits/script-treemap-data.js +26 -6
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/audits/unsized-images.d.ts +0 -7
- package/core/audits/unsized-images.js +7 -26
- package/core/computed/lcp-image-record.d.ts +2 -23
- package/core/computed/lcp-image-record.js +3 -67
- package/core/computed/lcp-network-record.d.ts +24 -0
- package/core/computed/lcp-network-record.js +74 -0
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +6 -6
- package/core/computed/metrics/time-to-first-byte.js +5 -3
- package/core/computed/navigation-insights.js +3 -2
- package/core/computed/page-dependency-graph.js +55 -5
- package/core/computed/speedline.js +20 -5
- package/core/computed/trace-engine-result.js +25 -5
- package/core/computed/unused-css.js +15 -2
- package/core/config/config-helpers.d.ts +5 -0
- package/core/config/config-helpers.js +17 -28
- package/core/config/config-plugin.d.ts +7 -0
- package/core/config/config-plugin.js +49 -0
- package/core/config/config.js +10 -2
- package/core/config/constants.d.ts +2 -28
- package/core/config/filters.js +1 -0
- package/core/config/lr-desktop-config.js +13 -0
- package/core/config/lr-mobile-config.js +13 -0
- package/core/gather/base-artifacts.js +7 -1
- package/core/gather/driver/storage.js +3 -2
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/core/gather/gatherers/full-page-screenshot.js +2 -2
- package/core/gather/gatherers/image-elements.d.ts +7 -0
- package/core/gather/gatherers/image-elements.js +75 -24
- package/core/gather/gatherers/inspector-issues.js +2 -0
- package/core/gather/gatherers/js-usage.js +2 -0
- package/core/gather/gatherers/scripts.d.ts +16 -3
- package/core/gather/gatherers/scripts.js +157 -14
- package/core/gather/gatherers/stylesheets.js +2 -0
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/gatherers/trace.d.ts +6 -0
- package/core/gather/gatherers/trace.js +56 -16
- package/core/index.d.ts +2 -0
- package/core/index.js +2 -0
- package/core/lib/arbitrary-equality-map.d.ts +14 -3
- package/core/lib/arbitrary-equality-map.js +113 -6
- package/core/lib/baseline/web-features-data.json +32 -11
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/bf-cache-strings.js +237 -254
- package/core/lib/cdt/Common.d.ts +1 -1
- package/core/lib/cdt/Common.js +2 -2
- package/core/lib/cdt/Platform.d.ts +7 -7
- package/core/lib/cdt/Platform.js +11 -8
- package/core/lib/cdt/SDK.d.ts +5 -1
- package/core/lib/cdt/SDK.js +4 -2
- package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
- package/core/lib/cdt/generated/ParsedURL.js +3 -9
- package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
- package/core/lib/cdt/generated/SourceMap.js +7 -15
- package/core/lib/deprecations-strings.d.ts +93 -88
- package/core/lib/deprecations-strings.js +20 -14
- package/core/lib/i18n/i18n.d.ts +2 -1
- package/core/lib/i18n/i18n.js +25 -13
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
- package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
- package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
- package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
- package/core/lib/minification-estimator.js +10 -10
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +21 -0
- package/core/lib/page-functions.js +2 -5
- package/core/lib/sentry.d.ts +1 -0
- package/core/lib/sentry.js +3 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +9 -3
- package/dist/report/bundle.esm.js +10 -10
- package/dist/report/flow.js +13 -13
- package/dist/report/standalone.js +5 -5
- package/flow-report/src/common.tsx +5 -2
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/flow-report/src/summary/summary.tsx +9 -1
- package/flow-report/src/util.d.ts +1 -1
- package/package.json +62 -98
- package/readme.md +13 -18
- package/report/generator/file-namer.d.ts +1 -1
- package/report/generator/file-namer.js +5 -4
- package/report/generator/flow-report-assets.js +1 -3
- package/report/generator/report-assets.js +1 -2
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/details-renderer.js +3 -2
- package/report/renderer/dom.d.ts +8 -2
- package/report/renderer/dom.js +29 -1
- package/report/renderer/element-screenshot-renderer.js +3 -0
- package/report/renderer/performance-category-renderer.js +3 -1
- package/report/renderer/report-renderer.js +4 -0
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/text-encoding.js +2 -2
- package/report/types/augment-dom.d.ts +4 -0
- package/shared/esm-utils.d.ts +0 -8
- package/shared/esm-utils.js +0 -18
- package/shared/localization/format.js +5 -3
- package/shared/localization/locales/ar-XB.json +157 -142
- package/shared/localization/locales/ar.json +157 -142
- package/shared/localization/locales/bg.json +154 -139
- package/shared/localization/locales/ca.json +157 -142
- package/shared/localization/locales/cs.json +148 -133
- package/shared/localization/locales/da.json +153 -138
- package/shared/localization/locales/de.json +152 -137
- package/shared/localization/locales/el.json +153 -138
- package/shared/localization/locales/en-GB.json +160 -145
- package/shared/localization/locales/en-US.json +239 -218
- package/shared/localization/locales/en-XL.json +239 -218
- package/shared/localization/locales/es-419.json +150 -135
- package/shared/localization/locales/es.json +149 -134
- package/shared/localization/locales/fi.json +151 -136
- package/shared/localization/locales/fil.json +154 -139
- package/shared/localization/locales/fr.json +150 -135
- package/shared/localization/locales/he.json +160 -145
- package/shared/localization/locales/hi.json +156 -141
- package/shared/localization/locales/hr.json +154 -139
- package/shared/localization/locales/hu.json +111 -96
- package/shared/localization/locales/id.json +153 -138
- package/shared/localization/locales/it.json +153 -138
- package/shared/localization/locales/ja.json +150 -135
- package/shared/localization/locales/ko.json +42 -27
- package/shared/localization/locales/lt.json +152 -137
- package/shared/localization/locales/lv.json +93 -78
- package/shared/localization/locales/nl.json +149 -134
- package/shared/localization/locales/no.json +152 -137
- package/shared/localization/locales/pl.json +153 -138
- package/shared/localization/locales/pt-PT.json +152 -137
- package/shared/localization/locales/pt.json +157 -142
- package/shared/localization/locales/ro.json +149 -134
- package/shared/localization/locales/ru.json +76 -61
- package/shared/localization/locales/sk.json +153 -138
- package/shared/localization/locales/sl.json +149 -134
- package/shared/localization/locales/sr-Latn.json +151 -136
- package/shared/localization/locales/sr.json +151 -136
- package/shared/localization/locales/sv.json +152 -137
- package/shared/localization/locales/ta.json +156 -141
- package/shared/localization/locales/te.json +157 -142
- package/shared/localization/locales/th.json +111 -96
- package/shared/localization/locales/tr.json +153 -138
- package/shared/localization/locales/uk.json +149 -134
- package/shared/localization/locales/vi.json +162 -147
- package/shared/localization/locales/zh-HK.json +150 -135
- package/shared/localization/locales/zh-TW.json +154 -139
- package/shared/localization/locales/zh.json +152 -137
- package/shared/localization/swap-locale.js +10 -5
- package/shared/root.js +1 -3
- package/third-party/ard/README.md +19 -10
- package/third-party/ard/ard-test.js +189 -266
- package/third-party/ard/ard.d.ts +18 -1
- package/third-party/ard/ard.js +282 -45
- package/third-party/ard/schema-validator.d.ts +14 -5
- package/third-party/ard/schema-validator.js +921 -1177
- package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +43 -7
- package/types/config.d.ts +6 -1
- package/types/externs.d.ts +4 -0
- package/types/internal/test.d.ts +1 -0
- package/build-tracker.config.js +0 -16
- package/core/lib/cdt/package.json +0 -4
- package/eslint-local-rules.d.cts +0 -9
- package/eslint.config.mjs +0 -242
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
- package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
- package/third-party/esbuild-plugins-polyfills/README.md +0 -7
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
- package/types/internal/lookup-closest-locale.d.ts +0 -13
package/core/audits/baseline.js
CHANGED
|
@@ -18,7 +18,7 @@ const UIStrings = {
|
|
|
18
18
|
*/
|
|
19
19
|
description:
|
|
20
20
|
'Lists web features used on the page and their Baseline status as of {date}. ' +
|
|
21
|
-
'[Learn more about Baseline](https://
|
|
21
|
+
'[Learn more about Baseline](https://developer.chrome.com/docs/lighthouse/best-practices/baseline-features).',
|
|
22
22
|
/** Label for the column displaying the feature ID. */
|
|
23
23
|
columnFeature: 'Web-features',
|
|
24
24
|
/** Label for the column displaying the feature\'s baseline status. */
|
|
@@ -48,6 +48,18 @@ class Baseline extends Audit {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Determines the baseline status and display string for a given feature ID.
|
|
51
|
+
*
|
|
52
|
+
* Baseline tiers & dates (built by `build/build-baseline-data.js`):
|
|
53
|
+
* - `limited`: `baseline === false` (Limited Availability — not yet in all core browsers, no date).
|
|
54
|
+
* - `low`: `baseline === 'low'` (Newly Available — in all core browsers; value is `baseline_low_date`).
|
|
55
|
+
* - `high`: `baseline === 'high'` (Widely Available — 30 months past `baseline_low_date`; note that
|
|
56
|
+
* `featureData.high[id]` still stores the feature's `baseline_low_date`).
|
|
57
|
+
*
|
|
58
|
+
* Widely Available rows always show `baseline_low_date`, including rows promoted at runtime.
|
|
59
|
+
* If a feature in `featureData.low` has reached `baseline_low_date + 30 months <= currentDate` at runtime,
|
|
60
|
+
* we dynamically promote it from `low` (Newly Available) to `high` (Widely Available); promotion
|
|
61
|
+
* only changes the tier.
|
|
62
|
+
*
|
|
51
63
|
* @param {string} featureId
|
|
52
64
|
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
|
|
53
65
|
* @param {Date} currentDate
|
|
@@ -66,11 +78,10 @@ class Baseline extends Audit {
|
|
|
66
78
|
const lowData = /** @type {Record<string, string>} */ (featureData.low);
|
|
67
79
|
const widelyAvailableDate = new Date(lowData[featureId]);
|
|
68
80
|
widelyAvailableDate.setUTCMonth(widelyAvailableDate.getUTCMonth() + 30);
|
|
69
|
-
const widelyAvailableDateStr = widelyAvailableDate.toISOString().slice(0, 10);
|
|
70
81
|
|
|
71
82
|
if (widelyAvailableDate <= currentDate) {
|
|
72
83
|
return {
|
|
73
|
-
displayStatus: `Widely Available (${
|
|
84
|
+
displayStatus: `Widely Available (${lowData[featureId]})`,
|
|
74
85
|
baselineTier: 'high',
|
|
75
86
|
};
|
|
76
87
|
} else {
|
|
@@ -92,6 +103,10 @@ class Baseline extends Audit {
|
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
/**
|
|
106
|
+
* Returns the `baseline_low_date` (YYYY-MM-DD, when the feature became Newly Available)
|
|
107
|
+
* for both `low` and `high` features, since `build/build-baseline-data.js` stores
|
|
108
|
+
* `baseline_low_date` in both `featureData.low` and `featureData.high`.
|
|
109
|
+
*
|
|
95
110
|
* @param {string} featureId
|
|
96
111
|
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
|
|
97
112
|
* @return {string|null}
|
|
@@ -103,9 +118,7 @@ class Baseline extends Audit {
|
|
|
103
118
|
}
|
|
104
119
|
if (featureId in featureData.high) {
|
|
105
120
|
const highData = /** @type {Record<string, string>} */ (featureData.high);
|
|
106
|
-
|
|
107
|
-
date.setUTCMonth(date.getUTCMonth() - 30);
|
|
108
|
-
return date.toISOString().slice(0, 10);
|
|
121
|
+
return highData[featureId];
|
|
109
122
|
}
|
|
110
123
|
return null;
|
|
111
124
|
}
|
|
@@ -10,7 +10,7 @@ import {NetworkRecords} from '../../computed/network-records.js';
|
|
|
10
10
|
import {LoadSimulator} from '../../computed/load-simulator.js';
|
|
11
11
|
import {LanternLargestContentfulPaint as LanternLCP} from '../../computed/metrics/lantern-largest-contentful-paint.js';
|
|
12
12
|
import {LanternFirstContentfulPaint as LanternFCP} from '../../computed/metrics/lantern-first-contentful-paint.js';
|
|
13
|
-
import {
|
|
13
|
+
import {LCPNetworkRecord} from '../../computed/lcp-network-record.js';
|
|
14
14
|
|
|
15
15
|
const str_ = i18n.createIcuMessageFn(import.meta.url, {});
|
|
16
16
|
|
|
@@ -190,7 +190,7 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
190
190
|
|
|
191
191
|
// The LCP graph can underestimate the LCP savings if there is potential savings on the LCP record itself.
|
|
192
192
|
let lcpRecordSavings = 0;
|
|
193
|
-
const lcpRecord = await
|
|
193
|
+
const lcpRecord = await LCPNetworkRecord.request(metricComputationInput, context);
|
|
194
194
|
if (lcpRecord) {
|
|
195
195
|
const lcpResult = results.find(result => result.url === lcpRecord.url);
|
|
196
196
|
if (lcpResult) {
|
|
@@ -21,6 +21,15 @@ export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/tr
|
|
|
21
21
|
numericValue?: number;
|
|
22
22
|
numericUnit?: LH.Audit.NumericProduct["numericUnit"];
|
|
23
23
|
} | LH.Audit.Details | undefined): Promise<LH.Audit.Product>;
|
|
24
|
+
/**
|
|
25
|
+
* @param {LH.Artifacts} artifacts
|
|
26
|
+
* @param {LH.Audit.Context} context
|
|
27
|
+
* @return {Promise<{insights: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet|undefined, data: LH.Artifacts.TraceEngineResult['data']}>}
|
|
28
|
+
*/
|
|
29
|
+
export function getInsightSet(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<{
|
|
30
|
+
insights: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightSet | undefined;
|
|
31
|
+
data: LH.Artifacts.TraceEngineResult["data"];
|
|
32
|
+
}>;
|
|
24
33
|
/**
|
|
25
34
|
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
26
35
|
* @param {number|null|undefined} nodeId
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {NO_NAVIGATION} from '@paulirish/trace_engine/models/trace/types/TraceEvents.js';
|
|
8
8
|
|
|
9
|
+
import {Helpers} from '../../lib/trace-engine.js';
|
|
9
10
|
import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
10
11
|
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
11
12
|
import {Audit} from '../audit.js';
|
|
@@ -28,8 +29,8 @@ async function getInsightSet(artifacts, context) {
|
|
|
28
29
|
|
|
29
30
|
const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
|
|
30
31
|
const insights = navigationId ?
|
|
31
|
-
[...traceEngineResult.insights.values()]
|
|
32
|
-
.
|
|
32
|
+
[...traceEngineResult.insights.values()].find(insightSet =>
|
|
33
|
+
Helpers.Trace.navigationIDForEvent(insightSet.navigation) === navigationId) :
|
|
33
34
|
traceEngineResult.insights.get(NO_NAVIGATION);
|
|
34
35
|
|
|
35
36
|
return {insights, data: traceEngineResult.data};
|
|
@@ -136,36 +137,41 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
136
137
|
// TODO: consider adding a `estimatedSavingsText` to InsightModel, which can capture
|
|
137
138
|
// the exact i18n string used by RPP; and include the same est. timing savings.
|
|
138
139
|
let displayValue;
|
|
140
|
+
let wastedMs;
|
|
141
|
+
|
|
142
|
+
switch (insight.insightKey) {
|
|
143
|
+
case 'DocumentLatency':
|
|
144
|
+
case 'FontDisplay':
|
|
145
|
+
case 'RenderBlocking': {
|
|
146
|
+
wastedMs = metricSavings?.FCP ?? 0;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
139
149
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
switch (insight.insightKey) {
|
|
146
|
-
case 'DocumentLatency':
|
|
147
|
-
case 'DuplicatedJavaScript':
|
|
148
|
-
case 'FontDisplay':
|
|
149
|
-
case 'LegacyJavaScript':
|
|
150
|
-
case 'RenderBlocking': {
|
|
151
|
-
wastedMs = metricSavings?.FCP;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
case 'LCPDiscovery':
|
|
156
|
-
case 'ModernHTTP': {
|
|
157
|
-
wastedMs = metricSavings?.LCP;
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
150
|
+
case 'LCPDiscovery':
|
|
151
|
+
case 'ModernHTTP': {
|
|
152
|
+
wastedMs = metricSavings?.LCP ?? 0;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
160
155
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
156
|
+
case 'Viewport': {
|
|
157
|
+
wastedMs = metricSavings?.INP ?? 0;
|
|
158
|
+
break;
|
|
165
159
|
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (insight.wastedBytes) {
|
|
163
|
+
displayValue = str_(i18n.UIStrings.displayValueByteSavings, {wastedBytes: insight.wastedBytes});
|
|
164
|
+
} else if (wastedMs) {
|
|
165
|
+
displayValue = str_(i18n.UIStrings.displayValueMsSavings, {wastedMs});
|
|
166
|
+
}
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
if (numericValue === undefined) {
|
|
169
|
+
if (wastedMs !== undefined) {
|
|
170
|
+
numericValue = wastedMs;
|
|
171
|
+
numericUnit = 'millisecond';
|
|
172
|
+
} else if (insight.wastedBytes !== undefined) {
|
|
173
|
+
numericValue = insight.wastedBytes;
|
|
174
|
+
numericUnit = 'byte';
|
|
169
175
|
}
|
|
170
176
|
}
|
|
171
177
|
|
|
@@ -225,5 +231,6 @@ function makeNodeItemForNodeId(traceElements, nodeId) {
|
|
|
225
231
|
|
|
226
232
|
export {
|
|
227
233
|
adaptInsightToAuditProduct,
|
|
234
|
+
getInsightSet,
|
|
228
235
|
makeNodeItemForNodeId,
|
|
229
236
|
};
|
|
@@ -192,9 +192,10 @@ class LongTasks extends Audit {
|
|
|
192
192
|
taskTimingsByEvent = new Map();
|
|
193
193
|
|
|
194
194
|
const simulatorOptions = {devtoolsLog, settings: context.settings};
|
|
195
|
+
const fromTrace = process.env.INTERNAL_LANTERN_USE_TRACE !== undefined;
|
|
195
196
|
const pageGraph =
|
|
196
197
|
// eslint-disable-next-line max-len
|
|
197
|
-
await PageDependencyGraph.request({settings, trace, devtoolsLog, URL, SourceMaps, HostDPR, fromTrace
|
|
198
|
+
await PageDependencyGraph.request({settings, trace, devtoolsLog, URL, SourceMaps, HostDPR, fromTrace}, context);
|
|
198
199
|
const simulator = await LoadSimulator.request(simulatorOptions, context);
|
|
199
200
|
const simulation = simulator.simulate(pageGraph, {label: 'long-tasks-diagnostic'});
|
|
200
201
|
for (const [node, timing] of simulation.nodeTimings.entries()) {
|
|
@@ -76,8 +76,8 @@ class PredictivePerf extends Audit {
|
|
|
76
76
|
pessimisticLCP: lcp.pessimisticEstimate.timeInMs,
|
|
77
77
|
|
|
78
78
|
roughEstimateOfTTFB: timingSummary.metrics.timeToFirstByte,
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
roughEstimateOfLCPLoadDelay: timingSummary.metrics.lcpLoadDelay,
|
|
80
|
+
roughEstimateOfLCPLoadDuration: timingSummary.metrics.lcpLoadDuration,
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const score = Audit.computeLogNormalScore(
|
|
@@ -2,19 +2,6 @@ export default ScreenshotThumbnails;
|
|
|
2
2
|
export type SpeedlineFrame = LH.Artifacts.Speedline["frames"][0];
|
|
3
3
|
/** @typedef {LH.Artifacts.Speedline['frames'][0]} SpeedlineFrame */
|
|
4
4
|
declare class ScreenshotThumbnails extends Audit {
|
|
5
|
-
/**
|
|
6
|
-
* Scales down an image to THUMBNAIL_WIDTH using nearest neighbor for speed, maintains aspect
|
|
7
|
-
* ratio of the original thumbnail.
|
|
8
|
-
*
|
|
9
|
-
* @param {ReturnType<SpeedlineFrame['getParsedImage']>} imageData
|
|
10
|
-
* @param {number} scaledWidth
|
|
11
|
-
* @return {{width: number, height: number, data: Uint8Array}}
|
|
12
|
-
*/
|
|
13
|
-
static scaleImageToThumbnail(imageData: ReturnType<SpeedlineFrame["getParsedImage"]>, scaledWidth: number): {
|
|
14
|
-
width: number;
|
|
15
|
-
height: number;
|
|
16
|
-
data: Uint8Array;
|
|
17
|
-
};
|
|
18
5
|
/**
|
|
19
6
|
* @param {LH.Artifacts} artifacts
|
|
20
7
|
* @param {LH.Audit.Context} context
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import jpeg from 'jpeg-js';
|
|
8
|
-
|
|
9
7
|
import {Audit} from './audit.js';
|
|
10
8
|
import {LighthouseError} from '../lib/lh-error.js';
|
|
11
9
|
import {Speedline} from '../computed/speedline.js';
|
|
@@ -28,42 +26,6 @@ class ScreenshotThumbnails extends Audit {
|
|
|
28
26
|
};
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
/**
|
|
32
|
-
* Scales down an image to THUMBNAIL_WIDTH using nearest neighbor for speed, maintains aspect
|
|
33
|
-
* ratio of the original thumbnail.
|
|
34
|
-
*
|
|
35
|
-
* @param {ReturnType<SpeedlineFrame['getParsedImage']>} imageData
|
|
36
|
-
* @param {number} scaledWidth
|
|
37
|
-
* @return {{width: number, height: number, data: Uint8Array}}
|
|
38
|
-
*/
|
|
39
|
-
static scaleImageToThumbnail(imageData, scaledWidth) {
|
|
40
|
-
const scaleFactor = imageData.width / scaledWidth;
|
|
41
|
-
const scaledHeight = Math.floor(imageData.height / scaleFactor);
|
|
42
|
-
|
|
43
|
-
const outPixels = new Uint8Array(scaledWidth * scaledHeight * 4);
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < scaledWidth; i++) {
|
|
46
|
-
for (let j = 0; j < scaledHeight; j++) {
|
|
47
|
-
const origX = Math.floor(i * scaleFactor);
|
|
48
|
-
const origY = Math.floor(j * scaleFactor);
|
|
49
|
-
|
|
50
|
-
const origPos = (origY * imageData.width + origX) * 4;
|
|
51
|
-
const outPos = (j * scaledWidth + i) * 4;
|
|
52
|
-
|
|
53
|
-
outPixels[outPos] = imageData.data[origPos];
|
|
54
|
-
outPixels[outPos + 1] = imageData.data[origPos + 1];
|
|
55
|
-
outPixels[outPos + 2] = imageData.data[origPos + 2];
|
|
56
|
-
outPixels[outPos + 3] = imageData.data[origPos + 3];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
width: scaledWidth,
|
|
62
|
-
height: scaledHeight,
|
|
63
|
-
data: outPixels,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
29
|
/**
|
|
68
30
|
* @param {LH.Artifacts} artifacts
|
|
69
31
|
* @param {LH.Audit.Context} context
|
|
@@ -79,7 +41,6 @@ class ScreenshotThumbnails extends Audit {
|
|
|
79
41
|
// Make the minimum time range 3s so sites that load super quickly don't get a single screenshot
|
|
80
42
|
const minimumTimelineDuration = context.options.minimumTimelineDuration || 3000;
|
|
81
43
|
const numberOfThumbnails = context.options.numberOfThumbnails || NUMBER_OF_THUMBNAILS;
|
|
82
|
-
const thumbnailWidth = context.options.thumbnailWidth || null;
|
|
83
44
|
|
|
84
45
|
const thumbnails = [];
|
|
85
46
|
const analyzedFrames = speedline.frames.filter(frame => !frame.isProgressInterpolated());
|
|
@@ -112,12 +73,6 @@ class ScreenshotThumbnails extends Audit {
|
|
|
112
73
|
const cachedThumbnail = cachedThumbnails.get(frameForTimestamp);
|
|
113
74
|
if (cachedThumbnail) {
|
|
114
75
|
base64Data = cachedThumbnail;
|
|
115
|
-
} else if (thumbnailWidth !== null) {
|
|
116
|
-
const imageData = frameForTimestamp.getParsedImage();
|
|
117
|
-
const thumbnailImageData =
|
|
118
|
-
ScreenshotThumbnails.scaleImageToThumbnail(imageData, thumbnailWidth);
|
|
119
|
-
base64Data = jpeg.encode(thumbnailImageData, 90).data.toString('base64');
|
|
120
|
-
cachedThumbnails.set(frameForTimestamp, base64Data);
|
|
121
76
|
} else {
|
|
122
77
|
base64Data = frameForTimestamp.getImage().toString('base64');
|
|
123
78
|
cachedThumbnails.set(frameForTimestamp, base64Data);
|
|
@@ -176,11 +176,32 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
176
176
|
const nodes = [];
|
|
177
177
|
/** @type {Map<string, LH.Treemap.Node>} */
|
|
178
178
|
const htmlNodesByFrameId = new Map();
|
|
179
|
-
|
|
180
|
-
const
|
|
179
|
+
/** @type {Set<LH.Artifacts.NetworkRequest>} */
|
|
180
|
+
const seenNetworkRecords = new Set();
|
|
181
|
+
const deduplicatedScripts = artifacts.Scripts.filter(script => {
|
|
182
|
+
if (script.scriptLanguage !== 'JavaScript') return false;
|
|
183
|
+
|
|
184
|
+
const networkRecord = isInline(script) ?
|
|
185
|
+
undefined :
|
|
186
|
+
getRequestForScript(networkRecords, script);
|
|
187
|
+
if (networkRecord) {
|
|
188
|
+
if (seenNetworkRecords.has(networkRecord)) return false;
|
|
189
|
+
seenNetworkRecords.add(networkRecord);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return true;
|
|
193
|
+
});
|
|
194
|
+
const deduplicatedArtifacts = deduplicatedScripts.length === artifacts.Scripts.length ?
|
|
195
|
+
artifacts :
|
|
196
|
+
{...artifacts, Scripts: deduplicatedScripts};
|
|
197
|
+
const bundles = await JSBundles.request(deduplicatedArtifacts, context);
|
|
198
|
+
const duplicationByPath = await ModuleDuplication.request(deduplicatedArtifacts, context);
|
|
181
199
|
|
|
182
|
-
for (const script of
|
|
183
|
-
|
|
200
|
+
for (const script of deduplicatedScripts) {
|
|
201
|
+
const isInlineScript = isInline(script);
|
|
202
|
+
const networkRecord = isInlineScript ?
|
|
203
|
+
undefined :
|
|
204
|
+
getRequestForScript(networkRecords, script);
|
|
184
205
|
|
|
185
206
|
const name = script.url;
|
|
186
207
|
const bundle = bundles.find(bundle => script.scriptId === bundle.script.scriptId) ?? null;
|
|
@@ -246,7 +267,7 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
246
267
|
|
|
247
268
|
// If this is an inline script, place the node inside a top-level (aka depth-one) node.
|
|
248
269
|
// Also separate each iframe / the main page's inline scripts into their own top-level nodes.
|
|
249
|
-
if (
|
|
270
|
+
if (isInlineScript) {
|
|
250
271
|
let htmlNode = htmlNodesByFrameId.get(script.executionContextAuxData.frameId);
|
|
251
272
|
if (!htmlNode) {
|
|
252
273
|
htmlNode = {
|
|
@@ -269,7 +290,6 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
269
290
|
// Non-inline scripts each have their own top-level node.
|
|
270
291
|
nodes.push(node);
|
|
271
292
|
|
|
272
|
-
const networkRecord = getRequestForScript(networkRecords, script);
|
|
273
293
|
if (networkRecord) {
|
|
274
294
|
const bodyTransferSize =
|
|
275
295
|
networkRecord.transferSize - networkRecord.responseHeadersTransferSize;
|
|
@@ -27,7 +27,7 @@ const DIRECTIVE_SAFELIST = new Set([
|
|
|
27
27
|
DIRECTIVE_ALLOW, DIRECTIVE_SITEMAP, // universally supported
|
|
28
28
|
'crawl-delay', // yahoo, bing, yandex
|
|
29
29
|
'clean-param', 'host', // yandex
|
|
30
|
-
'request-rate', 'visit-time', 'noindex', 'content-signal', // not officially supported, but used in the wild
|
|
30
|
+
'request-rate', 'visit-time', 'noindex', 'content-signal', 'license', // not officially supported, but used in the wild
|
|
31
31
|
]);
|
|
32
32
|
const SITEMAP_VALID_PROTOCOLS = new Set(['https:', 'http:', 'ftp:']);
|
|
33
33
|
|
|
@@ -209,7 +209,7 @@ class RobotsTxt extends Audit {
|
|
|
209
209
|
} = artifacts.RobotsTxt;
|
|
210
210
|
|
|
211
211
|
// Do specific error messages first.
|
|
212
|
-
if (status && status >= HTTP_SERVER_ERROR_CODE_LOW) {
|
|
212
|
+
if (status && (status >= HTTP_SERVER_ERROR_CODE_LOW || status === 429)) {
|
|
213
213
|
return {
|
|
214
214
|
score: 0,
|
|
215
215
|
displayValue: str_(UIStrings.displayValueHttpBadCode, {statusCode: status}),
|
|
@@ -228,18 +228,13 @@ class RobotsTxt extends Audit {
|
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
if (!status) {
|
|
231
|
+
if (!status || content === null) {
|
|
232
232
|
return {
|
|
233
233
|
score: 0,
|
|
234
234
|
explanation: str_(UIStrings.explanation),
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
// If status is good, content must be not null.
|
|
239
|
-
if (content === null) {
|
|
240
|
-
throw new Error(`Status ${status} was valid, but content was null`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
238
|
const validationErrors = validateRobots(content);
|
|
244
239
|
|
|
245
240
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
export default UnsizedImages;
|
|
2
2
|
declare class UnsizedImages extends Audit {
|
|
3
|
-
/**
|
|
4
|
-
* An img size attribute prevents layout shifts if it is a non-negative integer (incl zero!).
|
|
5
|
-
* @url https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes
|
|
6
|
-
* @param {string | null} attrValue
|
|
7
|
-
* @return {boolean}
|
|
8
|
-
*/
|
|
9
|
-
static doesHtmlAttrProvideExplicitSize(attrValue: string | null): boolean;
|
|
10
3
|
/**
|
|
11
4
|
* An img css size property prevents layout shifts if it is defined, not empty, and not equal to 'auto'.
|
|
12
5
|
* @param {string | null} property
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
import {Audit} from './audit.js';
|
|
13
|
+
import ImageElements from '../gather/gatherers/image-elements.js';
|
|
13
14
|
import * as i18n from './../lib/i18n/i18n.js';
|
|
14
15
|
import UrlUtils from '../lib/url-utils.js';
|
|
15
16
|
|
|
@@ -40,28 +41,6 @@ class UnsizedImages extends Audit {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/**
|
|
44
|
-
* An img size attribute prevents layout shifts if it is a non-negative integer (incl zero!).
|
|
45
|
-
* @url https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes
|
|
46
|
-
* @param {string | null} attrValue
|
|
47
|
-
* @return {boolean}
|
|
48
|
-
*/
|
|
49
|
-
static doesHtmlAttrProvideExplicitSize(attrValue) {
|
|
50
|
-
if (!attrValue) return false;
|
|
51
|
-
|
|
52
|
-
// First, superweird edge case of using the positive-sign. The spec _sorta_ says it's valid...
|
|
53
|
-
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-integers
|
|
54
|
-
// > Otherwise, if the character is … (+): Advance position to the next character.
|
|
55
|
-
// > (The "+" is ignored, but it is not conforming.)
|
|
56
|
-
// lol. Irrelevant though b/c Chrome (at least) doesn't ignore. It rejects this as a non-conforming value.
|
|
57
|
-
if (attrValue.startsWith('+')) return false;
|
|
58
|
-
|
|
59
|
-
// parseInt isn't exactly the same as the html's spec for parsing integers, but it's close enough
|
|
60
|
-
// https://tc39.es/ecma262/#sec-parseint-string-radix
|
|
61
|
-
const int = parseInt(attrValue, 10);
|
|
62
|
-
return int >= 0;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
44
|
/**
|
|
66
45
|
* An img css size property prevents layout shifts if it is defined, not empty, and not equal to 'auto'.
|
|
67
46
|
* @param {string | null} property
|
|
@@ -78,6 +57,12 @@ class UnsizedImages extends Audit {
|
|
|
78
57
|
* @return {boolean}
|
|
79
58
|
*/
|
|
80
59
|
static isSizedImage(image) {
|
|
60
|
+
const attrWidth = image.attributeWidth;
|
|
61
|
+
const attrHeight = image.attributeHeight;
|
|
62
|
+
const htmlWidthIsExplicit = ImageElements.doesHtmlAttrProvideExplicitSize(attrWidth);
|
|
63
|
+
const htmlHeightIsExplicit = ImageElements.doesHtmlAttrProvideExplicitSize(attrHeight);
|
|
64
|
+
if (htmlWidthIsExplicit && htmlHeightIsExplicit) return true;
|
|
65
|
+
|
|
81
66
|
// Perhaps we hit reachedGatheringBudget before collecting this image's cssWidth/Height
|
|
82
67
|
// in fetchSourceRules. In this case, we don't have enough information to determine if it's sized.
|
|
83
68
|
// We don't want to show the user a false positive, so we'll call it sized to give it as pass.
|
|
@@ -85,14 +70,10 @@ class UnsizedImages extends Audit {
|
|
|
85
70
|
// Handwavey TODO: explore ways to avoid this.
|
|
86
71
|
if (image.cssEffectiveRules === undefined) return true;
|
|
87
72
|
|
|
88
|
-
const attrWidth = image.attributeWidth;
|
|
89
|
-
const attrHeight = image.attributeHeight;
|
|
90
73
|
const cssWidth = image.cssEffectiveRules.width;
|
|
91
74
|
const cssHeight = image.cssEffectiveRules.height;
|
|
92
75
|
const cssAspectRatio = image.cssEffectiveRules.aspectRatio;
|
|
93
|
-
const htmlWidthIsExplicit = UnsizedImages.doesHtmlAttrProvideExplicitSize(attrWidth);
|
|
94
76
|
const cssWidthIsExplicit = UnsizedImages.isCssPropExplicitlySet(cssWidth);
|
|
95
|
-
const htmlHeightIsExplicit = UnsizedImages.doesHtmlAttrProvideExplicitSize(attrHeight);
|
|
96
77
|
const cssHeightIsExplicit = UnsizedImages.isCssPropExplicitlySet(cssHeight);
|
|
97
78
|
const explicitAspectRatio = UnsizedImages.isCssPropExplicitlySet(cssAspectRatio);
|
|
98
79
|
const explicitWidth = htmlWidthIsExplicit || cssWidthIsExplicit;
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
request: (dependencies: {
|
|
4
|
-
trace: LH.Trace;
|
|
5
|
-
devtoolsLog: LH.DevtoolsLog;
|
|
6
|
-
}, context: LH.Artifacts.ComputedContext) => Promise<import("../lib/network-request.js").NetworkRequest | undefined>;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* @fileoverview Match the LCP event with the paint event to get the request of the image actually painted.
|
|
10
|
-
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
11
|
-
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
12
|
-
*/
|
|
13
|
-
declare class LCPImageRecord {
|
|
14
|
-
/**
|
|
15
|
-
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
16
|
-
* @param {LH.Artifacts.ComputedContext} context
|
|
17
|
-
* @return {Promise<LH.Artifacts.NetworkRequest|undefined>}
|
|
18
|
-
*/
|
|
19
|
-
static compute_(data: {
|
|
20
|
-
trace: LH.Trace;
|
|
21
|
-
devtoolsLog: LH.DevtoolsLog;
|
|
22
|
-
}, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.NetworkRequest | undefined>;
|
|
23
|
-
}
|
|
1
|
+
export { LCPNetworkRecord as LCPImageRecord };
|
|
2
|
+
import { LCPNetworkRecord } from './lcp-network-record.js';
|
|
24
3
|
//# sourceMappingURL=lcp-image-record.d.ts.map
|
|
@@ -4,71 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {ProcessedNavigation} from './processed-navigation.js';
|
|
10
|
-
import {LighthouseError} from '../lib/lh-error.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @fileoverview Match the LCP event with the paint event to get the request of the image actually painted.
|
|
14
|
-
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
15
|
-
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
class LCPImageRecord {
|
|
19
|
-
/**
|
|
20
|
-
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
21
|
-
* @param {LH.Artifacts.ComputedContext} context
|
|
22
|
-
* @return {Promise<LH.Artifacts.NetworkRequest|undefined>}
|
|
23
|
-
*/
|
|
24
|
-
static async compute_(data, context) {
|
|
25
|
-
const {trace, devtoolsLog} = data;
|
|
26
|
-
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
27
|
-
const processedNavigation = await ProcessedNavigation.request(trace, context);
|
|
28
|
-
if (processedNavigation.timings.largestContentfulPaint === undefined) {
|
|
29
|
-
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Use main-frame-only LCP to match the metric value.
|
|
33
|
-
const lcpEvent = processedNavigation.largestContentfulPaintEvt;
|
|
34
|
-
if (!lcpEvent) return;
|
|
35
|
-
|
|
36
|
-
const lcpImagePaintEvent = trace.traceEvents.filter(e => {
|
|
37
|
-
return e.name === 'LargestImagePaint::Candidate' &&
|
|
38
|
-
e.args.frame === lcpEvent.args.frame &&
|
|
39
|
-
e.args.data?.DOMNodeId === lcpEvent.args.data?.nodeId &&
|
|
40
|
-
e.args.data?.size === lcpEvent.args.data?.size;
|
|
41
|
-
// Get last candidate, in case there was more than one.
|
|
42
|
-
}).sort((a, b) => b.ts - a.ts)[0];
|
|
43
|
-
|
|
44
|
-
const lcpUrl = lcpImagePaintEvent?.args.data?.imageUrl;
|
|
45
|
-
if (!lcpUrl) return;
|
|
46
|
-
|
|
47
|
-
const candidates = networkRecords.filter(record => {
|
|
48
|
-
return record.url === lcpUrl &&
|
|
49
|
-
record.finished &&
|
|
50
|
-
// Same frame as LCP trace event.
|
|
51
|
-
record.frameId === lcpImagePaintEvent.args.frame &&
|
|
52
|
-
record.networkRequestTime < (processedNavigation.timestamps.largestContentfulPaint || 0);
|
|
53
|
-
}).map(record => {
|
|
54
|
-
// Follow any redirects to find the real image request.
|
|
55
|
-
while (record.redirectDestination) {
|
|
56
|
-
record = record.redirectDestination;
|
|
57
|
-
}
|
|
58
|
-
return record;
|
|
59
|
-
}).filter(record => {
|
|
60
|
-
// Don't select if also loaded by some other means (xhr, etc). `resourceType`
|
|
61
|
-
// isn't set on redirect _sources_, so have to check after following redirects.
|
|
62
|
-
return record.resourceType === 'Image';
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// If there are still multiple candidates, at this point it appears the page
|
|
66
|
-
// simply made multiple requests for the image. The first loaded is the best
|
|
67
|
-
// guess of the request that made the image available for use.
|
|
68
|
-
return candidates.sort((a, b) => a.networkEndTime - b.networkEndTime)[0];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const LCPImageRecordComputed = makeComputedArtifact(LCPImageRecord, ['devtoolsLog', 'trace']);
|
|
73
|
-
export {LCPImageRecordComputed as LCPImageRecord};
|
|
7
|
+
// TODO(v14): remove
|
|
8
|
+
import {LCPNetworkRecord} from './lcp-network-record.js';
|
|
74
9
|
|
|
10
|
+
export {LCPNetworkRecord as LCPImageRecord};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { LCPNetworkRecordComputed as LCPNetworkRecord };
|
|
2
|
+
declare const LCPNetworkRecordComputed: typeof LCPNetworkRecord & {
|
|
3
|
+
request: (dependencies: {
|
|
4
|
+
trace: LH.Trace;
|
|
5
|
+
devtoolsLog: LH.DevtoolsLog;
|
|
6
|
+
}, context: LH.Artifacts.ComputedContext) => Promise<import("../lib/network-request.js").NetworkRequest | undefined>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* @fileoverview Match the LCP event with the paint event to get the request of the image actually painted.
|
|
10
|
+
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
11
|
+
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
12
|
+
*/
|
|
13
|
+
declare class LCPNetworkRecord {
|
|
14
|
+
/**
|
|
15
|
+
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
16
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
17
|
+
* @return {Promise<LH.Artifacts.NetworkRequest|undefined>}
|
|
18
|
+
*/
|
|
19
|
+
static compute_(data: {
|
|
20
|
+
trace: LH.Trace;
|
|
21
|
+
devtoolsLog: LH.DevtoolsLog;
|
|
22
|
+
}, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.NetworkRequest | undefined>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=lcp-network-record.d.ts.map
|